Fossil

Changes On Branch bv-infotool
Login

Changes On Branch bv-infotool

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

Changes In Branch bv-infotool Excluding Merge-Ins

This is equivalent to a diff from 735bd3dccb to 031f1e6d9f

2026-02-11
06:41
Tiny nip-tuck for info text. ... (Leaf check-in: 031f1e6d9f user: brickviking tags: bv-infotool)
2026-02-10
21:58
Tidying up the script comments. ... (check-in: 663d33c226 user: brickviking tags: bv-infotool)
2024-11-05
09:55
Merge from trunk ... (check-in: e367ca7373 user: brickviking tags: bv-corrections01)
05:57
Create new branch named "bv-infotool" ... (check-in: e413dc32f0 user: brickviking tags: bv-infotool)
2024-11-04
13:09
Fix (harmless) off-by-one error in the new test-trust-store command. ... (Leaf check-in: 735bd3dccb user: drh tags: httpmsg-debug)
12:54
Improvements to the diagnostic output from the test-trust-store command. ... (check-in: aa5bddda68 user: drh tags: httpmsg-debug)

Changes to .fossil-settings/crlf-glob.
1
2
3
4


5
1
2
3
4
5
6
7




+
+

compat/zlib/*
setup/fossil.iss
test/th1-docs-input.txt
test/th1-hooks-input.txt
win/build32.bat
win/build64.bat
win/buildmsvc.bat
Changes to .fossil-settings/ignore-glob.
1
2
3
4
5
6
7
8
9

1
2
3
4
5
6
7
8
9
10









+
compat/openssl*
compat/tcl*
compat/zlib/contrib/ada/*
compat/zlib/doc/*
fossil
fossil.exe
win/fossil.exe
*shell-see.*
*sqlite3-see.*
bld
Changes to Dockerfile.
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
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







-
+







-
+











-
+





-
-
-
+
+
+







# syntax=docker/dockerfile:1.3
# See www/containers.md for documentation on how to use this file.

## ---------------------------------------------------------------------
## STAGE 1: Build static Fossil binary
## ---------------------------------------------------------------------

### We aren't pinning to a more stable version of Alpine because we want
### We don't pin a more stable version of our base layer because we want
### to build with the latest tools and libraries available in case they
### fixed something that matters to us since the last build.  Everything
### below depends on this layer, and so, alas, we toss this container's
### cache on Alpine's release schedule, roughly once a month.
FROM alpine:latest AS bld
WORKDIR /fsl

### Bake the basic Alpine Linux into a base layer so it only changes
### Bake the build-time userland into a base layer so it only changes
### when the upstream image is updated or we change the package set.
RUN set -x                                                             \
    && apk update                                                      \
    && apk upgrade --no-cache                                          \
    && apk add --no-cache                                              \
         gcc make                                                      \
         linux-headers musl-dev                                        \
         openssl-dev openssl-libs-static                               \
         zlib-dev zlib-static

### Build Fossil as a separate layer so we don't have to rebuild the
### Alpine environment for each iteration of Fossil's dev cycle.
### userland for each iteration of Fossil's dev cycle.
###
### We must cope with a bizarre ADD misfeature here: it unpacks tarballs
### automatically when you give it a local file name but not if you give
### it a /tarball URL!  It matters because we default to a URL in case
### you're building outside a Fossil checkout, but when building via the
### container-image target, we avoid a costly hit on fossil-scm.org
### by leveraging its DVCS nature via the "tarball" command and passing
### the resulting file's name in.
### container-image target, we avoid a costly hit on fossil-scm.org by
### leveraging its DVCS nature via the "tarball" command and passing the
### resulting file's name in.
ARG FSLCFG=""
ARG FSLVER="trunk"
ARG FSLURL="https://fossil-scm.org/home/tarball/src?r=${FSLVER}"
ENV FSLSTB=/fsl/src.tar.gz
ADD $FSLURL $FSLSTB
RUN set -x                                                             \
    && if [ -d $FSLSTB ] ;                                             \
Changes to Makefile.classic.
93
94
95
96
97
98
99
100

101
102
103
104
105
106
107
93
94
95
96
97
98
99

100
101
102
103
104
105
106
107







-
+







# You should not need to change anything below this line
###############################################################################
#
# Automatic platform-specific options.
HOST_OS_CMD = uname -s
HOST_OS = $(HOST_OS_CMD:sh)

LIB.SunOS= -lsocket -lnsl
LIB.SunOS= -lsocket -lnsl -lrt
LIB += $(LIB.$(HOST_OS))

TCC.DragonFly += -DUSE_PREAD
TCC.FreeBSD += -DUSE_PREAD
TCC.NetBSD += -DUSE_PREAD
TCC.OpenBSD += -DUSE_PREAD
TCC += $(TCC.$(HOST_OS))
Changes to Makefile.in.
73
74
75
76
77
78
79
80

81
82
83
84
85
86
87
73
74
75
76
77
78
79

80
81
82
83
84
85
86
87







-
+







USE_SEE = @USE_SEE@
APPNAME = fossil
#
# APPNAME = fossil-fuzz
# may be more appropriate for fuzzing.

#### Emscripten stuff for optionally doing in-tree builds
# of any WASM components. We store precompiled WASM in the the SCM, so
# of any WASM components. We store precompiled WASM in the SCM, so
# this is only useful for people who actively work on WASM
# components. EMSDK_ENV refers to the "environment" script which comes
# with the Emscripten SDK package:
# https://emscripten.org/docs/getting_started/downloads.html
EMSDK_HOME = @EMSDK_HOME@
EMSDK_ENV = @EMSDK_ENV@
EMCC_OPT = @EMCC_OPT@
112
113
114
115
116
117
118
119

120
121
122
123
124
125
126
112
113
114
115
116
117
118

119
120
121
122
123
124
125
126







-
+







# Automatically reconfigure whenever an autosetup file or one of the
# make source files change.
#
# The "touch" is necessary to avoid a make loop due to a new upstream
# feature in autosetup (GH 0a71e3c3b7) which rewrites *.in outputs only
# if doing so will write different contents; otherwise, it leaves them
# alone so the mtime doesn't change.  This means that if you change one
# our depdendencies besides Makefile.in, we'll reconfigure but Makefile
# of our dependencies besides Makefile.in, we'll reconfigure but Makefile
# won't change, so this rule will remain out of date, so we'll reconfig
# but Makefile won't change, so we'll reconfig but... endlessly.
#
# This is also why we repeat the reconfig target's command here instead
# of delegating to it with "$(MAKE) reconfig": having children running
# around interfering makes this failure mode even worse.
Makefile: @srcdir@/Makefile.in $(SRCDIR)/main.mk @AUTODEPS@
Changes to VERSION.
1


1
-
+
2.25
2.28
Changes to auto.def.
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
165
166
167
168
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183







-
+




-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+










-
+







-
+

-
+
















-
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
+














-
+







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    compile-commands=0 =>
      "Check for compile_commands.json support."
}

# Update the minimum required SQLite version number here, and also
# in src/main.c near the sqlite3_libversion_number() call.  Take care
# that both places agree!
define MINIMUM_SQLITE_VERSION "3.46.0"
define MINIMUM_SQLITE_VERSION "3.49.0"

# This is useful for people wanting Fossil to use an external SQLite library
# to compare the one they have against the minimum required
if {[opt-bool print-minimum-sqlite-version]} {
    puts [get-define MINIMUM_SQLITE_VERSION]
    exit 0
  puts [get-define MINIMUM_SQLITE_VERSION]
  exit 0
}

# Space characters have never been allowed in either the source
# tree nor the build directory.  But the resulting error messages
# could be confusing.  The following checks make the reason for the
# failure clear.
#
if {[string first " " $autosetup(srcdir)] != -1} {
  user-error "The pathname of the source tree\
              may not contain space characters"
}
if {[string first " " $autosetup(builddir)] != -1} {
  user-error "The pathname of the build directory\
              may not contain space characters"
}

set outOfTreeBuild 0
if {![file exists fossil.1]} {
  puts "This appears to be an out-of-tree build."
  set outOfTreeBuild 1
}

# sqlite wants these types if possible
cc-with {-includes {stdint.h inttypes.h}} {
    cc-check-types uint32_t uint16_t int16_t uint8_t
  cc-check-types uint32_t uint16_t int16_t uint8_t
}

# Use pread/pwrite system calls in place of seek + read/write if possible
define USE_PREAD [cc-check-functions pread]

# If we have cscope here, we'll use it in the "tags" target
if {[cc-check-progs cscope]} {
    define COLLECT_CSCOPE_DATA "cscope -bR $::autosetup(srcdir)/src/*.\[ch\]"
  define COLLECT_CSCOPE_DATA "cscope -bR $::autosetup(srcdir)/src/*.\[ch\]"
} else {
    define COLLECT_CSCOPE_DATA ""
  define COLLECT_CSCOPE_DATA ""
}

# Find tclsh for the test suite.
#
# We can't use jimsh for this: the test suite uses features of Tcl that
# Jim doesn't support, either statically or due to the way it's built by
# autosetup.  For example, Jim supports `file normalize`, but only if
# you build it with HAVE_REALPATH, which won't ever be defined in this
# context because autosetup doesn't try to discover platform-specific
# details like that before it decides to build jimsh0.  Besides which,
# autosetup won't build jimsh0 at all if it can find tclsh itself.
# Ironically, this means we may right now be running under either jimsh0
# or a version of tclsh that we find unsuitable below!
cc-check-progs tclsh
set hbtd /usr/local/Cellar/tcl-tk
if {[string equal false [get-define TCLSH]]} {
    msg-result "WARNING: 'make test' will not run here."
  msg-result "WARNING: 'make test' will not run here."
  set v 8.6
} else {
    set v [exec /bin/sh -c "echo 'puts \$tcl_version' | tclsh"]
    if {[expr {$v >= 8.6}]} {
        msg-result "Found Tclsh version $v in the PATH."
        define TCLSH tclsh
    } elseif {[file isdirectory $hbtd]} {
        # This is a macOS system with the Homebrew version of Tcl/Tk
        # installed.  Select the newest version.  It won't normally be
        # in the PATH to avoid shadowing /usr/bin/tclsh, and even if it
        # were in the PATH, it's bad practice to put /usr/local/bin (the
        # Homebrew default) ahead of /usr/bin, especially given that
        # it's user-writeable by default with Homebrew.  Thus, we can be
        # pretty sure the only way to call it is with an absolute path.
        set v [exec ls -tr $hbtd | tail -1]
        set path "$hbtd/$v/bin/tclsh"
        define TCLSH $path
        msg-result "Using Homebrew Tcl/Tk version $path."
    } else {
        msg-result "WARNING: tclsh $v found; need >= 8.6 for 'make test'."
        define TCLSH false     ;# force "make test" failure via /usr/bin/false
    }
  set v [exec sh -c "echo 'puts \$tcl_version' | tclsh"]
  if {$v >= 8.6} {
    msg-result "Found Tclsh version $v in the PATH."
    define TCLSH tclsh
  } elseif {[file isdirectory $hbtd]} {
    # This is a macOS system with the Homebrew version of Tcl/Tk
    # installed.  Select the newest version.  It won't normally be
    # in the PATH to avoid shadowing /usr/bin/tclsh, and even if it
    # were in the PATH, it's bad practice to put /usr/local/bin (the
    # Homebrew default) ahead of /usr/bin, especially given that
    # it's user-writeable by default with Homebrew.  Thus, we can be
    # pretty sure the only way to call it is with an absolute path.
    set v [exec ls -tr $hbtd | tail -1]
    set path "$hbtd/$v/bin/tclsh"
    define TCLSH $path
    msg-result "Using Homebrew Tcl/Tk version $path."
  } else {
    msg-result "WARNING: tclsh $v found; need >= 8.6 for 'make test'."
    define TCLSH false     ;# force "make test" failure via /usr/bin/false
  }
}

define CFLAGS [get-env CFLAGS "-g -O2"]
define CFLAGS [get-env CFLAGS "-g -Os"]
define EXTRA_CFLAGS "-Wall"
define EXTRA_LDFLAGS ""
define USE_SYSTEM_SQLITE 0
define USE_LINENOISE 0
define USE_MMAN_H 0
define USE_SEE 0
define SQLITE3_ORIGIN 0
# SQLITE3_ORIGIN 0 = src/sqlite3, 1=src/sqlite3-see.c, 2=client-provided
define SQLITE_OPTIONS_EXT ""
# SQLITE_OPTIONS_EXT => build-dependent CFLAGS for sqlite3.c and shell.c

# Maintain the C89/C90-style order of variable declarations before statements.
# Check if the compiler supports the respective warning flag.
if {[cctest -cflags -Wdeclaration-after-statement]} {
    define-append EXTRA_CFLAGS -Wdeclaration-after-statement
  define-append EXTRA_CFLAGS -Wdeclaration-after-statement
}


# This procedure is a customized version of "cc-check-function-in-lib",
# that does not modify the LIBS variable.  Its use prevents prematurely
# pulling in libraries that will be added later anyhow (e.g. "-ldl").
proc check-function-in-lib {function libs {otherlibs {}}} {
    if {[string length $otherlibs]} {
        msg-checking "Checking for $function in $libs with $otherlibs..."
    } else {
        msg-checking "Checking for $function in $libs..."
    }
    set found 0
    cc-with [list -libs $otherlibs] {
        if {[cctest_function $function]} {
            msg-result "none needed"
            define lib_$function ""
            incr found
        } else {
            foreach lib $libs {
                cc-with [list -libs -l$lib] {
                    if {[cctest_function $function]} {
                        msg-result -l$lib
                        define lib_$function -l$lib
                        incr found
                        break
                    }
                }
            }
        }
    }
    if {$found} {
        define [feature-define-name $function]
    } else {
        msg-result "no"
    }
    return $found
  if {[string length $otherlibs]} {
    msg-checking "Checking for $function in $libs with $otherlibs..."
  } else {
    msg-checking "Checking for $function in $libs..."
  }
  set found 0
  cc-with [list -libs $otherlibs] {
    if {[cctest_function $function]} {
      msg-result "none needed"
      define lib_$function ""
      incr found
    } else {
      foreach lib $libs {
        cc-with [list -libs -l$lib] {
          if {[cctest_function $function]} {
            msg-result -l$lib
            define lib_$function -l$lib
            incr found
            break
          }
        }
      }
    }
  }
  if {$found} {
    define [feature-define-name $function]
  } else {
    msg-result "no"
  }
  return $found
}

if {![opt-bool internal-sqlite]} {
  proc find_system_sqlite {} {

    # On some systems (slackware), libsqlite3 requires -ldl to link. So
    # search for the system SQLite once with -ldl, and once without. If
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
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


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
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
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
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

843
844
845
846

847
848




































849
850
851
852
853
854
855
856
857
858
859
860
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
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
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

















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
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
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
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







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
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







-
-
-
-
+
+
+
+

-
-
+
+


















+
-
+
+
+



-
-
+
+

-
-
-
-
-
+
+
+
+
+



-
+



-
-
-
+
+
+



-
-
-
-
-
+
+
+
+
+



-
-
-
+
+
+



-
-
-
-
+
+
+
+



-
-
-
-
-
-
-
+
+
+
+
+
+
+



-
-
-
+
+
+



-
-
-
+
+
+



-
-
-
+
+
+








-
-
-
+
+
+

-
-
+
+





+
+
+


-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


+

+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
-
-
+
+


-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+


-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
-
+
+
+


-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
+
+
+
+
+
+

-
+

-
+

-
+

-
+
+

-
+
+
+
+
+
+
+
+
+
+






-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-




-
-
-
-
+
+
+
+

+
+
+














-
+











-
+


-
-
+
+











-
-
+
+

-




-
+

-
-
-
+
+
+









-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+




















-
-
-
-
-
-
-
+
+
+
+
+
+
+


-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+




+


+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+












    lappend cmdline {*}[get-define LDFLAGS]
    lappend cmdline {*}[get-define LIBS]
    set sqlite-version [string cat "-D MINIMUM_SQLITE_VERSION=" [get-define MINIMUM_SQLITE_VERSION]]
    lappend cmdline {*}[set sqlite-version]
    set ok 1
    set err [catch {exec-with-stderr {*}$cmdline} result errinfo]
    if {$err} {
       configlog "Failed: [join $cmdline]"
       if {[string length $result]>0} {configlog $result}
       configlog "============"
       set ok 0
      configlog "Failed: [join $cmdline]"
      if {[string length $result]>0} {configlog $result}
      configlog "============"
      set ok 0
    } elseif {$::autosetup(debug)} {
       configlog "Compiled OK: [join $cmdline]"
       configlog "============"
      configlog "Compiled OK: [join $cmdline]"
      configlog "============"
    }
    if {!$ok} {
      user-error "unable to compile SQLite compatibility test program"
    }
    set err [catch {exec-with-stderr ./conftest__} result errinfo]
    if {[get-define build] eq [get-define host]} {
      set err [catch {exec-with-stderr ./conftest__} result errinfo]
      if {$err} {
        user-error $result
      }
    }
    file delete ./conftest__
  }
  test_system_sqlite

}

proc is_mingw {} {
  return [expr {
    return [string match *mingw* [get-define host]]
    [string match *mingw* [get-define host]] &&
   ![file exists "/dev/null"]
  }]
}

if {[is_mingw]} {
    define-append EXTRA_CFLAGS -DBROKEN_MINGW_CMDLINE
    define-append LIBS -lkernel32 -lws2_32
  define-append EXTRA_CFLAGS -DBROKEN_MINGW_CMDLINE
  define-append LIBS -lkernel32 -lws2_32
} else {
    #
    # NOTE: All platforms except MinGW should use the linenoise
    #       package.  It is currently unsupported on Win32.
    #
    define USE_LINENOISE 1
  #
  # NOTE: All platforms except MinGW should use the linenoise
  #       package.  It is currently unsupported on Win32.
  #
  define USE_LINENOISE 1
}

if {[string match *-solaris* [get-define host]]} {
    define-append EXTRA_CFLAGS {-D__EXTENSIONS__}
  define-append EXTRA_CFLAGS {-D__EXTENSIONS__}
}

if {[opt-bool fossil-debug]} {
    define CFLAGS {-g -O0 -Wall}
    define-append CFLAGS -DFOSSIL_DEBUG
    msg-result "Debugging support enabled"
  define CFLAGS {-g -O0 -Wall}
  define-append CFLAGS -DFOSSIL_DEBUG
  msg-result "Debugging support enabled"
}

if {[opt-bool no-opt]} {
    define CFLAGS {-g -O0 -Wall}
    msg-result "Builting without compiler optimization"
    if {[opt-bool fossil-debug]} {
        define-append CFLAGS -DFOSSIL_DEBUG
    }
  define CFLAGS {-g -O0 -Wall}
  msg-result "Builting without compiler optimization"
  if {[opt-bool fossil-debug]} {
    define-append CFLAGS -DFOSSIL_DEBUG
  }
}

if {[opt-bool with-mman]} {
    define-append EXTRA_CFLAGS -DUSE_MMAN_H
    define USE_MMAN_H 1
    msg-result "Enabling \"sys/mman.h\" support"
  define-append EXTRA_CFLAGS -DUSE_MMAN_H
  define USE_MMAN_H 1
  msg-result "Enabling \"sys/mman.h\" support"
}

if {[opt-bool with-see]} {
    define-append EXTRA_CFLAGS -DUSE_SEE
    define USE_SEE 1
    define SQLITE3_ORIGIN 1
    msg-result "Enabling encryption support"
  define-append EXTRA_CFLAGS -DUSE_SEE
  define USE_SEE 1
  define SQLITE3_ORIGIN 1
  msg-result "Enabling encryption support"
}

if {[opt-bool json]} {
    # Reminder/FIXME (stephan): FOSSIL_ENABLE_JSON
    # is required in the CFLAGS because json*.c
    # have #ifdef guards around the whole file without
    # reading config.h first.
    define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_JSON
    define FOSSIL_ENABLE_JSON
    msg-result "JSON support enabled"
  # Reminder/FIXME (stephan): FOSSIL_ENABLE_JSON
  # is required in the CFLAGS because json*.c
  # have #ifdef guards around the whole file without
  # reading config.h first.
  define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_JSON
  define FOSSIL_ENABLE_JSON
  msg-result "JSON support enabled"
}

if {[opt-bool with-exec-rel-paths]} {
    define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_EXEC_REL_PATHS
    define FOSSIL_ENABLE_EXEC_REL_PATHS
    msg-result "Relative paths in external diff/gdiff enabled"
  define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_EXEC_REL_PATHS
  define FOSSIL_ENABLE_EXEC_REL_PATHS
  msg-result "Relative paths in external diff/gdiff enabled"
}

if {[opt-bool with-th1-docs]} {
    define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_TH1_DOCS
    define FOSSIL_ENABLE_TH1_DOCS
    msg-result "TH1 embedded documentation support enabled"
  define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_TH1_DOCS
  define FOSSIL_ENABLE_TH1_DOCS
  msg-result "TH1 embedded documentation support enabled"
}

if {[opt-bool with-th1-hooks]} {
    define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_TH1_HOOKS
    define FOSSIL_ENABLE_TH1_HOOKS
    msg-result "TH1 hooks support enabled"
  define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_TH1_HOOKS
  define FOSSIL_ENABLE_TH1_HOOKS
  msg-result "TH1 hooks support enabled"
}

#if {[opt-bool markdown]} {
#    # no-op.  Markdown is now enabled by default.
#    msg-result "Markdown support enabled"
#}

if {[opt-bool static]} {
    # XXX: This will not work on all systems.
    define-append EXTRA_LDFLAGS -static
    msg-result "Trying to link statically"
  # XXX: This will not work on all systems.
  define-append EXTRA_LDFLAGS -static
  msg-result "Trying to link statically"
} else {
    define-append EXTRA_CFLAGS -DFOSSIL_DYNAMIC_BUILD=1
    define FOSSIL_DYNAMIC_BUILD
  define-append EXTRA_CFLAGS -DFOSSIL_DYNAMIC_BUILD=1
  define FOSSIL_DYNAMIC_BUILD
}

# Check for libraries that need to be sorted out early
cc-check-function-in-lib iconv iconv

cc-check-function-in-lib sin m
cc-check-function-in-lib dlopen dl

# Helper for OpenSSL checking
proc check-for-openssl {msg {cflags {}} {libs {-lssl -lcrypto -lpthread}}} {
    msg-checking "Checking for $msg..."
    set rc 0
    if {[is_mingw]} {
        lappend libs -lgdi32 -lwsock32 -lcrypt32
    }
    if {[info exists ::zlib_lib]} {
        lappend libs $::zlib_lib
    }
    msg-quiet cc-with [list -cflags $cflags -libs $libs] {
        if {[cc-check-includes openssl/ssl.h] && \
                [cc-check-functions SSL_new]} {
            incr rc
        }
    }
    if {!$rc && ![is_mingw]} {
        # On some systems, OpenSSL appears to require -ldl to link.
        lappend libs -ldl
        msg-quiet cc-with [list -cflags $cflags -libs $libs] {
            if {[cc-check-includes openssl/ssl.h] && \
                    [cc-check-functions SSL_new]} {
                incr rc
            }
        }
    }
    if {$rc} {
        msg-result "ok"
        return 1
    } else {
        msg-result "no"
        return 0
    }
  msg-checking "Checking for $msg..."
  set rc 0
  if {[is_mingw]} {
    lappend libs -lgdi32 -lwsock32 -lcrypt32
  }
  if {[info exists ::zlib_lib]} {
    lappend libs $::zlib_lib
  }
  msg-quiet cc-with [list -cflags $cflags -libs $libs] {
    if {[cc-check-includes openssl/ssl.h] && \
          [cc-check-functions SSL_new]} {
      incr rc
    }
  }
  if {!$rc && ![is_mingw]} {
    # On some systems, OpenSSL appears to require -ldl to link.
    lappend libs -ldl
    msg-quiet cc-with [list -cflags $cflags -libs $libs] {
      if {[cc-check-includes openssl/ssl.h] && \
            [cc-check-functions SSL_new]} {
        incr rc
      }
    }
  }
  if {$rc} {
    msg-result "ok"
    return 1
  } else {
    msg-result "no"
    return 0
  }
}

#
# Check for zlib, using the given location if specified
#
proc handle-zlib {} {
set zlibpath [opt-val with-zlib]
if {$zlibpath eq "tree"} {
  set zlibdir [file dirname $autosetup(dir)]/compat/zlib
  if {![file isdirectory $zlibdir]} {
    user-error "The zlib in source tree directory does not exist"
  } elseif { ([llength [glob -nocomplain -directory $zlibdir libz*]] == 0) } {
    user-error "With --with-zlib=tree, $zlibdir must be configured and built first."
  }
  cc-with [list -cflags "-I$zlibdir -L$zlibdir"]
  define-append EXTRA_CFLAGS -I$zlibdir
  define-append LIBS $zlibdir/libz.a
  set ::zlib_lib $zlibdir/libz.a
  msg-result "Using zlib in source tree"
} else {
  set cftry {""}
  set ldtry {""}
  if {$zlibpath ni {auto ""}} {
    lappend cftry "-I$zlibpath"
    lappend cftry "-I$zlibpath/include"
    lappend ldtry "-L$zlibpath"
    lappend ldtry "-L$zlibpath/lib"
  }
  set ::zlibpath [opt-val with-zlib]; # used by downstream tcl tests
  if {$::zlibpath eq "tree"} {
    set ::zlibdir [file dirname $::autosetup(dir)]/compat/zlib
    if {![file isdirectory $::zlibdir]} {
      user-error "The zlib in source tree directory does not exist"
    } elseif { ([llength [glob -nocomplain -directory $::zlibdir libz*]] == 0) } {
      user-error "With --with-zlib=tree, $::zlibdir must be configured and built first."
    }
    cc-with [list -cflags "-I$::zlibdir -L$::zlibdir"]
    define-append EXTRA_CFLAGS -I$::zlibdir
    define-append LIBS $::zlibdir/libz.a
    set ::zlib_lib $::zlibdir/libz.a
    msg-result "Using zlib in source tree"
  } else {
    set cftry {""}
    set ldtry {""}
    if {$::zlibpath ni {auto ""}} {
      lappend cftry "-I$::zlibpath"
      lappend cftry "-I$::zlibpath/include"
      lappend ldtry "-L$::zlibpath"
      lappend ldtry "-L$::zlibpath/lib"
    }

  # Reverse the list of tests so we check most-specific to least, else
  # platform devel files will shadow local --with-zlib overrides.
  foreach c [lreverse $cftry] {
    if {[cc-with [list -cflags $c] {cc-check-includes zlib.h}]} {
      if {$c eq ""} {
        msg-result "Found zlib.h in default include path"
      } else {
        define-append EXTRA_CFLAGS "$c"
        msg-result "Found zlib.h via $c"
      }
      set cfound $c
      break
    }
  }
  if {![info exists cfound]} {
    user-error "zlib.h not found; either install it or specify its location via --with-zlib"
  }
  foreach lcheck [lreverse $ldtry] {
    if {[cc-with [list -cflags "$cfound $lcheck"] {check-function-in-lib inflateEnd z}]} {
      if {$lcheck eq ""} {
        msg-result "Linked to zlib via default library path"
      } else {
        define-append EXTRA_LDFLAGS "$lcheck"
        msg-result "Linked to zlib via $lcheck"
      }
      if {![check-function-in-lib compressBound z]} {
        puts "Notice: disabling zlib compression in the SQL shell"
        define-append SQLITE_OPTIONS_EXT {-USQLITE_HAVE_ZLIB}
      }
      break
    }
  }
  set ::zlib_lib -lz
}
    # Reverse the list of tests so we check most-specific to least, else
    # platform devel files will shadow local --with-zlib overrides.
    foreach c [lreverse $cftry] {
      if {[cc-with [list -cflags $c] {cc-check-includes zlib.h}]} {
        if {$c eq ""} {
          msg-result "Found zlib.h in default include path"
        } else {
          define-append EXTRA_CFLAGS "$c"
          msg-result "Found zlib.h via $c"
        }
        set cfound $c
        break
      }
    }
    if {![info exists cfound]} {
      user-error "zlib.h not found; either install it or specify its location via --with-zlib"
    }
    foreach lcheck [lreverse $ldtry] {
      if {[cc-with [list -cflags "$cfound $lcheck"] {check-function-in-lib inflateEnd z}]} {
        if {$lcheck eq ""} {
          msg-result "Linked to zlib via default library path"
        } else {
          define-append EXTRA_LDFLAGS "$lcheck"
          msg-result "Linked to zlib via $lcheck"
        }
        if {![check-function-in-lib compressBound z]} {
          puts "Notice: disabling zlib compression in the SQL shell"
          define-append SQLITE_OPTIONS_EXT {-USQLITE_HAVE_ZLIB}
        }
        break
      }
    }
    set ::zlib_lib -lz
  }
}; # handle-zlib
handle-zlib

#
# Handle the --with-openssl flag and, incidentally, update @LIBS@ for
# zlib if openssl is _not_ used (if it is, we get zlib via libssl).
#
# This function should be called as late as possible in the configure
# script to avoid that its updates to @LIBS@ break tests which follow
# it when a custom local build of openssl is used, as discussed in
# <https://fossil-scm.org/forum/forumpost/15e3d9cdc137030c>.
#
proc handle-with-openssl {} {
set ssldirs [opt-val with-openssl]
if {$ssldirs ne "none"} {
  set ssldirs [opt-val with-openssl]
  if {$ssldirs ne "none"} {
    set found 0
    if {$ssldirs eq "tree"} {
        set ssldir [file dirname $autosetup(dir)]/compat/openssl
        if {![file isdirectory $ssldir]} {
            user-error "The OpenSSL in source tree directory does not exist"
        }
        set msg "ssl in $ssldir"
        set cflags "-I$ssldir/include"
        set ldflags "-L$ssldir"
        set ssllibs "$ssldir/libssl.a $ssldir/libcrypto.a -lpthread"
        set found [check-for-openssl "ssl in source tree" "$cflags $ldflags" $ssllibs]
      set ssldir [file dirname $::autosetup(dir)]/compat/openssl
      if {![file isdirectory $ssldir]} {
        user-error "The OpenSSL in source tree directory does not exist"
      }
      set msg "openssl in $ssldir"
      set cflags "-I$ssldir/include"
      set ldflags "-L$ssldir"
      set ssllibs "$ssldir/libssl.a $ssldir/libcrypto.a -lpthread"
      set found [check-for-openssl "openssl in source tree" "$cflags $ldflags" $ssllibs]
    } else {
        if {$ssldirs in {auto ""}} {
            catch {
                set cflags [exec pkg-config openssl --cflags-only-I]
                set ldflags [exec pkg-config openssl --libs-only-L]
                set found [check-for-openssl "ssl via pkg-config" "$cflags $ldflags"]
            } msg
            if {!$found} {
                set ssldirs "{} /usr/sfw /usr/local/ssl /usr/lib/ssl /usr/ssl \
      if {$ssldirs in {auto ""}} {
        catch {
          # TODO?: use autosetup's pkg-config support
          set cflags [exec pkg-config openssl --cflags-only-I]
          set ldflags [exec pkg-config openssl --libs-only-L]
          set found [check-for-openssl "ssl via pkg-config" "$cflags $ldflags"]
        } msg
        if {!$found} {
          set ssldirs "{} /usr/sfw /usr/local/ssl /usr/lib/ssl /usr/ssl \
                             /usr/pkg /usr/local /usr /usr/local/opt/openssl \
                             /opt/homebrew/opt/openssl"
            }
        }
        if {!$found} {
            foreach dir $ssldirs {
                if {$dir eq ""} {
                    set msg "system ssl"
                    set cflags ""
                    set ldflags ""
                } else {
                    set msg "ssl in $dir"
                    set cflags "-I$dir/include"
                    set ldflags "-L$dir/lib"
                }
                if {[check-for-openssl $msg "$cflags $ldflags"]} {
                    incr found
                    break
                }
        }
      }
      if {!$found} {
        foreach dir $ssldirs {
          if {$dir eq ""} {
            set msg "system openssl"
            set cflags ""
            set ldflags ""
          } else {
            set msg "openssl in $dir"
            set cflags "-I$dir/include"
            if {[file readable $dir/libssl.a]} {
              set ldflags -L$dir
            } elseif {[file readable $dir/lib/libssl.a]} {
              set ldflags -L$dir/lib
            } elseif {[file isdir $dir/lib]} {
              set ldflags "-L$dir -L$dir/lib"
            } else {
              set ldflags -L$dir
            }
          }
          if {[check-for-openssl $msg "$cflags $ldflags"]} {
            incr found
            break
          }
          if {$dir ne ""} {
            set ldflags ""
            set msg "static build of openssl in $dir"
            set ssllibs "$dir/libssl.a $dir/libcrypto.a -lpthread"
            if {[check-for-openssl $msg "$cflags $ldflags" $ssllibs]} {
              incr found
              break
            }
            # This test should arguably fail here if --with-openssl=X
            # points to an invalid X.
        }
          }
        }
      }
    }
    if {$found} {
        define FOSSIL_ENABLE_SSL
        define-append EXTRA_CFLAGS $cflags
        define-append EXTRA_LDFLAGS $ldflags
      define FOSSIL_ENABLE_SSL
      define-append EXTRA_CFLAGS $cflags
      define-append EXTRA_LDFLAGS $ldflags
        define-append CFLAGS $cflags
        define-append LDFLAGS $ldflags
        if {[info exists ssllibs]} {
            define-append LIBS $ssllibs
        } else {
            define-append LIBS -lssl -lcrypto
        }
        if {[info exists ::zlib_lib]} {
            define-append LIBS $::zlib_lib
        }
        if {[is_mingw]} {
            define-append LIBS -lgdi32 -lwsock32 -lcrypt32
        }
        msg-result "HTTPS support enabled"
      if {[info exists ssllibs]} {
        define-append LIBS $ssllibs
      } else {
        define-append LIBS -lssl -lcrypto
      }
      if {[info exists ::zlib_lib]} {
        define-append LIBS $::zlib_lib
      }
      if {[is_mingw]} {
        define-append LIBS -lgdi32 -lwsock32 -lcrypt32
      }
      msg-result "HTTPS support enabled"

        # Silence OpenSSL deprecation warnings on Mac OS X 10.7.
        if {[string match *-darwin* [get-define host]]} {
            if {[cctest -cflags {-Wdeprecated-declarations}]} {
                define-append EXTRA_CFLAGS -Wdeprecated-declarations
            }
        }
      # Silence OpenSSL deprecation warnings on Mac OS X 10.7.
      if {[string match *-darwin* [get-define host]]} {
        if {[cctest -cflags {-Wdeprecated-declarations}]} {
          define-append EXTRA_CFLAGS -Wdeprecated-declarations
        }
      }
    } else {
        user-error "OpenSSL not found. Consider --with-openssl=none to disable HTTPS support"
      user-error "OpenSSL not found. Consider --with-openssl=none to disable HTTPS support"
    }
} else {
  } else {
    if {[info exists ::zlib_lib]} {
        define-append LIBS $::zlib_lib
      define-append LIBS $::zlib_lib
    }
}
  }
}; # handle-with-openssl


#
# CFLAGS_INCLUDE is ONLY for -I... flags and their order is
# significant so that --with-sqlite=PATH's header can shadow our
# own. <s>One caveat with this is that we cannot point
# --with-sqlite=PATH to the root of sqlite3's own build tree because
# that dir has a config.h which ends up shadowing src/config.h,
# breaking our build.</s> (That is no longer true: that that config.h
# was renamed to sqlite_cfg.h at some point.)
#
define CFLAGS_INCLUDE {}

########################################################################
# --with-sqlite=PATH checks for the first it finds of the following...
# - PATH/sqlite3.c and PATH/sqlite3.h
# - PATH/sqlite3.o (and assumes sqlite3.h is with it)
# - PATH/lib/libsqlite3* and PATH/include/sqlite3.h
define CFLAGS_INCLUDE {}
# ^^^ CFLAGS_INCLUDE is ONLY for -I... flags and their order is
# significant so that --with-sqlite=PATH's header can shadow our
proc handle-with-sqlite {} {
# own. One caveat with this is that we cannot point --with-sqlite=PATH
# to the root of sqlite3's own build tree because that dir has a
# config.h which ends up shadowing src/config.h, breaking our build.
set sq3path [opt-val with-sqlite]
define SQLITE3_SRC.2 {}
define SQLITE3_OBJ.2 {}
define SQLITE3_SHELL_SRC.2 {$(SQLITE3_SHELL_SRC.0)}
if {$sq3path in {tree ""}} {
  msg-result "Using sqlite3.c from this source tree."
} else {
  # SQLITE3_ORIGIN:
  #   0 = local source tree
  #   1 = use external lib or sqlite3.o
  #   2 = use external sqlite3.c and (if found) shell.c
  define USE_SYSTEM_SQLITE 1
  define SQLITE3_ORIGIN 2
  if {$sq3path != "auto"} {
    if {([file exists $sq3path/sqlite3.c]) && \
        ([file exists $sq3path/sqlite3.h]) } {
      # Prefer sqlite3.[ch] if found.
      define SQLITE3_SRC.2 $sq3path/sqlite3.c
      define SQLITE3_OBJ.2 {$(SQLITE3_OBJ.0)}
      define USE_SYSTEM_SQLITE 2
      define SQLITE3_ORIGIN 2
      if {[file exists $sq3path/shell.c]} {
        define SQLITE3_SHELL_SRC.2 $sq3path/shell.c
      }
      define-append CFLAGS_INCLUDE -I$sq3path
      define-append EXTRA_LDFLAGS -lpthread
      # ^^^ additional -lXXX flags are conservative estimates
      msg-result "Using sqlite3.c and sqlite3.h from $sq3path"
    } elseif {[file exists $sq3path/sqlite3.o]} {
      # Use sqlite3.o if found.
      define SQLITE3_OBJ.2 $sq3path/sqlite3.o
      define-append CFLAGS_INCLUDE -I$sq3path
      define-append EXTRA_LDFLAGS $sq3path/sqlite3.o -lpthread
      # ^^^ additional -lXXX flags are conservative estimates
      msg-result "Using sqlite3.o from $sq3path"
    } elseif { ([llength [glob -nocomplain -directory $sq3path/lib libsqlite3*]] != 0) \
                 && ([file exists $sq3path/include/sqlite3.h]) } {
      # e.g. --with-sqlite=/usr/local. Try $sq3path/lib/libsqlite3*
      # and $sq3path/include/sqlite3.h
      define-append CFLAGS_INCLUDE -I$sq3path/include
      define-append EXTRA_LDFLAGS -L$sq3path/lib -lsqlite3 -lpthread
      # ^^^ additional -lXXX flags are conservative estimates
      msg-result "Using -lsqlite3 from $sq3path"
    } else {
      # Assume $sq3path holds both the lib and header
      cc-with [list -cflags "-I$sq3path -L$sq3path"]
      define-append CFLAGS_INCLUDE -I$sq3path
      define-append EXTRA_LDFLAGS -L$sq3path -lsqlite3 -lpthread
      # ^^^ additional -lXXX flags are conservative estimates
      msg-result "Using -lsqlite3 from $sq3path"
    }
  } elseif {![cc-check-includes sqlite3.h] || ![check-function-in-lib sqlite3_open_v2 sqlite3]} {
    user-error "libsqlite3 not found please install it or specify the location with --with-sqlite"
  }
}
define-append CFLAGS_INCLUDE {-I. -I$(SRCDIR) -I$(SRCDIR_extsrc)}
  set sq3path [opt-val with-sqlite]
  define SQLITE3_SRC.2 {}
  define SQLITE3_OBJ.2 {}
  define SQLITE3_SHELL_SRC.2 {$(SQLITE3_SHELL_SRC.0)}
  if {$sq3path in {tree ""}} {
    msg-result "Using sqlite3.c from this source tree."
  } else {
    # SQLITE3_ORIGIN:
    #   0 = local source tree
    #   1 = use external lib or sqlite3.o
    #   2 = use external sqlite3.c and (if found) shell.c
    define USE_SYSTEM_SQLITE 1
    define SQLITE3_ORIGIN 2
    if {$sq3path != "auto"} {
      if {([file exists $sq3path/sqlite3.c]) &&
          ([file exists $sq3path/sqlite3.h]) } {
        # Prefer sqlite3.[ch] if found.
        define SQLITE3_SRC.2 $sq3path/sqlite3.c
        define SQLITE3_OBJ.2 {$(SQLITE3_OBJ.0)}
        define USE_SYSTEM_SQLITE 2
        define SQLITE3_ORIGIN 2
        if {[file exists $sq3path/shell.c]} {
          define SQLITE3_SHELL_SRC.2 $sq3path/shell.c
        }
        define-append CFLAGS_INCLUDE -I$sq3path
        define-append EXTRA_LDFLAGS -lpthread
        # ^^^ additional -lXXX flags are conservative estimates
        msg-result "Using sqlite3.c and sqlite3.h from $sq3path"
      } elseif {[file exists $sq3path/sqlite3.o]} {
        # Use sqlite3.o if found.
        define SQLITE3_OBJ.2 $sq3path/sqlite3.o
        define-append CFLAGS_INCLUDE -I$sq3path
        define-append EXTRA_LDFLAGS $sq3path/sqlite3.o -lpthread
        # ^^^ additional -lXXX flags are conservative estimates
        msg-result "Using sqlite3.o from $sq3path"
      } elseif { ([llength [glob -nocomplain -directory $sq3path/lib libsqlite3*]] != 0) \
                   && ([file exists $sq3path/include/sqlite3.h]) } {
        # e.g. --with-sqlite=/usr/local. Try $sq3path/lib/libsqlite3*
        # and $sq3path/include/sqlite3.h
        define-append CFLAGS_INCLUDE -I$sq3path/include
        define-append EXTRA_LDFLAGS -L$sq3path/lib -lsqlite3 -lpthread
        # ^^^ additional -lXXX flags are conservative estimates
        msg-result "Using -lsqlite3 from $sq3path"
      } else {
        # Assume $sq3path holds both the lib and header
        cc-with [list -cflags "-I$sq3path -L$sq3path"]
        define-append CFLAGS_INCLUDE -I$sq3path
        define-append EXTRA_LDFLAGS -L$sq3path -lsqlite3 -lpthread
        # ^^^ additional -lXXX flags are conservative estimates
        msg-result "Using -lsqlite3 from $sq3path"
      }
    } elseif {![cc-check-includes sqlite3.h] || ![check-function-in-lib sqlite3_open_v2 sqlite3]} {
      user-error "libsqlite3 not found please install it or specify the location with --with-sqlite"
    }
  }
}; # handle-with-sqlite
handle-with-sqlite
define-append CFLAGS_INCLUDE {-I. -I$(SRCDIR) -I$(SRCDIR_extsrc)}; # must be after handle-with-sqlite

#
# Handle the --with-tcl flag.
#
proc handle-with-tcl {} {
  global v
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"} {
        set tcldir [file dirname $autosetup(dir)]/compat/tcl-8.6
        if {$tclprivatestubs} {
            set tclconfig(TCL_INCLUDE_SPEC) -I$tcldir/generic
            set tclconfig(TCL_VERSION) {Private Stubs}
            set tclconfig(TCL_PATCH_LEVEL) {}
            set tclconfig(TCL_PREFIX) $tcldir
            set tclconfig(TCL_LD_FLAGS) { }
        } else {
            # Use the system Tcl. Look in some likely places.
            array set tclconfig [parse-tclconfig-sh \
                $tcldir/unix $tcldir/win \
                /usr /usr/local /usr/share /opt/local]
            set msg "on your system"
        }
    } else {
        array set tclconfig [parse-tclconfig-sh $tclpath]
        set msg "at $tclpath"
    }
    if {[opt-bool static]} {
        set tclconfig(TCL_LD_FLAGS) { }
    }
    if {![info exists tclconfig(TCL_INCLUDE_SPEC)]} {
        user-error "Cannot find Tcl $msg"
    }
    set tclstubs [opt-bool with-tcl-stubs]
    if {$tclprivatestubs} {
        define FOSSIL_ENABLE_TCL_PRIVATE_STUBS
        define USE_TCL_STUBS
    } elseif {$tclstubs && $tclconfig(TCL_SUPPORTS_STUBS)} {
        set libs "$tclconfig(TCL_STUB_LIB_SPEC)"
        define FOSSIL_ENABLE_TCL_STUBS
        define USE_TCL_STUBS
    } else {
        set libs "$tclconfig(TCL_LIB_SPEC) $tclconfig(TCL_LIBS)"
    }
    set cflags $tclconfig(TCL_INCLUDE_SPEC)
    if {!$tclprivatestubs} {
        set foundtcl 0; # Did we find a working Tcl library?
        cc-with [list -cflags $cflags -libs $libs] {
            if {$tclstubs} {
                if {[cc-check-functions Tcl_InitStubs]} {
                    set foundtcl 1
                }
            } else {
                if {[cc-check-functions Tcl_CreateInterp]} {
                    set foundtcl 1
                }
            }
        }
        if {!$foundtcl && [string match *-lieee* $libs]} {
            # On some systems, using "-lieee" from TCL_LIB_SPEC appears
            # to cause issues.
            msg-result "Removing \"-lieee\" and retrying for Tcl..."
            set libs [string map [list -lieee ""] $libs]
            cc-with [list -cflags $cflags -libs $libs] {
                if {$tclstubs} {
                    if {[cc-check-functions Tcl_InitStubs]} {
                        set foundtcl 1
                    }
                } else {
                    if {[cc-check-functions Tcl_CreateInterp]} {
                        set foundtcl 1
                    }
                }
            }
        }
        if {!$foundtcl && ![string match *-lpthread* $libs]} {
            # On some systems, TCL_LIB_SPEC appears to be missing
            # "-lpthread".  Try adding it.
            msg-result "Adding \"-lpthread\" and retrying for Tcl..."
            set libs "$libs -lpthread"
            cc-with [list -cflags $cflags -libs $libs] {
                if {$tclstubs} {
                    if {[cc-check-functions Tcl_InitStubs]} {
                        set foundtcl 1
                    }
                } else {
                    if {[cc-check-functions Tcl_CreateInterp]} {
                        set foundtcl 1
                    }
                }
            }
        }
        if {!$foundtcl} {
            if {$tclstubs} {
                user-error "Cannot find a usable Tcl stubs library $msg"
            } else {
                user-error "Cannot find a usable Tcl library $msg"
            }
        }
    }
    set version $tclconfig(TCL_VERSION)$tclconfig(TCL_PATCH_LEVEL)
    msg-result "Found Tcl $version at $tclconfig(TCL_PREFIX)"
    if {!$tclprivatestubs} {
        define-append LIBS $libs
    }
    define-append EXTRA_CFLAGS $cflags
    define-append CFLAGS $cflags
    if {[info exists zlibpath] && $zlibpath eq "tree"} {
      #
      # NOTE: When using zlib in the source tree, prevent Tcl from
      #       pulling in the system one.
      #
      set tclconfig(TCL_LD_FLAGS) [string map [list -lz ""] \
          $tclconfig(TCL_LD_FLAGS)]
    }
    #
    # NOTE: Remove "-ldl" from the TCL_LD_FLAGS because it will be
    #       be checked for near the bottom of this file.
    #
    set tclconfig(TCL_LD_FLAGS) [string map [list -ldl ""] \
        $tclconfig(TCL_LD_FLAGS)]
    define-append EXTRA_LDFLAGS $tclconfig(TCL_LD_FLAGS)
    define FOSSIL_ENABLE_TCL
  set tclpath [opt-val with-tcl]
  if {$tclpath eq ""} {
    return
  }
  set tclprivatestubs [opt-bool with-tcl-private-stubs]
  # Note parse-tclconfig-sh is in autosetup/local.tcl
  if {$tclpath eq "1"} {
    if {$v >= 9.0} {
      set tcldir [file dirname $::autosetup(dir)]/compat/tcl-9.0
    } else {
      set tcldir [file dirname $::autosetup(dir)]/compat/tcl-8.6
    }
    if {$tclprivatestubs} {
      set tclconfig(TCL_INCLUDE_SPEC) -I$tcldir/generic
      set tclconfig(TCL_VERSION) {Private Stubs}
      set tclconfig(TCL_PATCH_LEVEL) {}
      set tclconfig(TCL_PREFIX) $tcldir
      set tclconfig(TCL_LD_FLAGS) { }
    } else {
      # Use the system Tcl. Look in some likely places.
      array set tclconfig [parse-tclconfig-sh \
                             $tcldir/unix $tcldir/win \
                             /usr /usr/local /usr/share /opt/local]
      set msg "on your system"
    }
  } else {
    array set tclconfig [parse-tclconfig-sh $tclpath]
    set msg "at $tclpath"
  }
  if {[opt-bool static]} {
    set tclconfig(TCL_LD_FLAGS) { }
  }
  if {![info exists tclconfig(TCL_INCLUDE_SPEC)]} {
    user-error "Cannot find Tcl $msg"
  }
  set tclstubs [opt-bool with-tcl-stubs]
  if {$tclprivatestubs} {
    define FOSSIL_ENABLE_TCL_PRIVATE_STUBS
    define USE_TCL_STUBS
  } elseif {$tclstubs && $tclconfig(TCL_SUPPORTS_STUBS)} {
    set libs "$tclconfig(TCL_STUB_LIB_SPEC)"
    define FOSSIL_ENABLE_TCL_STUBS
    define USE_TCL_STUBS
  } else {
    set libs "$tclconfig(TCL_LIB_SPEC) $tclconfig(TCL_LIBS)"
  }
  set cflags $tclconfig(TCL_INCLUDE_SPEC)
  if {!$tclprivatestubs} {
    set foundtcl 0; # Did we find a working Tcl library?
    cc-with [list -cflags $cflags -libs $libs] {
      if {$tclstubs} {
        if {[cc-check-functions Tcl_InitStubs]} {
          set foundtcl 1
        }
      } else {
        if {[cc-check-functions Tcl_CreateInterp]} {
          set foundtcl 1
        }
      }
    }
    if {!$foundtcl && [string match *-lieee* $libs]} {
      # On some systems, using "-lieee" from TCL_LIB_SPEC appears
      # to cause issues.
      msg-result "Removing \"-lieee\" and retrying for Tcl..."
      set libs [string map [list -lieee ""] $libs]
      cc-with [list -cflags $cflags -libs $libs] {
        if {$tclstubs} {
          if {[cc-check-functions Tcl_InitStubs]} {
            set foundtcl 1
          }
        } else {
          if {[cc-check-functions Tcl_CreateInterp]} {
            set foundtcl 1
          }
        }
      }
    }
    if {!$foundtcl && ![string match *-lpthread* $libs]} {
      # On some systems, TCL_LIB_SPEC appears to be missing
      # "-lpthread".  Try adding it.
      msg-result "Adding \"-lpthread\" and retrying for Tcl..."
      set libs "$libs -lpthread"
      cc-with [list -cflags $cflags -libs $libs] {
        if {$tclstubs} {
          if {[cc-check-functions Tcl_InitStubs]} {
            set foundtcl 1
          }
        } else {
          if {[cc-check-functions Tcl_CreateInterp]} {
            set foundtcl 1
          }
        }
      }
    }
    if {!$foundtcl} {
      if {$tclstubs} {
        user-error "Cannot find a usable Tcl stubs library $msg"
      } else {
        user-error "Cannot find a usable Tcl library $msg"
      }
    }
  }
  set version $tclconfig(TCL_VERSION)$tclconfig(TCL_PATCH_LEVEL)
  msg-result "Found Tcl $version at $tclconfig(TCL_PREFIX)"
  if {!$tclprivatestubs} {
    define-append LIBS $libs
  }
  define-append EXTRA_CFLAGS $cflags
  define-append CFLAGS $cflags
  if {[info exists ::zlibpath] && $::zlibpath eq "tree"} {
    #
    # NOTE: When using zlib in the source tree, prevent Tcl from
    #       pulling in the system one.
    #
    set tclconfig(TCL_LD_FLAGS) [string map [list -lz ""] \
                                   $tclconfig(TCL_LD_FLAGS)]
  }
  #
  # NOTE: Remove "-ldl" from the TCL_LD_FLAGS because it will be
  #       be checked for near the bottom of this file.
  #
  set tclconfig(TCL_LD_FLAGS) [string map [list -ldl ""] \
                                 $tclconfig(TCL_LD_FLAGS)]
  define-append EXTRA_LDFLAGS $tclconfig(TCL_LD_FLAGS)
  define FOSSIL_ENABLE_TCL
}

}; # handle-with-tcl
# Emscripten is a purely optional component used only for doing
# in-tree builds of WASM stuff, as opposed to WASM binaries we import
# from other places. This is only set up for Unix-style OSes and is
# untested anywhere but Linux.
set emsdkHome [opt-val with-emsdk]
handle-with-tcl
define EMSDK_HOME ""
define EMSDK_ENV ""
define EMCC_OPT "-Oz"
if {$emsdkHome eq "" && [info exists ::env(EMSDK)]} {
  # Fall back to checking the environment. $EMSDK gets set
  # by sourcing emsdk_env.sh.
  set emsdkHome $::env(EMSDK)
}
if {$emsdkHome ne ""} {
  define EMSDK_HOME $emsdkHome
  set emsdkEnv "$emsdkHome/emsdk_env.sh"
  if {[file exists $emsdkEnv]} {
    puts "Using Emscripten SDK environment from $emsdkEnv."
    define EMSDK_ENV $emsdkEnv
    if {[info exists ::env(EMCC_OPT)]} {
      define EMCC_OPT $::env(EMCC_OPT)
    }
  } else {
    puts "emsdk_env.sh not found. Assuming emcc is in the PATH."
  }
}

# Network functions require libraries on some systems
cc-check-function-in-lib gethostbyname nsl
if {![cc-check-function-in-lib socket {socket network}]} {
    # Last resort, may be Windows
    if {[is_mingw]} {
        define-append LIBS -lwsock32
    }
  # Last resort, may be Windows
  if {[is_mingw]} {
    define-append LIBS -lwsock32
  }
}

# Some systems (ex: SunOS) require -lrt in order to use nanosleep
cc-check-function-in-lib nanosleep rt

# The SMTP module requires special libraries and headers for MX DNS
# record lookups and such.
cc-check-includes arpa/nameser.h
cc-include-needs bind/resolv.h netinet/in.h
cc-check-includes bind/resolv.h
cc-check-includes resolv.h
if {    !(([cc-check-function-in-lib dn_expand resolv] ||
           [cc-check-function-in-lib   ns_name_uncompress {bind resolv}] ||
           [cc-check-function-in-lib __ns_name_uncompress {bind resolv}]) &&
          ([cc-check-function-in-lib   ns_parserr {bind resolv}] ||
           [cc-check-function-in-lib __ns_parserr {bind resolv}]) &&
          ([cc-check-function-in-lib   res_query {bind resolv}] ||
           [cc-check-function-in-lib __res_query {bind resolv}]))} {
    msg-result "WARNING: SMTP feature will not be able to look up local MX."
  msg-result "WARNING: SMTP feature will not be able to look up local MX."
}
cc-check-function-in-lib res_9_ns_initparse resolv

# Other nonstandard function checks
cc-check-functions utime
cc-check-functions usleep
cc-check-functions strchrnul
cc-check-functions pledge
cc-check-functions backtrace

# Termux on Android adds "getpass(char *)" to unistd.h, so check this so we
# guard against including it again; use cctest as cc-check-functions and 
# guard against including it again; use cctest as cc-check-functions and
# cctest_function check for "getpass()" with no args and fail
if {[cctest -link 1 -includes {unistd.h} -code "getpass(0);"]} {
    define FOSSIL_HAVE_GETPASS 1
    msg-result "Found getpass() with unistd.h"
  define FOSSIL_HAVE_GETPASS 1
  msg-result "Found getpass() with unistd.h"
}

# Check for getloadavg(), and if it doesn't exist, define FOSSIL_OMIT_LOAD_AVERAGE
if {![cc-check-functions getloadavg] ||
    ![cctest -link 1 -includes {unistd.h} -code "double a\[3\]; getloadavg(a,3);"]} {
  define FOSSIL_OMIT_LOAD_AVERAGE 1
  msg-result "Load average support unavailable"
}

# 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
  # Haiku needs this
  cc-check-function-in-lib getpass bsd
}
cc-check-function-in-lib sin m

# Check for the FuseFS library
if {[opt-bool fusefs]} {
  if {[opt-bool static]} {
     msg-result "FuseFS support disabled due to -static"
    msg-result "FuseFS support disabled due to -static"
  } elseif {[cc-check-function-in-lib fuse_mount fuse]} {
     define-append EXTRA_CFLAGS -DFOSSIL_HAVE_FUSEFS
     define FOSSIL_HAVE_FUSEFS 1
     msg-result "FuseFS support enabled"
    define-append EXTRA_CFLAGS -DFOSSIL_HAVE_FUSEFS
    define FOSSIL_HAVE_FUSEFS 1
    msg-result "FuseFS support enabled"
  }
}

########################################################################
# Checks the compiler for compile_commands.json support.
#
# Returns 1 if supported, else 0. Defines MAKE_COMPILATION_DB to "yes"
# if supported, "no" if not.
proc check-compile-commands {} {
    msg-checking "compile_commands.json support... "
    if {[cctest -lang c -cflags {/dev/null -MJ} -source {}]} {
        # This test reportedly incorrectly succeeds on one of
        # Martin G.'s older systems.
        msg-result "compiler supports compile_commands.json"
        define MAKE_COMPILATION_DB yes
        return 1
    } else {
        msg-result "compiler does not support compile_commands.json"
        define MAKE_COMPILATION_DB no
        return 0
    }
  msg-checking "compile_commands.json support... "
  if {[cctest -lang c -cflags {/dev/null -MJ} -source {}]} {
    # This test reportedly incorrectly succeeds on one of
    # Martin G.'s older systems.
    msg-result "compiler supports compile_commands.json"
    define MAKE_COMPILATION_DB yes
    return 1
  } else {
    msg-result "compiler does not support compile_commands.json"
    define MAKE_COMPILATION_DB no
    return 0
  }
}

define MAKE_COMPILATION_DB no
if {!$outOfTreeBuild} {
  if {[opt-bool compile-commands]} {
    check-compile-commands
  } else {
    puts "Use --compile-commands to enable check for compile-commands-capable compiler."
  }
} else {
  puts "Disabling compile_commands.json check for out-of-tree build."
  # This is an attempt to resolve the problem reported at
  # https://fossil-scm.org/forum/forumpost/d19061d09a8179d0
}

# Add -fsanitize compile and link options late: we don't want the C
# checks above to run with those sanitizers enabled.  It can not only
# be pointless, it can actually break correct tests.
set fsan [opt-val with-sanitizer]
if {[string length $fsan]} {
    define-append  EXTRA_CFLAGS -fsanitize=$fsan
    define-append EXTRA_LDFLAGS -fsanitize=$fsan
    if {[string first "undefined" $fsan] != -1} {
        # We need to link with libubsan if we're compiling under
        # GCC with -fsanitize=undefined.
        cc-check-function-in-lib __ubsan_handle_add_overflow ubsan
    }
  define-append  EXTRA_CFLAGS -fsanitize=$fsan
  define-append EXTRA_LDFLAGS -fsanitize=$fsan
  if {[string first "undefined" $fsan] != -1} {
    # We need to link with libubsan if we're compiling under
    # GCC with -fsanitize=undefined.
    cc-check-function-in-lib __ubsan_handle_add_overflow ubsan
  }
}

# Finally, append libraries that must be last. This matters more on some
########################################################################
# OSes than others, but is most broadly required for static linking.
if {[check-function-in-lib dlopen dl]} {
    # Some platforms (*BSD) have the dl functions already in libc and no libdl.
    # In such case we can link directly without -ldl.
    define-append LIBS [get-define lib_dlopen]
}
if {[opt-bool static]} {
# @proj-check-emsdk
#
# Emscripten is used for doing in-tree builds of web-based WASM stuff,
# as opposed to WASI-based WASM or WASM binaries we import from other
# places. This is only set up for Unix-style OSes and is untested
# anywhere but Linux. Requires that the --with-emsdk flag be
# registered with autosetup.
#
# It looks for the SDK in the location specified by --with-emsdk.
# Values of "" or "auto" mean to check for the environment var EMSDK
# (which gets set by the emsdk_env.sh script from the SDK) or that
# same var passed to configure.
#
# If the given directory is found, it expects to find emsdk_env.sh in
# that directory, as well as the emcc compiler somewhere under there.
#
# If the --with-emsdk flag is explicitly provided and the SDK is not
# found then a fatal error is generated, otherwise failure to find the
# SDK is not fatal.
#
# Defines the following:
#
# - EMSDK_HOME = top dir of the emsdk or "".
# - EMSDK_ENV_SH = path to EMSDK_HOME/emsdk_env.sh or ""
# - BIN_EMCC = $EMSDK_HOME/upstream/emscripten/emcc or ""
# - HAVE_EMSDK = 0 or 1 (this function's return value)
#
# Returns 1 if EMSDK_ENV_SH is found, else 0.  If EMSDK_HOME is not empty
# but BIN_EMCC is then emcc was not found in the EMSDK_HOME, in which
# case we have to rely on the fact that sourcing $EMSDK_ENV_SH from a
# shell will add emcc to the $PATH.
proc proj-check-emsdk {} {
  set emsdkHome [opt-val with-emsdk]
  define EMSDK_HOME ""
  define EMSDK_ENV_SH ""
  define BIN_EMCC ""
  set hadValue [llength $emsdkHome]
  msg-checking "Emscripten SDK? "
  if {$emsdkHome in {"" "auto"}} {
    # Check the environment. $EMSDK gets set by sourcing emsdk_env.sh.
    set emsdkHome [get-env EMSDK ""]
  }
  set rc 0
  if {$emsdkHome ne ""} {
    define EMSDK_HOME $emsdkHome
    set emsdkEnv "$emsdkHome/emsdk_env.sh"
    if {[file exists $emsdkEnv]} {
    # Linux can only infer the dependency on pthread from OpenSSL when
    # doing dynamic linkage.
    define-append LIBS -lpthread
}

if {[get-define EMSDK_HOME] ne ""} {
      msg-result "$emsdkHome"
      define EMSDK_ENV_SH $emsdkEnv
      set rc 1
      set emcc "$emsdkHome/upstream/emscripten/emcc"
      if {[file exists $emcc]} {
        define BIN_EMCC $emcc
      }
    } else {
      msg-result "emsdk_env.sh not found in $emsdkHome"
    }
  } else {
    msg-result "not found"
  }
  if {$hadValue && 0 == $rc} {
    # Fail if it was explicitly requested but not found
    proj-fatal "Cannot find the Emscripten SDK"
  }
  define HAVE_EMSDK $rc
  return $rc
}

if {[proj-check-emsdk]} {
  define EMCC_WRAPPER $::autosetup(dir)/../tools/emcc.sh
  define EMCC_OPT [get-env EMCC_OPT "-Oz"]; # optional flags to pass to emcc
  make-template tools/emcc.sh.in
  catch {exec chmod u+x tools/emcc.sh}
} else {
  define EMCC_WRAPPER ""
  define EMCC_OPT ""
  catch {exec rm -f tools/emcc.sh}
}

handle-with-openssl

# Finally, append libraries that must be last. This matters more on some
# OSes than others, but is most broadly required for static linking.
if {[opt-bool static]} {
  # Linux can only infer the dependency on pthread from OpenSSL when
  # doing dynamic linkage.
  define-append LIBS -lpthread
}

apply {{} {
  # This started out as a workaround for getting the ordering of -ldl
  # correct in conjunction with openssl in some environments. Then it
  # evolved into a more generic preemptive portability workaround to
  # ensure that certain libraries are always appended to the global
  # LIBS list if they exist on the system. Based on a /chat discussion
  # on 2025-02-27 in the context of check-in [8d3b9bf4d4].
  #
  # Note that [move-lib-to-end] and [lib-actually-exists] are in
  # autosetup/local.tcl.
  set libs [get-define LIBS]
  #puts "**** 1 LIBS: $libs"
  foreach ll {-ldl -lpthread -lm} {
    if {![move-lib-to-end $ll $libs libs]} {
      # $ll was not in the list
      if {[lib-actually-exists $ll]} {
        # Add it to the list "just in case." This will be a no-op on
        # systems where the lib is not actually used.
        lappend libs $ll
      }
    }
  }
  #puts "**** 2 LIBS: $libs"
  define LIBS [join $libs " "]
}}

# Tag container builds with a prefix of the checkin ID of the version
# of Fossil each one contains.  This not only allows multiple images
# to coexist and multiple containers to be created unamgiguosly from
# them, it also changes the URL we fetch the source tarball from, so
# repeated builds of a given version generate and fetch the source
# tarball once only, keeping it in the local Docker/Podman cache.
set ci [readfile "$::autosetup(srcdir)/manifest.uuid"]
define FOSSIL_CI_PFX [string range $ci 0 11]

make-template Makefile.in
make-config-header autoconfig.h -auto {USE_* FOSSIL_*}
Changes to autosetup/local.tcl.
25
26
27
28
29
30
31






























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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
		if {[regexp {^(TCL_[^=]*)=(.*)$} $line -> name value]} {
			set value [regsub -all {\$\{.*\}} $value ""]
			set tclconfig($name) [string trim $value ']
		}
	}
	return [array get tclconfig]
}

#
# Given a library link flag, e.g. -lfoo, returns 1 if that library can
# actually be linked to, else returns 0.
proc lib-actually-exists {linkFlag} {
  cctest -link 1 -code "void libActuallyExists(void){}" -libs $linkFlag
}

#
# Given a library flag, e.g. -lfoo, a list of libs, e.g. {-lfoo -lbar
# -lbaz}, and a target variable name, this function appends all
# entries of $libList which do not match $flag to $tgtVar, then
# appends $flag to the end of $tgtVar. Returns the number of matches
# found.
proc move-lib-to-end {flag libList tgtVar} {
  upvar $tgtVar tgt
  set tgt {}
  set found 0
  foreach e $libList {
    if {$flag eq $e} {
      incr found
    } else {
      lappend tgt $e
    }
  }
  if {$found} {
    lappend tgt $flag
  }
  return $found
}
Added compat/tcl-9.0/generic/tcl.h.





































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































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
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
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
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
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
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
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
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
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
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
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
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
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
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
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
1210
1211
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
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
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
1320
1321
1322
1323
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
1359
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
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
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
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
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
1517
1518
1519
1520
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
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
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
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
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
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
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
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
1862
1863
1864
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
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
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
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
1994
1995
1996
1997
1998
1999
2000
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
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
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
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
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
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
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
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
2444
2445
2446
2447
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
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
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
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
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
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/*
 * tcl.h --
 *
 *	This header file describes the externally-visible facilities of the
 *	Tcl interpreter.
 *
 * Copyright (c) 1987-1994 The Regents of the University of California.
 * Copyright (c) 1993-1996 Lucent Technologies.
 * Copyright (c) 1994-1998 Sun Microsystems, Inc.
 * Copyright (c) 1998-2000 by Scriptics Corporation.
 * Copyright (c) 2002 by Kevin B. Kenny.  All rights reserved.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#ifndef _TCL
#define _TCL

/*
 * For C++ compilers, use extern "C"
 */

#ifdef __cplusplus
extern "C" {
#endif

/*
 * The following defines are used to indicate the various release levels.
 */

#define TCL_ALPHA_RELEASE	0
#define TCL_BETA_RELEASE	1
#define TCL_FINAL_RELEASE	2

/*
 * When version numbers change here, must also go into the following files and
 * update the version numbers:
 *
 * library/init.tcl	(1 LOC patch)
 * unix/configure.ac	(2 LOC Major, 2 LOC minor, 1 LOC patch)
 * win/configure.ac	(as above)
 * win/tcl.m4		(not patchlevel)
 * README.md		(sections 0 and 2, with and without separator)
 * win/README		(not patchlevel) (sections 0 and 2)
 * unix/tcl.spec	(1 LOC patch)
 */

#if !defined(TCL_MAJOR_VERSION)
#   define TCL_MAJOR_VERSION	9
#endif
#if TCL_MAJOR_VERSION == 9
#   define TCL_MINOR_VERSION	0
#   define TCL_RELEASE_LEVEL	TCL_FINAL_RELEASE
#   define TCL_RELEASE_SERIAL	0

#   define TCL_VERSION		"9.0"
#   define TCL_PATCH_LEVEL	"9.0.0"
#endif /* TCL_MAJOR_VERSION */

#if defined(RC_INVOKED)
/*
 * Utility macros: STRINGIFY takes an argument and wraps it in "" (double
 * quotation marks), JOIN joins two arguments.
 */

#ifndef STRINGIFY
#  define STRINGIFY(x) STRINGIFY1(x)
#  define STRINGIFY1(x) #x
#endif
#ifndef JOIN
#  define JOIN(a,b) JOIN1(a,b)
#  define JOIN1(a,b) a##b
#endif
#endif /* RC_INVOKED */

/*
 * A special definition used to allow this header file to be included from
 * windows resource files so that they can obtain version information.
 * RC_INVOKED is defined by default by the windows RC tool.
 *
 * Resource compilers don't like all the C stuff, like typedefs and function
 * declarations, that occur below, so block them out.
 */

#ifndef RC_INVOKED

/*
 * Special macro to define mutexes.
 */

#define TCL_DECLARE_MUTEX(name) \
    static Tcl_Mutex name;

/*
 * Tcl's public routine Tcl_FSSeek() uses the values SEEK_SET, SEEK_CUR, and
 * SEEK_END, all #define'd by stdio.h .
 *
 * Also, many extensions need stdio.h, and they've grown accustomed to tcl.h
 * providing it for them rather than #include-ing it themselves as they
 * should, so also for their sake, we keep the #include to be consistent with
 * prior Tcl releases.
 */

#include <stdio.h>
#include <stddef.h>

#if defined(__GNUC__) && (__GNUC__ > 2)
#   if defined(_WIN32) && defined(__USE_MINGW_ANSI_STDIO) && __USE_MINGW_ANSI_STDIO
#	define TCL_FORMAT_PRINTF(a,b) __attribute__ ((__format__ (__MINGW_PRINTF_FORMAT, a, b)))
#   else
#	define TCL_FORMAT_PRINTF(a,b) __attribute__ ((__format__ (__printf__, a, b)))
#   endif
#   define TCL_NORETURN __attribute__ ((__noreturn__))
#   define TCL_NOINLINE __attribute__ ((__noinline__))
#   define TCL_NORETURN1 __attribute__ ((__noreturn__))
#else
#   define TCL_FORMAT_PRINTF(a,b)
#   if defined(_MSC_VER)
#	define TCL_NORETURN __declspec(noreturn)
#	define TCL_NOINLINE __declspec(noinline)
#   else
#	define TCL_NORETURN /* nothing */
#	define TCL_NOINLINE /* nothing */
#   endif
#   define TCL_NORETURN1 /* nothing */
#endif

/*
 * Allow a part of Tcl's API to be explicitly marked as deprecated.
 *
 * Used to make TIP 330/336 generate moans even if people use the
 * compatibility macros. Change your code, guys! We won't support you forever.
 */

#if defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)))
#   if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 5))
#	define TCL_DEPRECATED_API(msg)	__attribute__ ((__deprecated__ (msg)))
#   else
#	define TCL_DEPRECATED_API(msg)	__attribute__ ((__deprecated__))
#   endif
#else
#   define TCL_DEPRECATED_API(msg)	/* nothing portable */
#endif

/*
 *----------------------------------------------------------------------------
 * Macros used to declare a function to be exported by a DLL. Used by Windows,
 * maps to no-op declarations on non-Windows systems. The default build on
 * windows is for a DLL, which causes the DLLIMPORT and DLLEXPORT macros to be
 * nonempty. To build a static library, the macro STATIC_BUILD should be
 * defined.
 *
 * Note: when building static but linking dynamically to MSVCRT we must still
 *       correctly decorate the C library imported function.  Use CRTIMPORT
 *       for this purpose.  _DLL is defined by the compiler when linking to
 *       MSVCRT.
 */

#ifdef _WIN32
#   ifdef STATIC_BUILD
#       define DLLIMPORT
#       define DLLEXPORT
#       ifdef _DLL
#           define CRTIMPORT __declspec(dllimport)
#       else
#           define CRTIMPORT
#       endif
#   else
#       define DLLIMPORT __declspec(dllimport)
#       define DLLEXPORT __declspec(dllexport)
#       define CRTIMPORT __declspec(dllimport)
#   endif
#else
#   define DLLIMPORT
#   if defined(__GNUC__) && __GNUC__ > 3
#       define DLLEXPORT __attribute__ ((visibility("default")))
#   else
#       define DLLEXPORT
#   endif
#   define CRTIMPORT
#endif

/*
 * These macros are used to control whether functions are being declared for
 * import or export. If a function is being declared while it is being built
 * to be included in a shared library, then it should have the DLLEXPORT
 * storage class. If is being declared for use by a module that is going to
 * link against the shared library, then it should have the DLLIMPORT storage
 * class. If the symbol is being declared for a static build or for use from a
 * stub library, then the storage class should be empty.
 *
 * The convention is that a macro called BUILD_xxxx, where xxxx is the name of
 * a library we are building, is set on the compile line for sources that are
 * to be placed in the library. When this macro is set, the storage class will
 * be set to DLLEXPORT. At the end of the header file, the storage class will
 * be reset to DLLIMPORT.
 */

#undef TCL_STORAGE_CLASS
#ifdef BUILD_tcl
#   define TCL_STORAGE_CLASS DLLEXPORT
#else
#   ifdef USE_TCL_STUBS
#      define TCL_STORAGE_CLASS
#   else
#      define TCL_STORAGE_CLASS DLLIMPORT
#   endif
#endif

#if !defined(CONST86) && !defined(TCL_NO_DEPRECATED)
#      define CONST86 const
#endif

/*
 * Make sure EXTERN isn't defined elsewhere.
 */

#ifdef EXTERN
#   undef EXTERN
#endif /* EXTERN */

#ifdef __cplusplus
#   define EXTERN extern "C" TCL_STORAGE_CLASS
#else
#   define EXTERN extern TCL_STORAGE_CLASS
#endif

/*
 * Miscellaneous declarations.
 */

typedef void *ClientData;

/*
 * Darwin specific configure overrides (to support fat compiles, where
 * configure runs only once for multiple architectures):
 */

#ifdef __APPLE__
#   ifdef __LP64__
#	define TCL_WIDE_INT_IS_LONG 1
#	define TCL_CFG_DO64BIT 1
#    else /* !__LP64__ */
#	undef TCL_WIDE_INT_IS_LONG
#	undef TCL_CFG_DO64BIT
#    endif /* __LP64__ */
#    undef HAVE_STRUCT_STAT64
#endif /* __APPLE__ */

/* Cross-compiling 32-bit on a 64-bit platform? Then our
 * configure script does the wrong thing. Correct that here.
 */
#if defined(__GNUC__) && !defined(_WIN32) && !defined(__LP64__)
#   undef TCL_WIDE_INT_IS_LONG
#endif

/*
 * Define Tcl_WideInt to be a type that is (at least) 64-bits wide, and define
 * Tcl_WideUInt to be the unsigned variant of that type (assuming that where
 * we have one, we can have the other.)
 *
 * Also defines the following macros:
 * TCL_WIDE_INT_IS_LONG - if wide ints are really longs (i.e. we're on a
 *	LP64 system such as modern Solaris or Linux ... not including Win64)
 * Tcl_WideAsLong - forgetful converter from wideInt to long.
 * Tcl_LongAsWide - sign-extending converter from long to wideInt.
 * Tcl_WideAsDouble - converter from wideInt to double.
 * Tcl_DoubleAsWide - converter from double to wideInt.
 *
 * The following invariant should hold for any long value 'longVal':
 *	longVal == Tcl_WideAsLong(Tcl_LongAsWide(longVal))
 */

#if !defined(TCL_WIDE_INT_TYPE) && !defined(TCL_WIDE_INT_IS_LONG) && !defined(_WIN32) && !defined(__GNUC__)
/*
 * Don't know what platform it is and configure hasn't discovered what is
 * going on for us. Try to guess...
 */
#   include <limits.h>
#   if defined(LLONG_MAX) && (LLONG_MAX == LONG_MAX)
#	define TCL_WIDE_INT_IS_LONG	1
#   endif
#endif

#ifndef TCL_WIDE_INT_TYPE
#   define TCL_WIDE_INT_TYPE		long long
#endif /* !TCL_WIDE_INT_TYPE */

typedef TCL_WIDE_INT_TYPE		Tcl_WideInt;
typedef unsigned TCL_WIDE_INT_TYPE	Tcl_WideUInt;

#ifndef TCL_LL_MODIFIER
#   if defined(_WIN32) && (!defined(__USE_MINGW_ANSI_STDIO) || !__USE_MINGW_ANSI_STDIO)
#	define TCL_LL_MODIFIER	"I64"
#   else
#	define TCL_LL_MODIFIER	"ll"
#   endif
#endif /* !TCL_LL_MODIFIER */
#ifndef TCL_Z_MODIFIER
#   if defined(__GNUC__) && !defined(_WIN32)
#	define TCL_Z_MODIFIER	"z"
#   elif defined(_WIN64)
#	define TCL_Z_MODIFIER	TCL_LL_MODIFIER
#   else
#	define TCL_Z_MODIFIER	""
#   endif
#endif /* !TCL_Z_MODIFIER */
#ifndef TCL_T_MODIFIER
#   if defined(__GNUC__) && !defined(_WIN32)
#	define TCL_T_MODIFIER	"t"
#   elif defined(_WIN64)
#	define TCL_T_MODIFIER	TCL_LL_MODIFIER
#   else
#	define TCL_T_MODIFIER	TCL_Z_MODIFIER
#   endif
#endif /* !TCL_T_MODIFIER */

#define Tcl_WideAsLong(val)	((long)((Tcl_WideInt)(val)))
#define Tcl_LongAsWide(val)	((Tcl_WideInt)((long)(val)))
#define Tcl_WideAsDouble(val)	((double)((Tcl_WideInt)(val)))
#define Tcl_DoubleAsWide(val)	((Tcl_WideInt)((double)(val)))

#if TCL_MAJOR_VERSION < 9
# ifndef Tcl_Size
    typedef int Tcl_Size;
# endif
# ifndef TCL_SIZE_MAX
#   define TCL_SIZE_MAX ((int)(((unsigned int)-1)>>1))
# endif
# ifndef TCL_SIZE_MODIFIER
#   define TCL_SIZE_MODIFIER ""
#endif
#else
    typedef ptrdiff_t Tcl_Size;
#   define TCL_SIZE_MAX ((Tcl_Size)(((size_t)-1)>>1))
#   define TCL_SIZE_MODIFIER TCL_T_MODIFIER
#endif /* TCL_MAJOR_VERSION */

#ifdef _WIN32
#   if TCL_MAJOR_VERSION > 8 || defined(_WIN64) || defined(_USE_64BIT_TIME_T)
	typedef struct __stat64 Tcl_StatBuf;
#   elif defined(_USE_32BIT_TIME_T)
	typedef struct _stati64	Tcl_StatBuf;
#   else
	typedef struct _stat32i64 Tcl_StatBuf;
#   endif
#elif defined(__CYGWIN__)
    typedef struct {
	unsigned st_dev;
	unsigned short st_ino;
	unsigned short st_mode;
	short st_nlink;
	short st_uid;
	short st_gid;
	/* Here is a 2-byte gap */
	unsigned st_rdev;
	/* Here is a 4-byte gap */
	long long st_size;
	struct {long tv_sec;} st_atim;
	struct {long tv_sec;} st_mtim;
	struct {long tv_sec;} st_ctim;
    } Tcl_StatBuf;
#else
    typedef struct stat Tcl_StatBuf;
#endif

/*
 *----------------------------------------------------------------------------
 * Data structures defined opaquely in this module. The definitions below just
 * provide dummy types.
 */

typedef struct Tcl_AsyncHandler_ *Tcl_AsyncHandler;
typedef struct Tcl_Channel_ *Tcl_Channel;
typedef struct Tcl_ChannelTypeVersion_ *Tcl_ChannelTypeVersion;
typedef struct Tcl_Command_ *Tcl_Command;
typedef struct Tcl_Condition_ *Tcl_Condition;
typedef struct Tcl_Dict_ *Tcl_Dict;
typedef struct Tcl_EncodingState_ *Tcl_EncodingState;
typedef struct Tcl_Encoding_ *Tcl_Encoding;
typedef struct Tcl_Event Tcl_Event;
typedef struct Tcl_Interp Tcl_Interp;
typedef struct Tcl_InterpState_ *Tcl_InterpState;
typedef struct Tcl_LoadHandle_ *Tcl_LoadHandle;
typedef struct Tcl_Mutex_ *Tcl_Mutex;
typedef struct Tcl_Pid_ *Tcl_Pid;
typedef struct Tcl_RegExp_ *Tcl_RegExp;
typedef struct Tcl_ThreadDataKey_ *Tcl_ThreadDataKey;
typedef struct Tcl_ThreadId_ *Tcl_ThreadId;
typedef struct Tcl_TimerToken_ *Tcl_TimerToken;
typedef struct Tcl_Trace_ *Tcl_Trace;
typedef struct Tcl_Var_ *Tcl_Var;
typedef struct Tcl_ZLibStream_ *Tcl_ZlibStream;

/*
 *----------------------------------------------------------------------------
 * Definition of the interface to functions implementing threads. A function
 * following this definition is given to each call of 'Tcl_CreateThread' and
 * will be called as the main fuction of the new thread created by that call.
 */

#if defined _WIN32
typedef unsigned (__stdcall Tcl_ThreadCreateProc) (void *clientData);
#else
typedef void (Tcl_ThreadCreateProc) (void *clientData);
#endif

/*
 * Threading function return types used for abstracting away platform
 * differences when writing a Tcl_ThreadCreateProc. See the NewThread function
 * in generic/tclThreadTest.c for it's usage.
 */

#if defined _WIN32
#   define Tcl_ThreadCreateType		unsigned __stdcall
#   define TCL_THREAD_CREATE_RETURN	return 0
#else
#   define Tcl_ThreadCreateType		void
#   define TCL_THREAD_CREATE_RETURN
#endif

/*
 * Definition of values for default stacksize and the possible flags to be
 * given to Tcl_CreateThread.
 */

#define TCL_THREAD_STACK_DEFAULT (0)    /* Use default size for stack. */
#define TCL_THREAD_NOFLAGS	 (0000) /* Standard flags, default
					 * behaviour. */
#define TCL_THREAD_JOINABLE	 (0001) /* Mark the thread as joinable. */

/*
 * Flag values passed to Tcl_StringCaseMatch.
 */

#define TCL_MATCH_NOCASE	(1<<0)

/*
 * Flag values passed to Tcl_GetRegExpFromObj.
 */

#define	TCL_REG_BASIC		000000	/* BREs (convenience). */
#define	TCL_REG_EXTENDED	000001	/* EREs. */
#define	TCL_REG_ADVF		000002	/* Advanced features in EREs. */
#define	TCL_REG_ADVANCED	000003	/* AREs (which are also EREs). */
#define	TCL_REG_QUOTE		000004	/* No special characters, none. */
#define	TCL_REG_NOCASE		000010	/* Ignore case. */
#define	TCL_REG_NOSUB		000020	/* Don't care about subexpressions. */
#define	TCL_REG_EXPANDED	000040	/* Expanded format, white space &
					 * comments. */
#define	TCL_REG_NLSTOP		000100  /* \n doesn't match . or [^ ] */
#define	TCL_REG_NLANCH		000200  /* ^ matches after \n, $ before. */
#define	TCL_REG_NEWLINE		000300  /* Newlines are line terminators. */
#define	TCL_REG_CANMATCH	001000  /* Report details on partial/limited
					 * matches. */

/*
 * Flags values passed to Tcl_RegExpExecObj.
 */

#define	TCL_REG_NOTBOL	0001	/* Beginning of string does not match ^. */
#define	TCL_REG_NOTEOL	0002	/* End of string does not match $. */

/*
 * Structures filled in by Tcl_RegExpInfo. Note that all offset values are
 * relative to the start of the match string, not the beginning of the entire
 * string.
 */

typedef struct Tcl_RegExpIndices {
#if TCL_MAJOR_VERSION > 8
    Tcl_Size start;		/* Character offset of first character in
				 * match. */
    Tcl_Size end;		/* Character offset of first character after
				 * the match. */
#else
    long start;
    long end;
#endif
} Tcl_RegExpIndices;

typedef struct Tcl_RegExpInfo {
    Tcl_Size nsubs;		/* Number of subexpressions in the compiled
				 * expression. */
    Tcl_RegExpIndices *matches;	/* Array of nsubs match offset pairs. */
#if TCL_MAJOR_VERSION > 8
    Tcl_Size extendStart;	/* The offset at which a subsequent match
				 * might begin. */
#else
    long extendStart;
    long reserved;		/* Reserved for later use. */
#endif
} Tcl_RegExpInfo;

/*
 * Picky compilers complain if this typdef doesn't appear before the struct's
 * reference in tclDecls.h.
 */

typedef Tcl_StatBuf *Tcl_Stat_;
typedef struct stat *Tcl_OldStat_;

/*
 *----------------------------------------------------------------------------
 * When a TCL command returns, the interpreter contains a result from the
 * command. Programmers are strongly encouraged to use one of the functions
 * Tcl_GetObjResult() or Tcl_GetStringResult() to read the interpreter's
 * result. See the SetResult man page for details. Besides this result, the
 * command function returns an integer code, which is one of the following:
 *
 * TCL_OK		Command completed normally; the interpreter's result
 *			contains the command's result.
 * TCL_ERROR		The command couldn't be completed successfully; the
 *			interpreter's result describes what went wrong.
 * TCL_RETURN		The command requests that the current function return;
 *			the interpreter's result contains the function's
 *			return value.
 * TCL_BREAK		The command requests that the innermost loop be
 *			exited; the interpreter's result is meaningless.
 * TCL_CONTINUE		Go on to the next iteration of the current loop; the
 *			interpreter's result is meaningless.
 * Integer return codes in the range TCL_CODE_USER_MIN to TCL_CODE_USER_MAX are
 * reserved for the use of packages.
 */

#define TCL_OK			0
#define TCL_ERROR		1
#define TCL_RETURN		2
#define TCL_BREAK		3
#define TCL_CONTINUE		4
#define TCL_CODE_USER_MIN	5
#define TCL_CODE_USER_MAX	0x3fffffff /*  1073741823 */

/*
 *----------------------------------------------------------------------------
 * Flags to control what substitutions are performed by Tcl_SubstObj():
 */

#define TCL_SUBST_COMMANDS	001
#define TCL_SUBST_VARIABLES	002
#define TCL_SUBST_BACKSLASHES	004
#define TCL_SUBST_ALL		007

/*
 * Forward declaration of Tcl_Obj to prevent an error when the forward
 * reference to Tcl_Obj is encountered in the function types declared below.
 */

struct Tcl_Obj;

/*
 *----------------------------------------------------------------------------
 * Function types defined by Tcl:
 */

typedef int (Tcl_AppInitProc) (Tcl_Interp *interp);
typedef int (Tcl_AsyncProc) (void *clientData, Tcl_Interp *interp,
	int code);
typedef void (Tcl_ChannelProc) (void *clientData, int mask);
typedef void (Tcl_CloseProc) (void *data);
typedef void (Tcl_CmdDeleteProc) (void *clientData);
typedef int (Tcl_CmdProc) (void *clientData, Tcl_Interp *interp,
	int argc, const char *argv[]);
typedef void (Tcl_CmdTraceProc) (void *clientData, Tcl_Interp *interp,
	int level, char *command, Tcl_CmdProc *proc,
	void *cmdClientData, int argc, const char *argv[]);
typedef int (Tcl_CmdObjTraceProc) (void *clientData, Tcl_Interp *interp,
	int level, const char *command, Tcl_Command commandInfo, int objc,
	struct Tcl_Obj *const *objv);
typedef void (Tcl_CmdObjTraceDeleteProc) (void *clientData);
typedef void (Tcl_DupInternalRepProc) (struct Tcl_Obj *srcPtr,
	struct Tcl_Obj *dupPtr);
typedef int (Tcl_EncodingConvertProc) (void *clientData, const char *src,
	int srcLen, int flags, Tcl_EncodingState *statePtr, char *dst,
	int dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr);
typedef void (Tcl_EncodingFreeProc) (void *clientData);
typedef int (Tcl_EventProc) (Tcl_Event *evPtr, int flags);
typedef void (Tcl_EventCheckProc) (void *clientData, int flags);
typedef int (Tcl_EventDeleteProc) (Tcl_Event *evPtr, void *clientData);
typedef void (Tcl_EventSetupProc) (void *clientData, int flags);
typedef void (Tcl_ExitProc) (void *clientData);
typedef void (Tcl_FileProc) (void *clientData, int mask);
typedef void (Tcl_FileFreeProc) (void *clientData);
typedef void (Tcl_FreeInternalRepProc) (struct Tcl_Obj *objPtr);
typedef void (Tcl_IdleProc) (void *clientData);
typedef void (Tcl_InterpDeleteProc) (void *clientData,
	Tcl_Interp *interp);
typedef void (Tcl_NamespaceDeleteProc) (void *clientData);
typedef int (Tcl_ObjCmdProc) (void *clientData, Tcl_Interp *interp,
	int objc, struct Tcl_Obj *const *objv);
#if TCL_MAJOR_VERSION > 8
typedef int (Tcl_ObjCmdProc2) (void *clientData, Tcl_Interp *interp,
	Tcl_Size objc, struct Tcl_Obj *const *objv);
typedef int (Tcl_CmdObjTraceProc2) (void *clientData, Tcl_Interp *interp,
	Tcl_Size level, const char *command, Tcl_Command commandInfo, Tcl_Size objc,
	struct Tcl_Obj *const *objv);
typedef void (Tcl_FreeProc) (void *blockPtr);
#define Tcl_ExitProc Tcl_FreeProc
#define Tcl_FileFreeProc Tcl_FreeProc
#define Tcl_FileFreeProc Tcl_FreeProc
#define Tcl_EncodingFreeProc Tcl_FreeProc
#else
#define Tcl_ObjCmdProc2 Tcl_ObjCmdProc
#define Tcl_CmdObjTraceProc2 Tcl_CmdObjTraceProc
typedef void (Tcl_FreeProc) (char *blockPtr);
#endif
typedef int (Tcl_LibraryInitProc) (Tcl_Interp *interp);
typedef int (Tcl_LibraryUnloadProc) (Tcl_Interp *interp, int flags);
typedef void (Tcl_PanicProc) (const char *format, ...);
typedef void (Tcl_TcpAcceptProc) (void *callbackData, Tcl_Channel chan,
	char *address, int port);
typedef void (Tcl_TimerProc) (void *clientData);
typedef int (Tcl_SetFromAnyProc) (Tcl_Interp *interp, struct Tcl_Obj *objPtr);
typedef void (Tcl_UpdateStringProc) (struct Tcl_Obj *objPtr);
typedef char * (Tcl_VarTraceProc) (void *clientData, Tcl_Interp *interp,
	const char *part1, const char *part2, int flags);
typedef void (Tcl_CommandTraceProc) (void *clientData, Tcl_Interp *interp,
	const char *oldName, const char *newName, int flags);
typedef void (Tcl_CreateFileHandlerProc) (int fd, int mask, Tcl_FileProc *proc,
	void *clientData);
typedef void (Tcl_DeleteFileHandlerProc) (int fd);
typedef void (Tcl_AlertNotifierProc) (void *clientData);
typedef void (Tcl_ServiceModeHookProc) (int mode);
typedef void *(Tcl_InitNotifierProc) (void);
typedef void (Tcl_FinalizeNotifierProc) (void *clientData);
typedef void (Tcl_MainLoopProc) (void);

/* Abstract List functions */
typedef Tcl_Size (Tcl_ObjTypeLengthProc) (struct Tcl_Obj *listPtr);
typedef int (Tcl_ObjTypeIndexProc) (Tcl_Interp *interp, struct Tcl_Obj *listPtr,
	Tcl_Size index, struct Tcl_Obj** elemObj);
typedef int (Tcl_ObjTypeSliceProc) (Tcl_Interp *interp, struct Tcl_Obj *listPtr,
	Tcl_Size fromIdx, Tcl_Size toIdx, struct Tcl_Obj **newObjPtr);
typedef int (Tcl_ObjTypeReverseProc) (Tcl_Interp *interp,
	struct Tcl_Obj *listPtr, struct Tcl_Obj **newObjPtr);
typedef int (Tcl_ObjTypeGetElements) (Tcl_Interp *interp,
	struct Tcl_Obj *listPtr, Tcl_Size *objcptr, struct Tcl_Obj ***objvptr);
typedef	struct Tcl_Obj *(Tcl_ObjTypeSetElement) (Tcl_Interp *interp,
	struct Tcl_Obj *listPtr, Tcl_Size indexCount,
	struct Tcl_Obj *const indexArray[], struct Tcl_Obj *valueObj);
typedef int (Tcl_ObjTypeReplaceProc) (Tcl_Interp *interp,
	struct Tcl_Obj *listObj, Tcl_Size first, Tcl_Size numToDelete,
	Tcl_Size numToInsert, struct Tcl_Obj *const insertObjs[]);
typedef int (Tcl_ObjTypeInOperatorProc) (Tcl_Interp *interp,
	struct Tcl_Obj *valueObj, struct Tcl_Obj *listObj, int *boolResult);

#ifndef TCL_NO_DEPRECATED
#   define Tcl_PackageInitProc Tcl_LibraryInitProc
#   define Tcl_PackageUnloadProc Tcl_LibraryUnloadProc
#endif

/*
 *----------------------------------------------------------------------------
 * The following structure represents a type of object, which is a particular
 * internal representation for an object plus a set of functions that provide
 * standard operations on objects of that type.
 */

typedef struct Tcl_ObjType {
    const char *name;		/* Name of the type, e.g. "int". */
    Tcl_FreeInternalRepProc *freeIntRepProc;
				/* Called to free any storage for the type's
				 * internal rep. NULL if the internal rep does
				 * not need freeing. */
    Tcl_DupInternalRepProc *dupIntRepProc;
				/* Called to create a new object as a copy of
				 * an existing object. */
    Tcl_UpdateStringProc *updateStringProc;
				/* Called to update the string rep from the
				 * type's internal representation. */
    Tcl_SetFromAnyProc *setFromAnyProc;
				/* Called to convert the object's internal rep
				 * to this type. Frees the internal rep of the
				 * old type. Returns TCL_ERROR on failure. */
#if TCL_MAJOR_VERSION > 8
    size_t version;		/* Version field for future-proofing. */

    /* List emulation functions - ObjType Version 1 */
    Tcl_ObjTypeLengthProc *lengthProc;
				/* Return the [llength] of the AbstractList */
    Tcl_ObjTypeIndexProc *indexProc;
				/* Return a value (Tcl_Obj) at a given index */
    Tcl_ObjTypeSliceProc *sliceProc;
				/* Return an AbstractList for
				 * [lrange $al $start $end] */
    Tcl_ObjTypeReverseProc *reverseProc;
				/* Return an AbstractList for [lreverse $al] */
    Tcl_ObjTypeGetElements *getElementsProc;
				/* Return an objv[] of all elements in the list */
    Tcl_ObjTypeSetElement *setElementProc;
				/* Replace the element at the indicies with the
				 * given valueObj. */
    Tcl_ObjTypeReplaceProc *replaceProc;
				/* Replace sublist with another sublist */
    Tcl_ObjTypeInOperatorProc *inOperProc;
				/* "in" and "ni" expr list operation.
				 * Determine if the given string value matches
				 * an element in the list. */
#endif
} Tcl_ObjType;

#if TCL_MAJOR_VERSION > 8
#   define TCL_OBJTYPE_V0 0, \
	   0,0,0,0,0,0,0,0	/* Pre-Tcl 9 */
#   define TCL_OBJTYPE_V1(a) offsetof(Tcl_ObjType, indexProc), \
	   a,0,0,0,0,0,0,0	/* Tcl 9 Version 1 */
#   define TCL_OBJTYPE_V2(a,b,c,d,e,f,g,h) sizeof(Tcl_ObjType),  \
	   a,b,c,d,e,f,g,h	/* Tcl 9 - AbstractLists */
#else
#   define TCL_OBJTYPE_V0 /* just empty */
#   define TCL_OBJTYPE_V1(a) /* just empty */
#   define TCL_OBJTYPE_V2(a,b,c,d,e,f,g,h) /* just empty */
#endif

/*
 * The following structure stores an internal representation (internalrep) for
 * a Tcl value. An internalrep is associated with an Tcl_ObjType when both
 * are stored in the same Tcl_Obj.  The routines of the Tcl_ObjType govern
 * the handling of the internalrep.
 */

typedef union Tcl_ObjInternalRep {	/* The internal representation: */
    long longValue;		/*   - an long integer value. */
    double doubleValue;		/*   - a double-precision floating value. */
    void *otherValuePtr;	/*   - another, type-specific value, */
				/*     not used internally any more. */
    Tcl_WideInt wideValue;	/*   - an integer value >= 64bits */
    struct {			/*   - internal rep as two pointers. */
	void *ptr1;
	void *ptr2;
    } twoPtrValue;
    struct {			/*   - internal rep as a pointer and a long, */
	void *ptr;		/*     not used internally any more. */
	unsigned long value;
    } ptrAndLongRep;
    struct {			/*   - use for pointer and length reps */
	void *ptr;
	Tcl_Size size;
    } ptrAndSize;
} Tcl_ObjInternalRep;

/*
 * One of the following structures exists for each object in the Tcl system.
 * An object stores a value as either a string, some internal representation,
 * or both.
 */

typedef struct Tcl_Obj {
    Tcl_Size refCount;		/* When 0 the object will be freed. */
    char *bytes;		/* This points to the first byte of the
				 * object's string representation. The array
				 * must be followed by a null byte (i.e., at
				 * offset length) but may also contain
				 * embedded null characters. The array's
				 * storage is allocated by Tcl_Alloc. NULL means
				 * the string rep is invalid and must be
				 * regenerated from the internal rep.  Clients
				 * should use Tcl_GetStringFromObj or
				 * Tcl_GetString to get a pointer to the byte
				 * array as a readonly value. */
    Tcl_Size length;		/* The number of bytes at *bytes, not
				 * including the terminating null. */
    const Tcl_ObjType *typePtr;	/* Denotes the object's type. Always
				 * corresponds to the type of the object's
				 * internal rep. NULL indicates the object has
				 * no internal rep (has no type). */
    Tcl_ObjInternalRep internalRep;
				/* The internal representation: */
} Tcl_Obj;

/*
 *----------------------------------------------------------------------------
 * The following definitions support Tcl's namespace facility. Note: the first
 * five fields must match exactly the fields in a Namespace structure (see
 * tclInt.h).
 */

typedef struct Tcl_Namespace {
    char *name;			/* The namespace's name within its parent
				 * namespace. This contains no ::'s. The name
				 * of the global namespace is "" although "::"
				 * is an synonym. */
    char *fullName;		/* The namespace's fully qualified name. This
				 * starts with ::. */
    void *clientData;		/* Arbitrary value associated with this
				 * namespace. */
    Tcl_NamespaceDeleteProc *deleteProc;
				/* Function invoked when deleting the
				 * namespace to, e.g., free clientData. */
    struct Tcl_Namespace *parentPtr;
				/* Points to the namespace that contains this
				 * one. NULL if this is the global
				 * namespace. */
} Tcl_Namespace;

/*
 *----------------------------------------------------------------------------
 * The following structure represents a call frame, or activation record. A
 * call frame defines a naming context for a procedure call: its local scope
 * (for local variables) and its namespace scope (used for non-local
 * variables; often the global :: namespace). A call frame can also define the
 * naming context for a namespace eval or namespace inscope command: the
 * namespace in which the command's code should execute. The Tcl_CallFrame
 * structures exist only while procedures or namespace eval/inscope's are
 * being executed, and provide a Tcl call stack.
 *
 * A call frame is initialized and pushed using Tcl_PushCallFrame and popped
 * using Tcl_PopCallFrame. Storage for a Tcl_CallFrame must be provided by the
 * Tcl_PushCallFrame caller, and callers typically allocate them on the C call
 * stack for efficiency. For this reason, Tcl_CallFrame is defined as a
 * structure and not as an opaque token. However, most Tcl_CallFrame fields
 * are hidden since applications should not access them directly; others are
 * declared as "dummyX".
 *
 * WARNING!! The structure definition must be kept consistent with the
 * CallFrame structure in tclInt.h. If you change one, change the other.
 */

typedef struct Tcl_CallFrame {
    Tcl_Namespace *nsPtr;	/* Current namespace for the call frame. */
    int dummy1;
    Tcl_Size dummy2;
    void *dummy3;
    void *dummy4;
    void *dummy5;
    Tcl_Size dummy6;
    void *dummy7;
    void *dummy8;
    Tcl_Size dummy9;
    void *dummy10;
    void *dummy11;
    void *dummy12;
    void *dummy13;
} Tcl_CallFrame;

/*
 *----------------------------------------------------------------------------
 * Information about commands that is returned by Tcl_GetCommandInfo and
 * passed to Tcl_SetCommandInfo. objProc is an objc/objv object-based command
 * function while proc is a traditional Tcl argc/argv string-based function.
 * Tcl_CreateObjCommand and Tcl_CreateCommand ensure that both objProc and
 * proc are non-NULL and can be called to execute the command. However, it may
 * be faster to call one instead of the other. The member isNativeObjectProc
 * is set to 1 if an object-based function was registered by
 * Tcl_CreateObjCommand, and to 0 if a string-based function was registered by
 * Tcl_CreateCommand. The other function is typically set to a compatibility
 * wrapper that does string-to-object or object-to-string argument conversions
 * then calls the other function.
 */

typedef struct {
    int isNativeObjectProc;	/* 1 if objProc was registered by a call to
				 * Tcl_CreateObjCommand; 2 if objProc was registered by
				 * a call to Tcl_CreateObjCommand2; 0 otherwise.
				 * Tcl_SetCmdInfo does not modify this field. */
    Tcl_ObjCmdProc *objProc;	/* Command's object-based function. */
    void *objClientData;	/* ClientData for object proc. */
    Tcl_CmdProc *proc;		/* Command's string-based function. */
    void *clientData;		/* ClientData for string proc. */
    Tcl_CmdDeleteProc *deleteProc;
				/* Function to call when command is
				 * deleted. */
    void *deleteData;		/* Value to pass to deleteProc (usually the
				 * same as clientData). */
    Tcl_Namespace *namespacePtr;/* Points to the namespace that contains this
				 * command. Note that Tcl_SetCmdInfo will not
				 * change a command's namespace; use
				 * TclRenameCommand or Tcl_Eval (of 'rename')
				 * to do that. */
    Tcl_ObjCmdProc2 *objProc2;	/* Command's object2-based function. */
    void *objClientData2;	/* ClientData for object2 proc. */
} Tcl_CmdInfo;

/*
 *----------------------------------------------------------------------------
 * The structure defined below is used to hold dynamic strings. The only
 * fields that clients should use are string and length, accessible via the
 * macros Tcl_DStringValue and Tcl_DStringLength.
 */

#define TCL_DSTRING_STATIC_SIZE 200
typedef struct Tcl_DString {
    char *string;		/* Points to beginning of string: either
				 * staticSpace below or a malloced array. */
    Tcl_Size length;		/* Number of bytes in string excluding
				 * terminating nul */
    Tcl_Size spaceAvl;		/* Total number of bytes available for the
				 * string and its terminating NULL char. */
    char staticSpace[TCL_DSTRING_STATIC_SIZE];
				/* Space to use in common case where string is
				 * small. */
} Tcl_DString;

#define Tcl_DStringLength(dsPtr) ((dsPtr)->length)
#define Tcl_DStringValue(dsPtr) ((dsPtr)->string)

/*
 * Definitions for the maximum number of digits of precision that may be
 * produced by Tcl_PrintDouble, and the number of bytes of buffer space
 * required by Tcl_PrintDouble.
 */

#define TCL_MAX_PREC		17
#define TCL_DOUBLE_SPACE	(TCL_MAX_PREC+10)

/*
 * Definition for a number of bytes of buffer space sufficient to hold the
 * string representation of an integer in base 10 (assuming the existence of
 * 64-bit integers).
 */

#define TCL_INTEGER_SPACE	(3*(int)sizeof(Tcl_WideInt))

/*
 *----------------------------------------------------------------------------
 * Type values returned by Tcl_GetNumberFromObj
 *	TCL_NUMBER_INT		Representation is a Tcl_WideInt
 *	TCL_NUMBER_BIG		Representation is an mp_int
 *	TCL_NUMBER_DOUBLE	Representation is a double
 *	TCL_NUMBER_NAN		Value is NaN.
 */

#define TCL_NUMBER_INT          2
#define TCL_NUMBER_BIG          3
#define TCL_NUMBER_DOUBLE       4
#define TCL_NUMBER_NAN          5

/*
 * Flag values passed to Tcl_ConvertElement.
 * TCL_DONT_USE_BRACES forces it not to enclose the element in braces, but to
 *	use backslash quoting instead.
 * TCL_DONT_QUOTE_HASH disables the default quoting of the '#' character. It
 *	is safe to leave the hash unquoted when the element is not the first
 *	element of a list, and this flag can be used by the caller to indicate
 *	that condition.
 */

#define TCL_DONT_USE_BRACES	1
#define TCL_DONT_QUOTE_HASH	8

/*
 * Flags that may be passed to Tcl_GetIndexFromObj.
 * TCL_EXACT disallows abbreviated strings.
 * TCL_NULL_OK allows the empty string or NULL to return TCL_OK.
 *      The returned value will be -1;
 * TCL_INDEX_TEMP_TABLE disallows caching of lookups. A possible use case is
 *      a table that will not live long enough to make it worthwhile.
 */

#define TCL_EXACT		1
#define TCL_NULL_OK		32
#define TCL_INDEX_TEMP_TABLE	64

/*
 * Flags that may be passed to Tcl_UniCharToUtf.
 * TCL_COMBINE Combine surrogates
 */

#if TCL_MAJOR_VERSION > 8
#    define TCL_COMBINE		0x1000000
#else
#    define TCL_COMBINE		0
#endif
/*
 *----------------------------------------------------------------------------
 * Flag values passed to Tcl_RecordAndEval, Tcl_EvalObj, Tcl_EvalObjv.
 * WARNING: these bit choices must not conflict with the bit choices for
 * evalFlag bits in tclInt.h!
 *
 * Meanings:
 *	TCL_NO_EVAL:		Just record this command
 *	TCL_EVAL_GLOBAL:	Execute script in global namespace
 *	TCL_EVAL_DIRECT:	Do not compile this script
 *	TCL_EVAL_INVOKE:	Magical Tcl_EvalObjv mode for aliases/ensembles
 *				o Run in iPtr->lookupNsPtr or global namespace
 *				o Cut out of error traces
 *				o Don't reset the flags controlling ensemble
 *				  error message rewriting.
 *	TCL_CANCEL_UNWIND:	Magical Tcl_CancelEval mode that causes the
 *				stack for the script in progress to be
 *				completely unwound.
 *	TCL_EVAL_NOERR:		Do no exception reporting at all, just return
 *				as the caller will report.
 */

#define TCL_NO_EVAL		0x010000
#define TCL_EVAL_GLOBAL		0x020000
#define TCL_EVAL_DIRECT		0x040000
#define TCL_EVAL_INVOKE		0x080000
#define TCL_CANCEL_UNWIND	0x100000
#define TCL_EVAL_NOERR          0x200000

/*
 * Special freeProc values that may be passed to Tcl_SetResult (see the man
 * page for details):
 */

#define TCL_VOLATILE		((Tcl_FreeProc *) 1)
#define TCL_STATIC		((Tcl_FreeProc *) 0)
#define TCL_DYNAMIC		((Tcl_FreeProc *) 3)

/*
 * Flag values passed to variable-related functions.
 * WARNING: these bit choices must not conflict with the bit choice for
 * TCL_CANCEL_UNWIND, above.
 */

#define TCL_GLOBAL_ONLY		 1
#define TCL_NAMESPACE_ONLY	 2
#define TCL_APPEND_VALUE	 4
#define TCL_LIST_ELEMENT	 8
#define TCL_TRACE_READS		 0x10
#define TCL_TRACE_WRITES	 0x20
#define TCL_TRACE_UNSETS	 0x40
#define TCL_TRACE_DESTROYED	 0x80

#define TCL_LEAVE_ERR_MSG	 0x200
#define TCL_TRACE_ARRAY		 0x800
/* Indicate the semantics of the result of a trace. */
#define TCL_TRACE_RESULT_DYNAMIC 0x8000
#define TCL_TRACE_RESULT_OBJECT  0x10000

/*
 * Flag values for ensemble commands.
 */

#define TCL_ENSEMBLE_PREFIX 0x02/* Flag value to say whether to allow
				 * unambiguous prefixes of commands or to
				 * require exact matches for command names. */

/*
 * Flag values passed to command-related functions.
 */

#define TCL_TRACE_RENAME	0x2000
#define TCL_TRACE_DELETE	0x4000

#define TCL_ALLOW_INLINE_COMPILATION 0x20000

/*
 * Types for linked variables:
 */

#define TCL_LINK_INT		1
#define TCL_LINK_DOUBLE		2
#define TCL_LINK_BOOLEAN	3
#define TCL_LINK_STRING		4
#define TCL_LINK_WIDE_INT	5
#define TCL_LINK_CHAR		6
#define TCL_LINK_UCHAR		7
#define TCL_LINK_SHORT		8
#define TCL_LINK_USHORT		9
#define TCL_LINK_UINT		10
#define TCL_LINK_LONG		((sizeof(long) != sizeof(int)) ? TCL_LINK_WIDE_INT : TCL_LINK_INT)
#define TCL_LINK_ULONG		((sizeof(long) != sizeof(int)) ? TCL_LINK_WIDE_UINT : TCL_LINK_UINT)
#define TCL_LINK_FLOAT		13
#define TCL_LINK_WIDE_UINT	14
#define TCL_LINK_CHARS		15
#define TCL_LINK_BINARY		16
#define TCL_LINK_READ_ONLY	0x80

/*
 *----------------------------------------------------------------------------
 * Forward declarations of Tcl_HashTable and related types.
 */

#ifndef TCL_HASH_TYPE
#if TCL_MAJOR_VERSION > 8
#  define TCL_HASH_TYPE size_t
#else
#  define TCL_HASH_TYPE unsigned
#endif
#endif

typedef struct Tcl_HashKeyType Tcl_HashKeyType;
typedef struct Tcl_HashTable Tcl_HashTable;
typedef struct Tcl_HashEntry Tcl_HashEntry;

typedef TCL_HASH_TYPE (Tcl_HashKeyProc) (Tcl_HashTable *tablePtr, void *keyPtr);
typedef int (Tcl_CompareHashKeysProc) (void *keyPtr, Tcl_HashEntry *hPtr);
typedef Tcl_HashEntry * (Tcl_AllocHashEntryProc) (Tcl_HashTable *tablePtr,
	void *keyPtr);
typedef void (Tcl_FreeHashEntryProc) (Tcl_HashEntry *hPtr);

/*
 * Structure definition for an entry in a hash table. No-one outside Tcl
 * should access any of these fields directly; use the macros defined below.
 */

struct Tcl_HashEntry {
    Tcl_HashEntry *nextPtr;	/* Pointer to next entry in this hash bucket,
				 * or NULL for end of chain. */
    Tcl_HashTable *tablePtr;	/* Pointer to table containing entry. */
    size_t hash;		/* Hash value. */
    void *clientData;		/* Application stores something here with
				 * Tcl_SetHashValue. */
    union {			/* Key has one of these forms: */
	char *oneWordValue;	/* One-word value for key. */
	Tcl_Obj *objPtr;	/* Tcl_Obj * key value. */
	int words[1];		/* Multiple integer words for key. The actual
				 * size will be as large as necessary for this
				 * table's keys. */
	char string[1];		/* String for key. The actual size will be as
				 * large as needed to hold the key. */
    } key;			/* MUST BE LAST FIELD IN RECORD!! */
};

/*
 * Flags used in Tcl_HashKeyType.
 *
 * TCL_HASH_KEY_RANDOMIZE_HASH -
 *				There are some things, pointers for example
 *				which don't hash well because they do not use
 *				the lower bits. If this flag is set then the
 *				hash table will attempt to rectify this by
 *				randomising the bits and then using the upper
 *				N bits as the index into the table.
 * TCL_HASH_KEY_SYSTEM_HASH -	If this flag is set then all memory internally
 *                              allocated for the hash table that is not for an
 *                              entry will use the system heap.
 * TCL_HASH_KEY_DIRECT_COMPARE -
 *	                        Allows fast comparison for hash keys directly
 *                              by compare of their key.oneWordValue values,
 *                              before call of compareKeysProc (much slower
 *                              than a direct compare, so it is speed-up only
 *                              flag). Don't use it if keys contain values rather
 *                              than pointers.
 */

#define TCL_HASH_KEY_RANDOMIZE_HASH 0x1
#define TCL_HASH_KEY_SYSTEM_HASH    0x2
#define TCL_HASH_KEY_DIRECT_COMPARE 0x4

/*
 * Structure definition for the methods associated with a hash table key type.
 */

#define TCL_HASH_KEY_TYPE_VERSION 1
struct Tcl_HashKeyType {
    int version;		/* Version of the table. If this structure is
				 * extended in future then the version can be
				 * used to distinguish between different
				 * structures. */
    int flags;			/* Flags, see above for details. */
    Tcl_HashKeyProc *hashKeyProc;
				/* Calculates a hash value for the key. If
				 * this is NULL then the pointer itself is
				 * used as a hash value. */
    Tcl_CompareHashKeysProc *compareKeysProc;
				/* Compares two keys and returns zero if they
				 * do not match, and non-zero if they do. If
				 * this is NULL then the pointers are
				 * compared. */
    Tcl_AllocHashEntryProc *allocEntryProc;
				/* Called to allocate memory for a new entry,
				 * i.e. if the key is a string then this could
				 * allocate a single block which contains
				 * enough space for both the entry and the
				 * string. Only the key field of the allocated
				 * Tcl_HashEntry structure needs to be filled
				 * in. If something else needs to be done to
				 * the key, i.e. incrementing a reference
				 * count then that should be done by this
				 * function. If this is NULL then Tcl_Alloc is
				 * used to allocate enough space for a
				 * Tcl_HashEntry and the key pointer is
				 * assigned to key.oneWordValue. */
    Tcl_FreeHashEntryProc *freeEntryProc;
				/* Called to free memory associated with an
				 * entry. If something else needs to be done
				 * to the key, i.e. decrementing a reference
				 * count then that should be done by this
				 * function. If this is NULL then Tcl_Free is
				 * used to free the Tcl_HashEntry. */
};

/*
 * Structure definition for a hash table.  Must be in tcl.h so clients can
 * allocate space for these structures, but clients should never access any
 * fields in this structure.
 */

#define TCL_SMALL_HASH_TABLE 4
struct Tcl_HashTable {
    Tcl_HashEntry **buckets;	/* Pointer to bucket array. Each element
				 * points to first entry in bucket's hash
				 * chain, or NULL. */
    Tcl_HashEntry *staticBuckets[TCL_SMALL_HASH_TABLE];
				/* Bucket array used for small tables (to
				 * avoid mallocs and frees). */
    Tcl_Size numBuckets;	/* Total number of buckets allocated at
				 * **bucketPtr. */
    Tcl_Size numEntries;	/* Total number of entries present in
				 * table. */
    Tcl_Size rebuildSize;	/* Enlarge table when numEntries gets to be
				 * this large. */
#if TCL_MAJOR_VERSION > 8
    size_t mask;		/* Mask value used in hashing function. */
#endif
    int downShift;		/* Shift count used in hashing function.
				 * Designed to use high-order bits of
				 * randomized keys. */
#if TCL_MAJOR_VERSION < 9
    int mask;			/* Mask value used in hashing function. */
#endif
    int keyType;		/* Type of keys used in this table. It's
				 * either TCL_CUSTOM_KEYS, TCL_STRING_KEYS,
				 * TCL_ONE_WORD_KEYS, or an integer giving the
				 * number of ints that is the size of the
				 * key. */
    Tcl_HashEntry *(*findProc) (Tcl_HashTable *tablePtr, const char *key);
    Tcl_HashEntry *(*createProc) (Tcl_HashTable *tablePtr, const char *key,
	    int *newPtr);
    const Tcl_HashKeyType *typePtr;
				/* Type of the keys used in the
				 * Tcl_HashTable. */
};

/*
 * Structure definition for information used to keep track of searches through
 * hash tables:
 */

typedef struct Tcl_HashSearch {
    Tcl_HashTable *tablePtr;	/* Table being searched. */
    Tcl_Size nextIndex;		/* Index of next bucket to be enumerated after
				 * present one. */
    Tcl_HashEntry *nextEntryPtr;/* Next entry to be enumerated in the current
				 * bucket. */
} Tcl_HashSearch;

/*
 * Acceptable key types for hash tables:
 *
 * TCL_STRING_KEYS:		The keys are strings, they are copied into the
 *				entry.
 * TCL_ONE_WORD_KEYS:		The keys are pointers, the pointer is stored
 *				in the entry.
 * TCL_CUSTOM_TYPE_KEYS:	The keys are arbitrary types which are copied
 *				into the entry.
 * TCL_CUSTOM_PTR_KEYS:		The keys are pointers to arbitrary types, the
 *				pointer is stored in the entry.
 *
 * While maintaining binary compatibility the above have to be distinct values
 * as they are used to differentiate between old versions of the hash table
 * which don't have a typePtr and new ones which do. Once binary compatibility
 * is discarded in favour of making more wide spread changes TCL_STRING_KEYS
 * can be the same as TCL_CUSTOM_TYPE_KEYS, and TCL_ONE_WORD_KEYS can be the
 * same as TCL_CUSTOM_PTR_KEYS because they simply determine how the key is
 * accessed from the entry and not the behaviour.
 */

#define TCL_STRING_KEYS		(0)
#define TCL_ONE_WORD_KEYS	(1)
#define TCL_CUSTOM_TYPE_KEYS	(-2)
#define TCL_CUSTOM_PTR_KEYS	(-1)

/*
 * Structure definition for information used to keep track of searches through
 * dictionaries. These fields should not be accessed by code outside
 * tclDictObj.c
 */

typedef struct {
    void *next;			/* Search position for underlying hash
				 * table. */
    TCL_HASH_TYPE epoch;	/* Epoch marker for dictionary being searched,
				 * or 0 if search has terminated. */
    Tcl_Dict dictionaryPtr;	/* Reference to dictionary being searched. */
} Tcl_DictSearch;

/*
 *----------------------------------------------------------------------------
 * Flag values to pass to Tcl_DoOneEvent to disable searches for some kinds of
 * events:
 */

#define TCL_DONT_WAIT		(1<<1)
#define TCL_WINDOW_EVENTS	(1<<2)
#define TCL_FILE_EVENTS		(1<<3)
#define TCL_TIMER_EVENTS	(1<<4)
#define TCL_IDLE_EVENTS		(1<<5)	/* WAS 0x10 ???? */
#define TCL_ALL_EVENTS		(~TCL_DONT_WAIT)

/*
 * The following structure defines a generic event for the Tcl event system.
 * These are the things that are queued in calls to Tcl_QueueEvent and
 * serviced later by Tcl_DoOneEvent. There can be many different kinds of
 * events with different fields, corresponding to window events, timer events,
 * etc. The structure for a particular event consists of a Tcl_Event header
 * followed by additional information specific to that event.
 */

struct Tcl_Event {
    Tcl_EventProc *proc;	/* Function to call to service this event. */
    struct Tcl_Event *nextPtr;	/* Next in list of pending events, or NULL. */
};

/*
 * Positions to pass to Tcl_QueueEvent/Tcl_ThreadQueueEvent:
 */

typedef enum {
    TCL_QUEUE_TAIL, TCL_QUEUE_HEAD, TCL_QUEUE_MARK,
    TCL_QUEUE_ALERT_IF_EMPTY=4
} Tcl_QueuePosition;

/*
 * Values to pass to Tcl_SetServiceMode to specify the behavior of notifier
 * event routines.
 */

#define TCL_SERVICE_NONE 0
#define TCL_SERVICE_ALL 1

/*
 * The following structure keeps is used to hold a time value, either as an
 * absolute time (the number of seconds from the epoch) or as an elapsed time.
 * On Unix systems the epoch is Midnight Jan 1, 1970 GMT.
 */

typedef struct Tcl_Time {
#if TCL_MAJOR_VERSION > 8
    long long sec;		/* Seconds. */
#else
    long sec;			/* Seconds. */
#endif
#if defined(_CYGWIN_) && TCL_MAJOR_VERSION > 8
    int usec;			/* Microseconds. */
#else
    long usec;			/* Microseconds. */
#endif
} Tcl_Time;

typedef void (Tcl_SetTimerProc) (const Tcl_Time *timePtr);
typedef int (Tcl_WaitForEventProc) (const Tcl_Time *timePtr);

/*
 * TIP #233 (Virtualized Time)
 */

typedef void (Tcl_GetTimeProc)   (Tcl_Time *timebuf, void *clientData);
typedef void (Tcl_ScaleTimeProc) (Tcl_Time *timebuf, void *clientData);

/*
 *----------------------------------------------------------------------------
 * Bits to pass to Tcl_CreateFileHandler and Tcl_CreateChannelHandler to
 * indicate what sorts of events are of interest:
 */

#define TCL_READABLE		(1<<1)
#define TCL_WRITABLE		(1<<2)
#define TCL_EXCEPTION		(1<<3)

/*
 * Flag values to pass to Tcl_OpenCommandChannel to indicate the disposition
 * of the stdio handles. TCL_STDIN, TCL_STDOUT, TCL_STDERR, are also used in
 * Tcl_GetStdChannel.
 */

#define TCL_STDIN		(1<<1)
#define TCL_STDOUT		(1<<2)
#define TCL_STDERR		(1<<3)
#define TCL_ENFORCE_MODE	(1<<4)

/*
 * Bits passed to Tcl_DriverClose2Proc to indicate which side of a channel
 * should be closed.
 */

#define TCL_CLOSE_READ		(1<<1)
#define TCL_CLOSE_WRITE		(1<<2)

/*
 * Value to use as the closeProc for a channel that supports the close2Proc
 * interface.
 */

#if TCL_MAJOR_VERSION > 8
#   define TCL_CLOSE2PROC		NULL
#else
#   define TCL_CLOSE2PROC		((void *) 1)
#endif

/*
 * Channel version tag. This was introduced in 8.3.2/8.4.
 */

#define TCL_CHANNEL_VERSION_5	((Tcl_ChannelTypeVersion) 0x5)

/*
 * TIP #218: Channel Actions, Ids for Tcl_DriverThreadActionProc.
 */

#define TCL_CHANNEL_THREAD_INSERT (0)
#define TCL_CHANNEL_THREAD_REMOVE (1)

/*
 * Typedefs for the various operations in a channel type:
 */

typedef int	(Tcl_DriverBlockModeProc) (void *instanceData, int mode);
typedef void Tcl_DriverCloseProc;
typedef int	(Tcl_DriverClose2Proc) (void *instanceData,
			Tcl_Interp *interp, int flags);
typedef int	(Tcl_DriverInputProc) (void *instanceData, char *buf,
			int toRead, int *errorCodePtr);
typedef int	(Tcl_DriverOutputProc) (void *instanceData,
			const char *buf, int toWrite, int *errorCodePtr);
typedef void Tcl_DriverSeekProc;
typedef int	(Tcl_DriverSetOptionProc) (void *instanceData,
			Tcl_Interp *interp, const char *optionName,
			const char *value);
typedef int	(Tcl_DriverGetOptionProc) (void *instanceData,
			Tcl_Interp *interp, const char *optionName,
			Tcl_DString *dsPtr);
typedef void	(Tcl_DriverWatchProc) (void *instanceData, int mask);
typedef int	(Tcl_DriverGetHandleProc) (void *instanceData,
			int direction, void **handlePtr);
typedef int	(Tcl_DriverFlushProc) (void *instanceData);
typedef int	(Tcl_DriverHandlerProc) (void *instanceData,
			int interestMask);
typedef long long (Tcl_DriverWideSeekProc) (void *instanceData,
			long long offset, int mode, int *errorCodePtr);
/*
 * TIP #218, Channel Thread Actions
 */
typedef void	(Tcl_DriverThreadActionProc) (void *instanceData,
			int action);
/*
 * TIP #208, File Truncation (etc.)
 */
typedef int	(Tcl_DriverTruncateProc) (void *instanceData,
			long long length);

/*
 * struct Tcl_ChannelType:
 *
 * One such structure exists for each type (kind) of channel. It collects
 * together in one place all the functions that are part of the specific
 * channel type.
 *
 * It is recommend that the Tcl_Channel* functions are used to access elements
 * of this structure, instead of direct accessing.
 */

typedef struct Tcl_ChannelType {
    const char *typeName;	/* The name of the channel type in Tcl
				 * commands. This storage is owned by channel
				 * type. */
    Tcl_ChannelTypeVersion version;
				/* Version of the channel type. */
    void *closeProc;		/* Not used any more. */
    Tcl_DriverInputProc *inputProc;
				/* Function to call for input on channel. */
    Tcl_DriverOutputProc *outputProc;
				/* Function to call for output on channel. */
    void *seekProc;		/* Not used any more. */
    Tcl_DriverSetOptionProc *setOptionProc;
				/* Set an option on a channel. */
    Tcl_DriverGetOptionProc *getOptionProc;
				/* Get an option from a channel. */
    Tcl_DriverWatchProc *watchProc;
				/* Set up the notifier to watch for events on
				 * this channel. */
    Tcl_DriverGetHandleProc *getHandleProc;
				/* Get an OS handle from the channel or NULL
				 * if not supported. */
    Tcl_DriverClose2Proc *close2Proc;
				/* Function to call to close the channel if
				 * the device supports closing the read &
				 * write sides independently. */
    Tcl_DriverBlockModeProc *blockModeProc;
				/* Set blocking mode for the raw channel. May
				 * be NULL. */
    Tcl_DriverFlushProc *flushProc;
				/* Function to call to flush a channel. May be
				 * NULL. */
    Tcl_DriverHandlerProc *handlerProc;
				/* Function to call to handle a channel event.
				 * This will be passed up the stacked channel
				 * chain. */
    Tcl_DriverWideSeekProc *wideSeekProc;
				/* Function to call to seek on the channel
				 * which can handle 64-bit offsets. May be
				 * NULL, and must be NULL if seekProc is
				 * NULL. */
    Tcl_DriverThreadActionProc *threadActionProc;
				/* Function to call to notify the driver of
				 * thread specific activity for a channel. May
				 * be NULL. */
    Tcl_DriverTruncateProc *truncateProc;
				/* Function to call to truncate the underlying
				 * file to a particular length. May be NULL if
				 * the channel does not support truncation. */
} Tcl_ChannelType;

/*
 * The following flags determine whether the blockModeProc above should set
 * the channel into blocking or nonblocking mode. They are passed as arguments
 * to the blockModeProc function in the above structure.
 */

#define TCL_MODE_BLOCKING	0	/* Put channel into blocking mode. */
#define TCL_MODE_NONBLOCKING	1	/* Put channel into nonblocking
					 * mode. */

/*
 *----------------------------------------------------------------------------
 * Enum for different types of file paths.
 */

typedef enum Tcl_PathType {
    TCL_PATH_ABSOLUTE,
    TCL_PATH_RELATIVE,
    TCL_PATH_VOLUME_RELATIVE
} Tcl_PathType;

/*
 * The following structure is used to pass glob type data amongst the various
 * glob routines and Tcl_FSMatchInDirectory.
 */

typedef struct Tcl_GlobTypeData {
    int type;			/* Corresponds to bcdpfls as in 'find -t'. */
    int perm;			/* Corresponds to file permissions. */
    Tcl_Obj *macType;		/* Acceptable Mac type. */
    Tcl_Obj *macCreator;	/* Acceptable Mac creator. */
} Tcl_GlobTypeData;

/*
 * Type and permission definitions for glob command.
 */

#define TCL_GLOB_TYPE_BLOCK		(1<<0)
#define TCL_GLOB_TYPE_CHAR		(1<<1)
#define TCL_GLOB_TYPE_DIR		(1<<2)
#define TCL_GLOB_TYPE_PIPE		(1<<3)
#define TCL_GLOB_TYPE_FILE		(1<<4)
#define TCL_GLOB_TYPE_LINK		(1<<5)
#define TCL_GLOB_TYPE_SOCK		(1<<6)
#define TCL_GLOB_TYPE_MOUNT		(1<<7)

#define TCL_GLOB_PERM_RONLY		(1<<0)
#define TCL_GLOB_PERM_HIDDEN		(1<<1)
#define TCL_GLOB_PERM_R			(1<<2)
#define TCL_GLOB_PERM_W			(1<<3)
#define TCL_GLOB_PERM_X			(1<<4)

/*
 * Flags for the unload callback function.
 */

#define TCL_UNLOAD_DETACH_FROM_INTERPRETER	(1<<0)
#define TCL_UNLOAD_DETACH_FROM_PROCESS		(1<<1)

/*
 * Typedefs for the various filesystem operations:
 */

typedef int (Tcl_FSStatProc) (Tcl_Obj *pathPtr, Tcl_StatBuf *buf);
typedef int (Tcl_FSAccessProc) (Tcl_Obj *pathPtr, int mode);
typedef Tcl_Channel (Tcl_FSOpenFileChannelProc) (Tcl_Interp *interp,
	Tcl_Obj *pathPtr, int mode, int permissions);
typedef int (Tcl_FSMatchInDirectoryProc) (Tcl_Interp *interp, Tcl_Obj *result,
	Tcl_Obj *pathPtr, const char *pattern, Tcl_GlobTypeData *types);
typedef Tcl_Obj * (Tcl_FSGetCwdProc) (Tcl_Interp *interp);
typedef int (Tcl_FSChdirProc) (Tcl_Obj *pathPtr);
typedef int (Tcl_FSLstatProc) (Tcl_Obj *pathPtr, Tcl_StatBuf *buf);
typedef int (Tcl_FSCreateDirectoryProc) (Tcl_Obj *pathPtr);
typedef int (Tcl_FSDeleteFileProc) (Tcl_Obj *pathPtr);
typedef int (Tcl_FSCopyDirectoryProc) (Tcl_Obj *srcPathPtr,
	Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr);
typedef int (Tcl_FSCopyFileProc) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr);
typedef int (Tcl_FSRemoveDirectoryProc) (Tcl_Obj *pathPtr, int recursive,
	Tcl_Obj **errorPtr);
typedef int (Tcl_FSRenameFileProc) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr);
typedef void (Tcl_FSUnloadFileProc) (Tcl_LoadHandle loadHandle);
typedef Tcl_Obj * (Tcl_FSListVolumesProc) (void);
/* We have to declare the utime structure here. */
struct utimbuf;
typedef int (Tcl_FSUtimeProc) (Tcl_Obj *pathPtr, struct utimbuf *tval);
typedef int (Tcl_FSNormalizePathProc) (Tcl_Interp *interp, Tcl_Obj *pathPtr,
	int nextCheckpoint);
typedef int (Tcl_FSFileAttrsGetProc) (Tcl_Interp *interp, int index,
	Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef);
typedef const char *const * (Tcl_FSFileAttrStringsProc) (Tcl_Obj *pathPtr,
	Tcl_Obj **objPtrRef);
typedef int (Tcl_FSFileAttrsSetProc) (Tcl_Interp *interp, int index,
	Tcl_Obj *pathPtr, Tcl_Obj *objPtr);
typedef Tcl_Obj * (Tcl_FSLinkProc) (Tcl_Obj *pathPtr, Tcl_Obj *toPtr,
	int linkType);
typedef int (Tcl_FSLoadFileProc) (Tcl_Interp *interp, Tcl_Obj *pathPtr,
	Tcl_LoadHandle *handlePtr, Tcl_FSUnloadFileProc **unloadProcPtr);
typedef int (Tcl_FSPathInFilesystemProc) (Tcl_Obj *pathPtr,
	void **clientDataPtr);
typedef Tcl_Obj * (Tcl_FSFilesystemPathTypeProc) (Tcl_Obj *pathPtr);
typedef Tcl_Obj * (Tcl_FSFilesystemSeparatorProc) (Tcl_Obj *pathPtr);
#define Tcl_FSFreeInternalRepProc Tcl_FreeProc
typedef void *(Tcl_FSDupInternalRepProc) (void *clientData);
typedef Tcl_Obj * (Tcl_FSInternalToNormalizedProc) (void *clientData);
typedef void *(Tcl_FSCreateInternalRepProc) (Tcl_Obj *pathPtr);

typedef struct Tcl_FSVersion_ *Tcl_FSVersion;

/*
 *----------------------------------------------------------------------------
 * Data structures related to hooking into the filesystem
 */

/*
 * Filesystem version tag.  This was introduced in 8.4.
 */

#define TCL_FILESYSTEM_VERSION_1	((Tcl_FSVersion) 0x1)

/*
 * struct Tcl_Filesystem:
 *
 * One such structure exists for each type (kind) of filesystem. It collects
 * together the functions that form the interface for a particulr the
 * filesystem. Tcl always accesses the filesystem through one of these
 * structures.
 *
 * Not all entries need be non-NULL; any which are NULL are simply ignored.
 * However, a complete filesystem should provide all of these functions. The
 * explanations in the structure show the importance of each function.
 */

typedef struct Tcl_Filesystem {
    const char *typeName;	/* The name of the filesystem. */
    Tcl_Size structureLength;	/* Length of this structure, so future binary
				 * compatibility can be assured. */
    Tcl_FSVersion version;	/* Version of the filesystem type. */
    Tcl_FSPathInFilesystemProc *pathInFilesystemProc;
				/* Determines whether the pathname is in this
				 * filesystem. This is the most important
				 * filesystem function. */
    Tcl_FSDupInternalRepProc *dupInternalRepProc;
				/* Duplicates the internal handle of the node.
				 * If it is NULL, the filesystem is less
				 * performant. */
    Tcl_FSFreeInternalRepProc *freeInternalRepProc;
				/* Frees the internal handle of the node.  NULL
				 * only if there is no need to free resources
				 * used for the internal handle. */
    Tcl_FSInternalToNormalizedProc *internalToNormalizedProc;
				/* Converts the internal handle to a normalized
				 * path.  NULL if the filesystem creates nodes
				 * having no pathname. */
    Tcl_FSCreateInternalRepProc *createInternalRepProc;
				/* Creates an internal handle for a pathname.
				 * May be NULL if pathnames have no internal
				 * handle or if pathInFilesystemProc always
				 * immediately creates an internal
				 * representation for pathnames in the
				 * filesystem. */
    Tcl_FSNormalizePathProc *normalizePathProc;
				/* Normalizes a path.  Should be implemented if
				 * the filesystems supports multiple paths to
				 * the same node. */
    Tcl_FSFilesystemPathTypeProc *filesystemPathTypeProc;
				/* Determines the type of a path in this
				 * filesystem. May be NULL. */
    Tcl_FSFilesystemSeparatorProc *filesystemSeparatorProc;
				/* Produces the separator character(s) for this
				 * filesystem. Must not be NULL. */
    Tcl_FSStatProc *statProc;	/* Called by 'Tcl_FSStat()'.  Provided by any
				 * reasonable filesystem. */
    Tcl_FSAccessProc *accessProc;
				/* Called by 'Tcl_FSAccess()'.  Implemented by
				 * any reasonable filesystem. */
    Tcl_FSOpenFileChannelProc *openFileChannelProc;
				/* Called by 'Tcl_FSOpenFileChannel()'.
				 * Provided by any reasonable filesystem. */
    Tcl_FSMatchInDirectoryProc *matchInDirectoryProc;
				/* Called by 'Tcl_FSMatchInDirectory()'.  NULL
				 * if the filesystem does not support glob or
				 * recursive copy. */
    Tcl_FSUtimeProc *utimeProc;	/* Called by 'Tcl_FSUtime()', by 'file
				 *  mtime' to set (not read) times, 'file
				 *  atime', and the open-r/open-w/fcopy variant
				 *  of 'file copy'. */
    Tcl_FSLinkProc *linkProc;	/* Called by 'Tcl_FSLink()'. NULL if reading or
				 *  creating links is not supported. */
    Tcl_FSListVolumesProc *listVolumesProc;
				/* Lists filesystem volumes added by this
				 * filesystem. NULL if the filesystem does not
				 * use volumes. */
    Tcl_FSFileAttrStringsProc *fileAttrStringsProc;
				/* List all valid attributes strings.  NULL if
				 * the filesystem does not support the 'file
				 * attributes' command.  Can be used to attach
				 * arbitrary additional data to files in a
				 * filesystem. */
    Tcl_FSFileAttrsGetProc *fileAttrsGetProc;
				/* Called by 'Tcl_FSFileAttrsGet()' and by
				 * 'file attributes'. */
    Tcl_FSFileAttrsSetProc *fileAttrsSetProc;
				/* Called by 'Tcl_FSFileAttrsSet()' and by
				 * 'file attributes'. */
    Tcl_FSCreateDirectoryProc *createDirectoryProc;
				/* Called by 'Tcl_FSCreateDirectory()'.  May be
				 * NULL if the filesystem is read-only. */
    Tcl_FSRemoveDirectoryProc *removeDirectoryProc;
				/* Called by 'Tcl_FSRemoveDirectory()'.  May be
				 * NULL if the filesystem is read-only. */
    Tcl_FSDeleteFileProc *deleteFileProc;
				/* Called by 'Tcl_FSDeleteFile()' May be NULL
				 * if the filesystem is is read-only. */
    Tcl_FSCopyFileProc *copyFileProc;
				/* Called by 'Tcl_FSCopyFile()'.  If NULL, for
				 * a copy operation at the script level (not
				 * C) Tcl uses open-r, open-w and fcopy. */
    Tcl_FSRenameFileProc *renameFileProc;
				/* Called by 'Tcl_FSRenameFile()'. If NULL, for
				 * a rename operation at the script level (not
				 * C) Tcl performs a copy operation followed
				 * by a delete operation. */
    Tcl_FSCopyDirectoryProc *copyDirectoryProc;
				/* Called by 'Tcl_FSCopyDirectory()'. If NULL,
				 * for a copy operation at the script level
				 * (not C) Tcl recursively creates directories
				 * and copies files. */
    Tcl_FSLstatProc *lstatProc;	/* Called by 'Tcl_FSLstat()'. If NULL, Tcl
				 * attempts to use 'statProc' instead. */
    Tcl_FSLoadFileProc *loadFileProc;
				/* Called by 'Tcl_FSLoadFile()'. If NULL, Tcl
				 * performs a copy to a temporary file in the
				 * native filesystem and then calls
				 * Tcl_FSLoadFile() on that temporary copy. */
    Tcl_FSGetCwdProc *getCwdProc;
				/* Called by 'Tcl_FSGetCwd()'.  Normally NULL.
				 * Usually only called once:  If 'getcwd' is
				 * called before 'chdir' is ever called. */
    Tcl_FSChdirProc *chdirProc;	/* Called by 'Tcl_FSChdir()'.  For a virtual
				 * filesystem, chdirProc just returns zero
				 * (success) if the pathname is a valid
				 * directory, and some other value otherwise.
				 * For A real filesystem, chdirProc performs
				 * the correct action, e.g.  calls the system
				 * 'chdir' function. If not implemented, then
				 * 'cd' and 'pwd' fail for a pathname in this
				 * filesystem. On success Tcl stores the
				 * pathname for use by GetCwd.  If NULL, Tcl
				 * performs records the pathname as the new
				 * current directory if it passes a series of
				 * directory access checks. */
} Tcl_Filesystem;

/*
 * The following definitions are used as values for the 'linkAction' flag to
 * Tcl_FSLink, or the linkProc of any filesystem. Any combination of flags can
 * be given. For link creation, the linkProc should create a link which
 * matches any of the types given.
 *
 * TCL_CREATE_SYMBOLIC_LINK -	Create a symbolic or soft link.
 * TCL_CREATE_HARD_LINK -	Create a hard link.
 */

#define TCL_CREATE_SYMBOLIC_LINK	0x01
#define TCL_CREATE_HARD_LINK		0x02

/*
 *----------------------------------------------------------------------------
 * The following structure represents the Notifier functions that you can
 * override with the Tcl_SetNotifier call.
 */

typedef struct Tcl_NotifierProcs {
    Tcl_SetTimerProc *setTimerProc;
    Tcl_WaitForEventProc *waitForEventProc;
    Tcl_CreateFileHandlerProc *createFileHandlerProc;
    Tcl_DeleteFileHandlerProc *deleteFileHandlerProc;
    Tcl_InitNotifierProc *initNotifierProc;
    Tcl_FinalizeNotifierProc *finalizeNotifierProc;
    Tcl_AlertNotifierProc *alertNotifierProc;
    Tcl_ServiceModeHookProc *serviceModeHookProc;
} Tcl_NotifierProcs;

/*
 *----------------------------------------------------------------------------
 * The following data structures and declarations are for the new Tcl parser.
 *
 * For each word of a command, and for each piece of a word such as a variable
 * reference, one of the following structures is created to describe the
 * token.
 */

typedef struct Tcl_Token {
    int type;			/* Type of token, such as TCL_TOKEN_WORD; see
				 * below for valid types. */
    const char *start;		/* First character in token. */
    Tcl_Size size;		/* Number of bytes in token. */
    Tcl_Size numComponents;	/* If this token is composed of other tokens,
				 * this field tells how many of them there are
				 * (including components of components, etc.).
				 * The component tokens immediately follow
				 * this one. */
} Tcl_Token;

/*
 * Type values defined for Tcl_Token structures. These values are defined as
 * mask bits so that it's easy to check for collections of types.
 *
 * TCL_TOKEN_WORD -		The token describes one word of a command,
 *				from the first non-blank character of the word
 *				(which may be " or {) up to but not including
 *				the space, semicolon, or bracket that
 *				terminates the word. NumComponents counts the
 *				total number of sub-tokens that make up the
 *				word. This includes, for example, sub-tokens
 *				of TCL_TOKEN_VARIABLE tokens.
 * TCL_TOKEN_SIMPLE_WORD -	This token is just like TCL_TOKEN_WORD except
 *				that the word is guaranteed to consist of a
 *				single TCL_TOKEN_TEXT sub-token.
 * TCL_TOKEN_TEXT -		The token describes a range of literal text
 *				that is part of a word. NumComponents is
 *				always 0.
 * TCL_TOKEN_BS -		The token describes a backslash sequence that
 *				must be collapsed. NumComponents is always 0.
 * TCL_TOKEN_COMMAND -		The token describes a command whose result
 *				must be substituted into the word. The token
 *				includes the enclosing brackets. NumComponents
 *				is always 0.
 * TCL_TOKEN_VARIABLE -		The token describes a variable substitution,
 *				including the dollar sign, variable name, and
 *				array index (if there is one) up through the
 *				right parentheses. NumComponents tells how
 *				many additional tokens follow to represent the
 *				variable name. The first token will be a
 *				TCL_TOKEN_TEXT token that describes the
 *				variable name. If the variable is an array
 *				reference then there will be one or more
 *				additional tokens, of type TCL_TOKEN_TEXT,
 *				TCL_TOKEN_BS, TCL_TOKEN_COMMAND, and
 *				TCL_TOKEN_VARIABLE, that describe the array
 *				index; numComponents counts the total number
 *				of nested tokens that make up the variable
 *				reference, including sub-tokens of
 *				TCL_TOKEN_VARIABLE tokens.
 * TCL_TOKEN_SUB_EXPR -		The token describes one subexpression of an
 *				expression, from the first non-blank character
 *				of the subexpression up to but not including
 *				the space, brace, or bracket that terminates
 *				the subexpression. NumComponents counts the
 *				total number of following subtokens that make
 *				up the subexpression; this includes all
 *				subtokens for any nested TCL_TOKEN_SUB_EXPR
 *				tokens. For example, a numeric value used as a
 *				primitive operand is described by a
 *				TCL_TOKEN_SUB_EXPR token followed by a
 *				TCL_TOKEN_TEXT token. A binary subexpression
 *				is described by a TCL_TOKEN_SUB_EXPR token
 *				followed by the TCL_TOKEN_OPERATOR token for
 *				the operator, then TCL_TOKEN_SUB_EXPR tokens
 *				for the left then the right operands.
 * TCL_TOKEN_OPERATOR -		The token describes one expression operator.
 *				An operator might be the name of a math
 *				function such as "abs". A TCL_TOKEN_OPERATOR
 *				token is always preceded by one
 *				TCL_TOKEN_SUB_EXPR token for the operator's
 *				subexpression, and is followed by zero or more
 *				TCL_TOKEN_SUB_EXPR tokens for the operator's
 *				operands. NumComponents is always 0.
 * TCL_TOKEN_EXPAND_WORD -	This token is just like TCL_TOKEN_WORD except
 *				that it marks a word that began with the
 *				literal character prefix "{*}". This word is
 *				marked to be expanded - that is, broken into
 *				words after substitution is complete.
 */

#define TCL_TOKEN_WORD		1
#define TCL_TOKEN_SIMPLE_WORD	2
#define TCL_TOKEN_TEXT		4
#define TCL_TOKEN_BS		8
#define TCL_TOKEN_COMMAND	16
#define TCL_TOKEN_VARIABLE	32
#define TCL_TOKEN_SUB_EXPR	64
#define TCL_TOKEN_OPERATOR	128
#define TCL_TOKEN_EXPAND_WORD	256

/*
 * Parsing error types. On any parsing error, one of these values will be
 * stored in the error field of the Tcl_Parse structure defined below.
 */

#define TCL_PARSE_SUCCESS		0
#define TCL_PARSE_QUOTE_EXTRA		1
#define TCL_PARSE_BRACE_EXTRA		2
#define TCL_PARSE_MISSING_BRACE		3
#define TCL_PARSE_MISSING_BRACKET	4
#define TCL_PARSE_MISSING_PAREN		5
#define TCL_PARSE_MISSING_QUOTE		6
#define TCL_PARSE_MISSING_VAR_BRACE	7
#define TCL_PARSE_SYNTAX		8
#define TCL_PARSE_BAD_NUMBER		9

/*
 * A structure of the following type is filled in by Tcl_ParseCommand. It
 * describes a single command parsed from an input string.
 */

#define NUM_STATIC_TOKENS 20

typedef struct Tcl_Parse {
    const char *commentStart;	/* Pointer to # that begins the first of one
				 * or more comments preceding the command. */
    Tcl_Size commentSize;	/* Number of bytes in comments (up through
				 * newline character that terminates the last
				 * comment). If there were no comments, this
				 * field is 0. */
    const char *commandStart;	/* First character in first word of
				 * command. */
    Tcl_Size commandSize;	/* Number of bytes in command, including first
				 * character of first word, up through the
				 * terminating newline, close bracket, or
				 * semicolon. */
    Tcl_Size numWords;		/* Total number of words in command. May be
				 * 0. */
    Tcl_Token *tokenPtr;	/* Pointer to first token representing the
				 * words of the command. Initially points to
				 * staticTokens, but may change to point to
				 * malloc-ed space if command exceeds space in
				 * staticTokens. */
    Tcl_Size numTokens;		/* Total number of tokens in command. */
    Tcl_Size tokensAvailable;	/* Total number of tokens available at
				 * *tokenPtr. */
    int errorType;		/* One of the parsing error types defined
				 * above. */
#if TCL_MAJOR_VERSION > 8
    int incomplete;		/* This field is set to 1 by Tcl_ParseCommand
				 * if the command appears to be incomplete.
				 * This information is used by
				 * Tcl_CommandComplete. */
#endif

    /*
     * The fields below are intended only for the private use of the parser.
     * They should not be used by functions that invoke Tcl_ParseCommand.
     */

    const char *string;		/* The original command string passed to
				 * Tcl_ParseCommand. */
    const char *end;		/* Points to the character just after the last
				 * one in the command string. */
    Tcl_Interp *interp;		/* Interpreter to use for error reporting, or
				 * NULL. */
    const char *term;		/* Points to character in string that
				 * terminated most recent token. Filled in by
				 * ParseTokens. If an error occurs, points to
				 * beginning of region where the error
				 * occurred (e.g. the open brace if the close
				 * brace is missing). */
#if TCL_MAJOR_VERSION < 9
    int incomplete;
#endif
    Tcl_Token staticTokens[NUM_STATIC_TOKENS];
				/* Initial space for tokens for command. This
				 * space should be large enough to accommodate
				 * most commands; dynamic space is allocated
				 * for very large commands that don't fit
				 * here. */
} Tcl_Parse;

/*
 *----------------------------------------------------------------------------
 * The following structure represents a user-defined encoding. It collects
 * together all the functions that are used by the specific encoding.
 */

typedef struct Tcl_EncodingType {
    const char *encodingName;	/* The name of the encoding, e.g. "euc-jp".
				 * This name is the unique key for this
				 * encoding type. */
    Tcl_EncodingConvertProc *toUtfProc;
				/* Function to convert from external encoding
				 * into UTF-8. */
    Tcl_EncodingConvertProc *fromUtfProc;
				/* Function to convert from UTF-8 into
				 * external encoding. */
    Tcl_FreeProc *freeProc;	/* If non-NULL, function to call when this
				 * encoding is deleted. */
    void *clientData;		/* Arbitrary value associated with encoding
				 * type. Passed to conversion functions. */
    Tcl_Size nullSize;		/* Number of zero bytes that signify
				 * end-of-string in this encoding. This number
				 * is used to determine the source string
				 * length when the srcLen argument is
				 * negative. Must be 1, 2, or 4. */
} Tcl_EncodingType;

/*
 * The following definitions are used as values for the conversion control
 * flags argument when converting text from one character set to another:
 *
 * TCL_ENCODING_START -		Signifies that the source buffer is the first
 *				block in a (potentially multi-block) input
 *				stream. Tells the conversion function to reset
 *				to an initial state and perform any
 *				initialization that needs to occur before the
 *				first byte is converted. If the source buffer
 *				contains the entire input stream to be
 *				converted, this flag should be set.
 * TCL_ENCODING_END -		Signifies that the source buffer is the last
 *				block in a (potentially multi-block) input
 *				stream. Tells the conversion routine to
 *				perform any finalization that needs to occur
 *				after the last byte is converted and then to
 *				reset to an initial state. If the source
 *				buffer contains the entire input stream to be
 *				converted, this flag should be set.
 * TCL_ENCODING_STOPONERROR -	Not used any more.
 * TCL_ENCODING_NO_TERMINATE -	If set, Tcl_ExternalToUtf does not append a
 *				terminating NUL byte.  Since it does not need
 *				an extra byte for a terminating NUL, it fills
 *				all dstLen bytes with encoded UTF-8 content if
 *				needed.  If clear, a byte is reserved in the
 *				dst space for NUL termination, and a
 *				terminating NUL is appended.
 * TCL_ENCODING_CHAR_LIMIT -	If set and dstCharsPtr is not NULL, then
 *				Tcl_ExternalToUtf takes the initial value of
 *				*dstCharsPtr as a limit of the maximum number
 *				of chars to produce in the encoded UTF-8
 *				content.  Otherwise, the number of chars
 *				produced is controlled only by other limiting
 *				factors.
 * TCL_ENCODING_PROFILE_* -	Mutually exclusive encoding profile ids. Note
 *				these are bit masks.
 *
 * NOTE: THESE BIT DEFINITIONS SHOULD NOT OVERLAP WITH INTERNAL USE BITS
 * DEFINED IN tclEncoding.c (ENCODING_INPUT et al). Be cognizant of this
 * when adding bits.
 */

#define TCL_ENCODING_START		0x01
#define TCL_ENCODING_END		0x02
#if TCL_MAJOR_VERSION > 8
#   define TCL_ENCODING_STOPONERROR	0x0 /* Not used any more */
#else
#   define TCL_ENCODING_STOPONERROR	0x04
#endif
#define TCL_ENCODING_NO_TERMINATE	0x08
#define TCL_ENCODING_CHAR_LIMIT		0x10
/* Internal use bits, do not define bits in this space. See above comment */
#define TCL_ENCODING_INTERNAL_USE_MASK  0xFF00
/*
 * Reserve top byte for profile values (disjoint, not a mask). In case of
 * changes, ensure ENCODING_PROFILE_* macros in tclInt.h are modified if
 * necessary.
 */
#define TCL_ENCODING_PROFILE_STRICT   TCL_ENCODING_STOPONERROR
#define TCL_ENCODING_PROFILE_TCL8     0x01000000
#define TCL_ENCODING_PROFILE_REPLACE  0x02000000

/*
 * The following definitions are the error codes returned by the conversion
 * routines:
 *
 * TCL_OK -			All characters were converted.
 * TCL_CONVERT_NOSPACE -	The output buffer would not have been large
 *				enough for all of the converted data; as many
 *				characters as could fit were converted though.
 * TCL_CONVERT_MULTIBYTE -	The last few bytes in the source string were
 *				the beginning of a multibyte sequence, but
 *				more bytes were needed to complete this
 *				sequence. A subsequent call to the conversion
 *				routine should pass the beginning of this
 *				unconverted sequence plus additional bytes
 *				from the source stream to properly convert the
 *				formerly split-up multibyte sequence.
 * TCL_CONVERT_SYNTAX -		The source stream contained an invalid
 *				character sequence. This may occur if the
 *				input stream has been damaged or if the input
 *				encoding method was misidentified.
 * TCL_CONVERT_UNKNOWN -	The source string contained a character that
 *				could not be represented in the target
 *				encoding.
 */

#define TCL_CONVERT_MULTIBYTE	(-1)
#define TCL_CONVERT_SYNTAX	(-2)
#define TCL_CONVERT_UNKNOWN	(-3)
#define TCL_CONVERT_NOSPACE	(-4)

/*
 * The maximum number of bytes that are necessary to represent a single
 * Unicode character in UTF-8. The valid values are 3 and 4. If > 3,
 * then Tcl_UniChar must be 4-bytes in size (UCS-4) (the default). If == 3,
 * then Tcl_UniChar must be 2-bytes in size (UTF-16). Since Tcl 9.0, UCS-4
 * mode is the default and recommended mode.
 */

#ifndef TCL_UTF_MAX
#   if defined(BUILD_tcl) || TCL_MAJOR_VERSION > 8
#	define TCL_UTF_MAX		4
#   else
#	define TCL_UTF_MAX		3
#   endif
#endif

/*
 * This represents a Unicode character. Any changes to this should also be
 * reflected in regcustom.h.
 */

#if TCL_UTF_MAX == 4 && TCL_MAJOR_VERSION > 8
    /*
     * int isn't 100% accurate as it should be a strict 4-byte value
     * (perhaps int32_t). ILP64/SILP64 systems may have troubles. The
     * size of this value must be reflected correctly in regcustom.h.
     */
typedef int Tcl_UniChar;
#elif TCL_UTF_MAX == 3 && !defined(BUILD_tcl)
typedef unsigned short Tcl_UniChar;
#else
#   error "This TCL_UTF_MAX value is not supported"
#endif

/*
 *----------------------------------------------------------------------------
 * TIP #59: The following structure is used in calls 'Tcl_RegisterConfig' to
 * provide the system with the embedded configuration data.
 */

typedef struct Tcl_Config {
    const char *key;		/* Configuration key to register. ASCII
				 * encoded, thus UTF-8. */
    const char *value;		/* The value associated with the key. System
				 * encoding. */
} Tcl_Config;

/*
 *----------------------------------------------------------------------------
 * Flags for TIP#143 limits, detailing which limits are active in an
 * interpreter. Used for Tcl_{Add,Remove}LimitHandler type argument.
 */

#define TCL_LIMIT_COMMANDS	0x01
#define TCL_LIMIT_TIME		0x02

/*
 * Structure containing information about a limit handler to be called when a
 * command- or time-limit is exceeded by an interpreter.
 */

typedef void (Tcl_LimitHandlerProc) (void *clientData, Tcl_Interp *interp);
#if TCL_MAJOR_VERSION > 8
#define Tcl_LimitHandlerDeleteProc Tcl_FreeProc
#else
typedef void (Tcl_LimitHandlerDeleteProc) (void *clientData);
#endif

#if 0
/*
 *----------------------------------------------------------------------------
 * We would like to provide an anonymous structure "mp_int" here, which is
 * compatible with libtommath's "mp_int", but without duplicating anything
 * from <tommath.h> or including <tommath.h> here. But the libtommath project
 * didn't honor our request. See: <https://github.com/libtom/libtommath/pull/473>
 *
 * That's why this part is commented out, and we are using (void *) in
 * various API's in stead of the more correct (mp_int *).
 */

#ifndef MP_INT_DECLARED
#define MP_INT_DECLARED
typedef struct mp_int mp_int;
#endif

#endif

/*
 *----------------------------------------------------------------------------
 * Definitions needed for Tcl_ParseArgvObj routines.
 * Based on tkArgv.c.
 * Modifications from the original are copyright (c) Sam Bromley 2006
 */

typedef struct {
    int type;			/* Indicates the option type; see below. */
    const char *keyStr;		/* The key string that flags the option in the
				 * argv array. */
    void *srcPtr;		/* Value to be used in setting dst; usage
				 * depends on type.*/
    void *dstPtr;		/* Address of value to be modified; usage
				 * depends on type.*/
    const char *helpStr;	/* Documentation message describing this
				 * option. */
    void *clientData;		/* Word to pass to function callbacks. */
} Tcl_ArgvInfo;

/*
 * Legal values for the type field of a Tcl_ArgInfo: see the user
 * documentation for details.
 */

#define TCL_ARGV_CONSTANT	15
#define TCL_ARGV_INT		16
#define TCL_ARGV_STRING		17
#define TCL_ARGV_REST		18
#define TCL_ARGV_FLOAT		19
#define TCL_ARGV_FUNC		20
#define TCL_ARGV_GENFUNC	21
#define TCL_ARGV_HELP		22
#define TCL_ARGV_END		23

/*
 * Types of callback functions for the TCL_ARGV_FUNC and TCL_ARGV_GENFUNC
 * argument types:
 */

typedef int (Tcl_ArgvFuncProc)(void *clientData, Tcl_Obj *objPtr,
	void *dstPtr);
typedef Tcl_Size (Tcl_ArgvGenFuncProc)(void *clientData, Tcl_Interp *interp,
	Tcl_Size objc, Tcl_Obj *const *objv, void *dstPtr);

/*
 * Shorthand for commonly used argTable entries.
 */

#define TCL_ARGV_AUTO_HELP \
    {TCL_ARGV_HELP,	"-help",	NULL,	NULL, \
	    "Print summary of command-line options and abort", NULL}
#define TCL_ARGV_AUTO_REST \
    {TCL_ARGV_REST,	"--",		NULL,	NULL, \
	    "Marks the end of the options", NULL}
#define TCL_ARGV_TABLE_END \
    {TCL_ARGV_END, NULL, NULL, NULL, NULL, NULL}

/*
 *----------------------------------------------------------------------------
 * Definitions needed for Tcl_Zlib routines. [TIP #234]
 *
 * Constants for the format flags describing what sort of data format is
 * desired/expected for the Tcl_ZlibDeflate, Tcl_ZlibInflate and
 * Tcl_ZlibStreamInit functions.
 */

#define TCL_ZLIB_FORMAT_RAW	1
#define TCL_ZLIB_FORMAT_ZLIB	2
#define TCL_ZLIB_FORMAT_GZIP	4
#define TCL_ZLIB_FORMAT_AUTO	8

/*
 * Constants that describe whether the stream is to operate in compressing or
 * decompressing mode.
 */

#define TCL_ZLIB_STREAM_DEFLATE	16
#define TCL_ZLIB_STREAM_INFLATE	32

/*
 * Constants giving compression levels. Use of TCL_ZLIB_COMPRESS_DEFAULT is
 * recommended.
 */

#define TCL_ZLIB_COMPRESS_NONE	0
#define TCL_ZLIB_COMPRESS_FAST	1
#define TCL_ZLIB_COMPRESS_BEST	9
#define TCL_ZLIB_COMPRESS_DEFAULT (-1)

/*
 * Constants for types of flushing, used with Tcl_ZlibFlush.
 */

#define TCL_ZLIB_NO_FLUSH	0
#define TCL_ZLIB_FLUSH		2
#define TCL_ZLIB_FULLFLUSH	3
#define TCL_ZLIB_FINALIZE	4

/*
 *----------------------------------------------------------------------------
 * Definitions needed for the Tcl_LoadFile function. [TIP #416]
 */

#define TCL_LOAD_GLOBAL 1
#define TCL_LOAD_LAZY 2

/*
 *----------------------------------------------------------------------------
 * Definitions needed for the Tcl_OpenTcpServerEx function. [TIP #456]
 */
#define TCL_TCPSERVER_REUSEADDR (1<<0)
#define TCL_TCPSERVER_REUSEPORT (1<<1)

/*
 * Constants for special Tcl_Size-typed values, see TIP #494
 */

#define TCL_IO_FAILURE	((Tcl_Size)-1)
#define TCL_AUTO_LENGTH	((Tcl_Size)-1)
#define TCL_INDEX_NONE  ((Tcl_Size)-1)

/*
 *----------------------------------------------------------------------------
 * Single public declaration for NRE.
 */

typedef int (Tcl_NRPostProc) (void *data[], Tcl_Interp *interp,
				int result);

/*
 *----------------------------------------------------------------------------
 * The following constant is used to test for older versions of Tcl in the
 * stubs tables.
 */

#if TCL_MAJOR_VERSION > 8
#   define TCL_STUB_MAGIC	((int) 0xFCA3BACB + (int) sizeof(void *))
#else
#   define TCL_STUB_MAGIC	((int) 0xFCA3BACF)
#endif

/*
 * The following function is required to be defined in all stubs aware
 * extensions. The function is actually implemented in the stub library, not
 * the main Tcl library, although there is a trivial implementation in the
 * main library in case an extension is statically linked into an application.
 */

const char *		Tcl_InitStubs(Tcl_Interp *interp, const char *version,
			    int exact, int magic);
const char *		TclTomMathInitializeStubs(Tcl_Interp *interp,
			    const char *version, int epoch, int revision);
const char *		TclInitStubTable(const char *version);
void *			TclStubCall(void *arg);
#if defined(_WIN32)
    TCL_NORETURN void Tcl_ConsolePanic(const char *format, ...);
#else
#   define Tcl_ConsolePanic ((Tcl_PanicProc *)NULL)
#endif

#ifdef USE_TCL_STUBS
#if TCL_MAJOR_VERSION < 9
#   define Tcl_InitStubs(interp, version, exact) \
	(Tcl_InitStubs)(interp, version, \
	    (exact)|(TCL_MAJOR_VERSION<<8)|(0xFF<<16), \
	    TCL_STUB_MAGIC)
#else
#   define Tcl_InitStubs(interp, version, exact) \
	(Tcl_InitStubs)(interp, version, \
	    (exact)|(TCL_MAJOR_VERSION<<8)|(TCL_MINOR_VERSION<<16), \
	    TCL_STUB_MAGIC)
#endif
#else
#if TCL_MAJOR_VERSION < 9
#   define Tcl_InitStubs(interp, version, exact) \
	Tcl_Panic(((void)interp, (void)version, \
		(void)exact, "Please define -DUSE_TCL_STUBS"))
#else
#   define Tcl_InitStubs(interp, version, exact) \
	Tcl_PkgInitStubsCheck(interp, version, \
		(exact)|(TCL_MAJOR_VERSION<<8)|(TCL_MINOR_VERSION<<16))
#endif
#endif

/*
 * Public functions that are not accessible via the stubs table.
 * Tcl_GetMemoryInfo is needed for AOLserver. [Bug 1868171]
 */

#define Tcl_Main(argc, argv, proc) Tcl_MainEx(argc, argv, proc, \
	    ((Tcl_SetPanicProc(Tcl_ConsolePanic), Tcl_CreateInterp())))
EXTERN TCL_NORETURN void Tcl_MainEx(Tcl_Size argc, char **argv,
			    Tcl_AppInitProc *appInitProc, Tcl_Interp *interp);
EXTERN const char *	Tcl_PkgInitStubsCheck(Tcl_Interp *interp,
			    const char *version, int exact);
EXTERN const char *	Tcl_InitSubsystems(void);
EXTERN void		Tcl_GetMemoryInfo(Tcl_DString *dsPtr);
EXTERN const char *	Tcl_FindExecutable(const char *argv0);
EXTERN const char *	Tcl_SetPreInitScript(const char *string);
EXTERN const char *	Tcl_SetPanicProc(
			    Tcl_PanicProc *panicProc);
EXTERN void		Tcl_StaticLibrary(Tcl_Interp *interp,
			    const char *prefix,
			    Tcl_LibraryInitProc *initProc,
			    Tcl_LibraryInitProc *safeInitProc);
#ifndef TCL_NO_DEPRECATED
#   define Tcl_StaticPackage Tcl_StaticLibrary
#endif
EXTERN Tcl_ExitProc *	Tcl_SetExitProc(Tcl_ExitProc *proc);
#ifdef _WIN32
EXTERN const char *TclZipfs_AppHook(int *argc, unsigned short ***argv);
#else
EXTERN const char *TclZipfs_AppHook(int *argc, char ***argv);
#endif
#if defined(_WIN32) && defined(UNICODE)
#ifndef USE_TCL_STUBS
#   define Tcl_FindExecutable(arg) ((Tcl_FindExecutable)((const char *)(arg)))
#endif
#   define Tcl_MainEx Tcl_MainExW
    EXTERN TCL_NORETURN void Tcl_MainExW(Tcl_Size argc, unsigned short **argv,
	    Tcl_AppInitProc *appInitProc, Tcl_Interp *interp);
#endif
#if defined(USE_TCL_STUBS) && (TCL_MAJOR_VERSION > 8)
#define Tcl_SetPanicProc(panicProc) \
    TclInitStubTable(((const char *(*)(Tcl_PanicProc *))TclStubCall((void *)panicProc))(panicProc))
#define Tcl_InitSubsystems() \
    TclInitStubTable(((const char *(*)(void))TclStubCall((void *)1))())
#define Tcl_FindExecutable(argv0) \
    TclInitStubTable(((const char *(*)(const char *))TclStubCall((void *)2))(argv0))
#define TclZipfs_AppHook(argcp, argvp) \
	TclInitStubTable(((const char *(*)(int *, void *))TclStubCall((void *)3))(argcp, argvp))
#define Tcl_MainExW(argc, argv, appInitProc, interp) \
	(void)((const char *(*)(Tcl_Size, const void *, Tcl_AppInitProc *, Tcl_Interp *)) \
	TclStubCall((void *)4))(argc, argv, appInitProc, interp)
#if !defined(_WIN32) || !defined(UNICODE)
#define Tcl_MainEx(argc, argv, appInitProc, interp) \
	(void)((const char *(*)(Tcl_Size, const void *, Tcl_AppInitProc *, Tcl_Interp *)) \
	TclStubCall((void *)5))(argc, argv, appInitProc, interp)
#endif
#define Tcl_StaticLibrary(interp, pkgName, initProc, safeInitProc) \
	(void)((const char *(*)(Tcl_Interp *, const char *, Tcl_LibraryInitProc *, Tcl_LibraryInitProc *)) \
	TclStubCall((void *)6))(interp, pkgName, initProc, safeInitProc)
#define Tcl_SetExitProc(proc) \
	((Tcl_ExitProc *(*)(Tcl_ExitProc *))TclStubCall((void *)7))(proc)
#define Tcl_GetMemoryInfo(dsPtr) \
	(void)((const char *(*)(Tcl_DString *))TclStubCall((void *)8))(dsPtr)
#define Tcl_SetPreInitScript(string) \
	((const char *(*)(const char *))TclStubCall((void *)9))(string)
#endif

/*
 *----------------------------------------------------------------------------
 * Include the public function declarations that are accessible via the stubs
 * table.
 */

#include "tclDecls.h"

/*
 * Include platform specific public function declarations that are accessible
 * via the stubs table. Make all TclOO symbols MODULE_SCOPE (which only
 * has effect on building it as a shared library). See ticket [3010352].
 */

#if defined(BUILD_tcl)
#   undef TCLAPI
#   define TCLAPI MODULE_SCOPE
#endif

/*
 *----------------------------------------------------------------------------
 * The following declarations map ckalloc and ckfree to Tcl_Alloc and
 * Tcl_Free for use in Tcl-8.x-compatible extensions.
 */

#ifndef BUILD_tcl
#   define ckalloc Tcl_Alloc
#   define attemptckalloc Tcl_AttemptAlloc
#   ifdef _MSC_VER
	/* Silence invalid C4090 warnings */
#	define ckfree(a) Tcl_Free((void *)(a))
#	define ckrealloc(a,b) Tcl_Realloc((void *)(a),(b))
#	define attemptckrealloc(a,b) Tcl_AttemptRealloc((void *)(a),(b))
#   else
#	define ckfree Tcl_Free
#	define ckrealloc Tcl_Realloc
#	define attemptckrealloc Tcl_AttemptRealloc
#   endif
#endif

#ifndef TCL_MEM_DEBUG

/*
 * If we are not using the debugging allocator, we should call the Tcl_Alloc,
 * et al. routines in order to guarantee that every module is using the same
 * memory allocator both inside and outside of the Tcl library.
 */

#   undef  Tcl_InitMemory
#   define Tcl_InitMemory(x)
#   undef  Tcl_DumpActiveMemory
#   define Tcl_DumpActiveMemory(x)
#   undef  Tcl_ValidateAllMemory
#   define Tcl_ValidateAllMemory(x,y)

#endif /* !TCL_MEM_DEBUG */

#ifdef TCL_MEM_DEBUG
#   undef Tcl_IncrRefCount
#   define Tcl_IncrRefCount(objPtr) \
	Tcl_DbIncrRefCount(objPtr, __FILE__, __LINE__)
#   undef Tcl_DecrRefCount
#   define Tcl_DecrRefCount(objPtr) \
	Tcl_DbDecrRefCount(objPtr, __FILE__, __LINE__)
#   undef Tcl_IsShared
#   define Tcl_IsShared(objPtr) \
	Tcl_DbIsShared(objPtr, __FILE__, __LINE__)
/*
 * Free the Obj by effectively doing:
 *
 *   Tcl_IncrRefCount(objPtr);
 *   Tcl_DecrRefCount(objPtr);
 *
 * This will free the obj if there are no references to the obj.
 */
#   define Tcl_BounceRefCount(objPtr) \
    TclBounceRefCount(objPtr, __FILE__, __LINE__)

static inline void
TclBounceRefCount(
    Tcl_Obj* objPtr,
    const char* fn,
    int line)
{
    if (objPtr) {
	if ((objPtr)->refCount == 0) {
	    Tcl_DbDecrRefCount(objPtr, fn, line);
	}
    }
}
#else
#   undef Tcl_IncrRefCount
#   define Tcl_IncrRefCount(objPtr) \
	((void)++(objPtr)->refCount)
    /*
     * Use do/while0 idiom for optimum correctness without compiler warnings.
     * https://wiki.c2.com/?TrivialDoWhileLoop
     */
#   undef Tcl_DecrRefCount
#   define Tcl_DecrRefCount(objPtr) \
	do {								\
	    Tcl_Obj *_objPtr = (objPtr);				\
	    if (_objPtr->refCount-- <= 1) {				\
		TclFreeObj(_objPtr);					\
	    }								\
	} while(0)
#   undef Tcl_IsShared
#   define Tcl_IsShared(objPtr) \
	((objPtr)->refCount > 1)

/*
 * Declare that obj will no longer be used or referenced.
 * This will free the obj if there are no references to the obj.
 */
#   define Tcl_BounceRefCount(objPtr) \
    TclBounceRefCount(objPtr);

static inline void
TclBounceRefCount(
    Tcl_Obj* objPtr)
{
    if (objPtr) {
	if ((objPtr)->refCount == 0) {
	    Tcl_DecrRefCount(objPtr);
	}
    }
}

#endif

/*
 * Macros and definitions that help to debug the use of Tcl objects. When
 * TCL_MEM_DEBUG is defined, the Tcl_New declarations are overridden to call
 * debugging versions of the object creation functions.
 */

#ifdef TCL_MEM_DEBUG
#  undef  Tcl_NewBignumObj
#  define Tcl_NewBignumObj(val) \
     Tcl_DbNewBignumObj(val, __FILE__, __LINE__)
#  undef  Tcl_NewBooleanObj
#  define Tcl_NewBooleanObj(val) \
     Tcl_DbNewWideIntObj((val)!=0, __FILE__, __LINE__)
#  undef  Tcl_NewByteArrayObj
#  define Tcl_NewByteArrayObj(bytes, len) \
     Tcl_DbNewByteArrayObj(bytes, len, __FILE__, __LINE__)
#  undef  Tcl_NewDoubleObj
#  define Tcl_NewDoubleObj(val) \
     Tcl_DbNewDoubleObj(val, __FILE__, __LINE__)
#  undef  Tcl_NewListObj
#  define Tcl_NewListObj(objc, objv) \
     Tcl_DbNewListObj(objc, objv, __FILE__, __LINE__)
#  undef  Tcl_NewObj
#  define Tcl_NewObj() \
     Tcl_DbNewObj(__FILE__, __LINE__)
#  undef  Tcl_NewStringObj
#  define Tcl_NewStringObj(bytes, len) \
     Tcl_DbNewStringObj(bytes, len, __FILE__, __LINE__)
#  undef  Tcl_NewWideIntObj
#  define Tcl_NewWideIntObj(val) \
     Tcl_DbNewWideIntObj(val, __FILE__, __LINE__)
#endif /* TCL_MEM_DEBUG */

/*
 *----------------------------------------------------------------------------
 * Macros for clients to use to access fields of hash entries:
 */

#define Tcl_GetHashValue(h) ((h)->clientData)
#define Tcl_SetHashValue(h, value) ((h)->clientData = (void *)(value))
#define Tcl_GetHashKey(tablePtr, h) \
	((void *) (((tablePtr)->keyType == TCL_ONE_WORD_KEYS ||		\
		    (tablePtr)->keyType == TCL_CUSTOM_PTR_KEYS)		\
		? (h)->key.oneWordValue					\
		: (h)->key.string))

/*
 * Macros to use for clients to use to invoke find and create functions for
 * hash tables:
 */

#define Tcl_FindHashEntry(tablePtr, key) \
	(*((tablePtr)->findProc))(tablePtr, (const char *)(key))
#define Tcl_CreateHashEntry(tablePtr, key, newPtr) \
	(*((tablePtr)->createProc))(tablePtr, (const char *)(key), newPtr)

#endif /* RC_INVOKED */

/*
 * end block for C++
 */

#ifdef __cplusplus
}
#endif

#endif /* _TCL */

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */
Added compat/tcl-9.0/generic/tclDecls.h.

















































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































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
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
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
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
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
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
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
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
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
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
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
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
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
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
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
1210
1211
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
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
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
1320
1321
1322
1323
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
1359
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
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
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
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
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
1517
1518
1519
1520
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
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
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
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
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
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
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
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
1862
1863
1864
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
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
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
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
1994
1995
1996
1997
1998
1999
2000
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
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
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
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
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
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
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
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
2444
2445
2446
2447
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
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
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
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
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
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
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
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365
4366
4367
4368
4369
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/*
 * tclDecls.h --
 *
 *	Declarations of functions in the platform independent public Tcl API.
 *
 * Copyright (c) 1998-1999 by Scriptics Corporation.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#ifndef _TCLDECLS
#define _TCLDECLS

#include <stddef.h> /* for size_t */

#undef TCL_STORAGE_CLASS
#ifdef BUILD_tcl
#   define TCL_STORAGE_CLASS DLLEXPORT
#else
#   ifdef USE_TCL_STUBS
#      define TCL_STORAGE_CLASS
#   else
#      define TCL_STORAGE_CLASS DLLIMPORT
#   endif
#endif

#if !defined(BUILD_tcl)
# define TCL_DEPRECATED(msg) EXTERN TCL_DEPRECATED_API(msg)
#elif defined(TCL_NO_DEPRECATED)
# define TCL_DEPRECATED(msg) MODULE_SCOPE
#else
# define TCL_DEPRECATED(msg) EXTERN
#endif


/*
 * WARNING: This file is automatically generated by the tools/genStubs.tcl
 * script.  Any modifications to the function declarations below should be made
 * in the generic/tcl.decls script.
 */

/* !BEGIN!: Do not edit below this line. */

#ifdef __cplusplus
extern "C" {
#endif

/*
 * Exported function declarations:
 */

/* 0 */
EXTERN int		Tcl_PkgProvideEx(Tcl_Interp *interp,
				const char *name, const char *version,
				const void *clientData);
/* 1 */
EXTERN const char *	Tcl_PkgRequireEx(Tcl_Interp *interp,
				const char *name, const char *version,
				int exact, void *clientDataPtr);
/* 2 */
EXTERN TCL_NORETURN void Tcl_Panic(const char *format, ...) TCL_FORMAT_PRINTF(1, 2);
/* 3 */
EXTERN void *		Tcl_Alloc(TCL_HASH_TYPE size);
/* 4 */
EXTERN void		Tcl_Free(void *ptr);
/* 5 */
EXTERN void *		Tcl_Realloc(void *ptr, TCL_HASH_TYPE size);
/* 6 */
EXTERN void *		Tcl_DbCkalloc(TCL_HASH_TYPE size, const char *file,
				int line);
/* 7 */
EXTERN void		Tcl_DbCkfree(void *ptr, const char *file, int line);
/* 8 */
EXTERN void *		Tcl_DbCkrealloc(void *ptr, TCL_HASH_TYPE size,
				const char *file, int line);
/* 9 */
EXTERN void		Tcl_CreateFileHandler(int fd, int mask,
				Tcl_FileProc *proc, void *clientData);
/* 10 */
EXTERN void		Tcl_DeleteFileHandler(int fd);
/* 11 */
EXTERN void		Tcl_SetTimer(const Tcl_Time *timePtr);
/* 12 */
EXTERN void		Tcl_Sleep(int ms);
/* 13 */
EXTERN int		Tcl_WaitForEvent(const Tcl_Time *timePtr);
/* 14 */
EXTERN int		Tcl_AppendAllObjTypes(Tcl_Interp *interp,
				Tcl_Obj *objPtr);
/* 15 */
EXTERN void		Tcl_AppendStringsToObj(Tcl_Obj *objPtr, ...);
/* 16 */
EXTERN void		Tcl_AppendToObj(Tcl_Obj *objPtr, const char *bytes,
				Tcl_Size length);
/* 17 */
EXTERN Tcl_Obj *	Tcl_ConcatObj(Tcl_Size objc, Tcl_Obj *const objv[]);
/* 18 */
EXTERN int		Tcl_ConvertToType(Tcl_Interp *interp,
				Tcl_Obj *objPtr, const Tcl_ObjType *typePtr);
/* 19 */
EXTERN void		Tcl_DbDecrRefCount(Tcl_Obj *objPtr, const char *file,
				int line);
/* 20 */
EXTERN void		Tcl_DbIncrRefCount(Tcl_Obj *objPtr, const char *file,
				int line);
/* 21 */
EXTERN int		Tcl_DbIsShared(Tcl_Obj *objPtr, const char *file,
				int line);
/* Slot 22 is reserved */
/* 23 */
EXTERN Tcl_Obj *	Tcl_DbNewByteArrayObj(const unsigned char *bytes,
				Tcl_Size numBytes, const char *file,
				int line);
/* 24 */
EXTERN Tcl_Obj *	Tcl_DbNewDoubleObj(double doubleValue,
				const char *file, int line);
/* 25 */
EXTERN Tcl_Obj *	Tcl_DbNewListObj(Tcl_Size objc, Tcl_Obj *const *objv,
				const char *file, int line);
/* Slot 26 is reserved */
/* 27 */
EXTERN Tcl_Obj *	Tcl_DbNewObj(const char *file, int line);
/* 28 */
EXTERN Tcl_Obj *	Tcl_DbNewStringObj(const char *bytes,
				Tcl_Size length, const char *file, int line);
/* 29 */
EXTERN Tcl_Obj *	Tcl_DuplicateObj(Tcl_Obj *objPtr);
/* 30 */
EXTERN void		TclFreeObj(Tcl_Obj *objPtr);
/* 31 */
EXTERN int		Tcl_GetBoolean(Tcl_Interp *interp, const char *src,
				int *intPtr);
/* 32 */
EXTERN int		Tcl_GetBooleanFromObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, int *intPtr);
/* 33 */
EXTERN unsigned char *	Tcl_GetByteArrayFromObj(Tcl_Obj *objPtr,
				Tcl_Size *numBytesPtr);
/* 34 */
EXTERN int		Tcl_GetDouble(Tcl_Interp *interp, const char *src,
				double *doublePtr);
/* 35 */
EXTERN int		Tcl_GetDoubleFromObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, double *doublePtr);
/* Slot 36 is reserved */
/* 37 */
EXTERN int		Tcl_GetInt(Tcl_Interp *interp, const char *src,
				int *intPtr);
/* 38 */
EXTERN int		Tcl_GetIntFromObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, int *intPtr);
/* 39 */
EXTERN int		Tcl_GetLongFromObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, long *longPtr);
/* 40 */
EXTERN const Tcl_ObjType * Tcl_GetObjType(const char *typeName);
/* 41 */
EXTERN char *		TclGetStringFromObj(Tcl_Obj *objPtr, void *lengthPtr);
/* 42 */
EXTERN void		Tcl_InvalidateStringRep(Tcl_Obj *objPtr);
/* 43 */
EXTERN int		Tcl_ListObjAppendList(Tcl_Interp *interp,
				Tcl_Obj *listPtr, Tcl_Obj *elemListPtr);
/* 44 */
EXTERN int		Tcl_ListObjAppendElement(Tcl_Interp *interp,
				Tcl_Obj *listPtr, Tcl_Obj *objPtr);
/* 45 */
EXTERN int		TclListObjGetElements(Tcl_Interp *interp,
				Tcl_Obj *listPtr, void *objcPtr,
				Tcl_Obj ***objvPtr);
/* 46 */
EXTERN int		Tcl_ListObjIndex(Tcl_Interp *interp,
				Tcl_Obj *listPtr, Tcl_Size index,
				Tcl_Obj **objPtrPtr);
/* 47 */
EXTERN int		TclListObjLength(Tcl_Interp *interp,
				Tcl_Obj *listPtr, void *lengthPtr);
/* 48 */
EXTERN int		Tcl_ListObjReplace(Tcl_Interp *interp,
				Tcl_Obj *listPtr, Tcl_Size first,
				Tcl_Size count, Tcl_Size objc,
				Tcl_Obj *const objv[]);
/* Slot 49 is reserved */
/* 50 */
EXTERN Tcl_Obj *	Tcl_NewByteArrayObj(const unsigned char *bytes,
				Tcl_Size numBytes);
/* 51 */
EXTERN Tcl_Obj *	Tcl_NewDoubleObj(double doubleValue);
/* Slot 52 is reserved */
/* 53 */
EXTERN Tcl_Obj *	Tcl_NewListObj(Tcl_Size objc, Tcl_Obj *const objv[]);
/* Slot 54 is reserved */
/* 55 */
EXTERN Tcl_Obj *	Tcl_NewObj(void);
/* 56 */
EXTERN Tcl_Obj *	Tcl_NewStringObj(const char *bytes, Tcl_Size length);
/* Slot 57 is reserved */
/* 58 */
EXTERN unsigned char *	Tcl_SetByteArrayLength(Tcl_Obj *objPtr,
				Tcl_Size numBytes);
/* 59 */
EXTERN void		Tcl_SetByteArrayObj(Tcl_Obj *objPtr,
				const unsigned char *bytes,
				Tcl_Size numBytes);
/* 60 */
EXTERN void		Tcl_SetDoubleObj(Tcl_Obj *objPtr, double doubleValue);
/* Slot 61 is reserved */
/* 62 */
EXTERN void		Tcl_SetListObj(Tcl_Obj *objPtr, Tcl_Size objc,
				Tcl_Obj *const objv[]);
/* Slot 63 is reserved */
/* 64 */
EXTERN void		Tcl_SetObjLength(Tcl_Obj *objPtr, Tcl_Size length);
/* 65 */
EXTERN void		Tcl_SetStringObj(Tcl_Obj *objPtr, const char *bytes,
				Tcl_Size length);
/* Slot 66 is reserved */
/* Slot 67 is reserved */
/* 68 */
EXTERN void		Tcl_AllowExceptions(Tcl_Interp *interp);
/* 69 */
EXTERN void		Tcl_AppendElement(Tcl_Interp *interp,
				const char *element);
/* 70 */
EXTERN void		Tcl_AppendResult(Tcl_Interp *interp, ...);
/* 71 */
EXTERN Tcl_AsyncHandler	 Tcl_AsyncCreate(Tcl_AsyncProc *proc,
				void *clientData);
/* 72 */
EXTERN void		Tcl_AsyncDelete(Tcl_AsyncHandler async);
/* 73 */
EXTERN int		Tcl_AsyncInvoke(Tcl_Interp *interp, int code);
/* 74 */
EXTERN void		Tcl_AsyncMark(Tcl_AsyncHandler async);
/* 75 */
EXTERN int		Tcl_AsyncReady(void);
/* Slot 76 is reserved */
/* Slot 77 is reserved */
/* 78 */
EXTERN int		Tcl_BadChannelOption(Tcl_Interp *interp,
				const char *optionName,
				const char *optionList);
/* 79 */
EXTERN void		Tcl_CallWhenDeleted(Tcl_Interp *interp,
				Tcl_InterpDeleteProc *proc, void *clientData);
/* 80 */
EXTERN void		Tcl_CancelIdleCall(Tcl_IdleProc *idleProc,
				void *clientData);
/* 81 */
EXTERN int		Tcl_Close(Tcl_Interp *interp, Tcl_Channel chan);
/* 82 */
EXTERN int		Tcl_CommandComplete(const char *cmd);
/* 83 */
EXTERN char *		Tcl_Concat(Tcl_Size argc, const char *const *argv);
/* 84 */
EXTERN Tcl_Size		Tcl_ConvertElement(const char *src, char *dst,
				int flags);
/* 85 */
EXTERN Tcl_Size		Tcl_ConvertCountedElement(const char *src,
				Tcl_Size length, char *dst, int flags);
/* 86 */
EXTERN int		Tcl_CreateAlias(Tcl_Interp *childInterp,
				const char *childCmd, Tcl_Interp *target,
				const char *targetCmd, Tcl_Size argc,
				const char *const *argv);
/* 87 */
EXTERN int		Tcl_CreateAliasObj(Tcl_Interp *childInterp,
				const char *childCmd, Tcl_Interp *target,
				const char *targetCmd, Tcl_Size objc,
				Tcl_Obj *const objv[]);
/* 88 */
EXTERN Tcl_Channel	Tcl_CreateChannel(const Tcl_ChannelType *typePtr,
				const char *chanName, void *instanceData,
				int mask);
/* 89 */
EXTERN void		Tcl_CreateChannelHandler(Tcl_Channel chan, int mask,
				Tcl_ChannelProc *proc, void *clientData);
/* 90 */
EXTERN void		Tcl_CreateCloseHandler(Tcl_Channel chan,
				Tcl_CloseProc *proc, void *clientData);
/* 91 */
EXTERN Tcl_Command	Tcl_CreateCommand(Tcl_Interp *interp,
				const char *cmdName, Tcl_CmdProc *proc,
				void *clientData,
				Tcl_CmdDeleteProc *deleteProc);
/* 92 */
EXTERN void		Tcl_CreateEventSource(Tcl_EventSetupProc *setupProc,
				Tcl_EventCheckProc *checkProc,
				void *clientData);
/* 93 */
EXTERN void		Tcl_CreateExitHandler(Tcl_ExitProc *proc,
				void *clientData);
/* 94 */
EXTERN Tcl_Interp *	Tcl_CreateInterp(void);
/* Slot 95 is reserved */
/* 96 */
EXTERN Tcl_Command	Tcl_CreateObjCommand(Tcl_Interp *interp,
				const char *cmdName, Tcl_ObjCmdProc *proc,
				void *clientData,
				Tcl_CmdDeleteProc *deleteProc);
/* 97 */
EXTERN Tcl_Interp *	Tcl_CreateChild(Tcl_Interp *interp, const char *name,
				int isSafe);
/* 98 */
EXTERN Tcl_TimerToken	Tcl_CreateTimerHandler(int milliseconds,
				Tcl_TimerProc *proc, void *clientData);
/* 99 */
EXTERN Tcl_Trace	Tcl_CreateTrace(Tcl_Interp *interp, Tcl_Size level,
				Tcl_CmdTraceProc *proc, void *clientData);
/* 100 */
EXTERN void		Tcl_DeleteAssocData(Tcl_Interp *interp,
				const char *name);
/* 101 */
EXTERN void		Tcl_DeleteChannelHandler(Tcl_Channel chan,
				Tcl_ChannelProc *proc, void *clientData);
/* 102 */
EXTERN void		Tcl_DeleteCloseHandler(Tcl_Channel chan,
				Tcl_CloseProc *proc, void *clientData);
/* 103 */
EXTERN int		Tcl_DeleteCommand(Tcl_Interp *interp,
				const char *cmdName);
/* 104 */
EXTERN int		Tcl_DeleteCommandFromToken(Tcl_Interp *interp,
				Tcl_Command command);
/* 105 */
EXTERN void		Tcl_DeleteEvents(Tcl_EventDeleteProc *proc,
				void *clientData);
/* 106 */
EXTERN void		Tcl_DeleteEventSource(Tcl_EventSetupProc *setupProc,
				Tcl_EventCheckProc *checkProc,
				void *clientData);
/* 107 */
EXTERN void		Tcl_DeleteExitHandler(Tcl_ExitProc *proc,
				void *clientData);
/* 108 */
EXTERN void		Tcl_DeleteHashEntry(Tcl_HashEntry *entryPtr);
/* 109 */
EXTERN void		Tcl_DeleteHashTable(Tcl_HashTable *tablePtr);
/* 110 */
EXTERN void		Tcl_DeleteInterp(Tcl_Interp *interp);
/* 111 */
EXTERN void		Tcl_DetachPids(Tcl_Size numPids, Tcl_Pid *pidPtr);
/* 112 */
EXTERN void		Tcl_DeleteTimerHandler(Tcl_TimerToken token);
/* 113 */
EXTERN void		Tcl_DeleteTrace(Tcl_Interp *interp, Tcl_Trace trace);
/* 114 */
EXTERN void		Tcl_DontCallWhenDeleted(Tcl_Interp *interp,
				Tcl_InterpDeleteProc *proc, void *clientData);
/* 115 */
EXTERN int		Tcl_DoOneEvent(int flags);
/* 116 */
EXTERN void		Tcl_DoWhenIdle(Tcl_IdleProc *proc, void *clientData);
/* 117 */
EXTERN char *		Tcl_DStringAppend(Tcl_DString *dsPtr,
				const char *bytes, Tcl_Size length);
/* 118 */
EXTERN char *		Tcl_DStringAppendElement(Tcl_DString *dsPtr,
				const char *element);
/* 119 */
EXTERN void		Tcl_DStringEndSublist(Tcl_DString *dsPtr);
/* 120 */
EXTERN void		Tcl_DStringFree(Tcl_DString *dsPtr);
/* 121 */
EXTERN void		Tcl_DStringGetResult(Tcl_Interp *interp,
				Tcl_DString *dsPtr);
/* 122 */
EXTERN void		Tcl_DStringInit(Tcl_DString *dsPtr);
/* 123 */
EXTERN void		Tcl_DStringResult(Tcl_Interp *interp,
				Tcl_DString *dsPtr);
/* 124 */
EXTERN void		Tcl_DStringSetLength(Tcl_DString *dsPtr,
				Tcl_Size length);
/* 125 */
EXTERN void		Tcl_DStringStartSublist(Tcl_DString *dsPtr);
/* 126 */
EXTERN int		Tcl_Eof(Tcl_Channel chan);
/* 127 */
EXTERN const char *	Tcl_ErrnoId(void);
/* 128 */
EXTERN const char *	Tcl_ErrnoMsg(int err);
/* Slot 129 is reserved */
/* 130 */
EXTERN int		Tcl_EvalFile(Tcl_Interp *interp,
				const char *fileName);
/* Slot 131 is reserved */
/* 132 */
EXTERN void		Tcl_EventuallyFree(void *clientData,
				Tcl_FreeProc *freeProc);
/* 133 */
EXTERN TCL_NORETURN void Tcl_Exit(int status);
/* 134 */
EXTERN int		Tcl_ExposeCommand(Tcl_Interp *interp,
				const char *hiddenCmdToken,
				const char *cmdName);
/* 135 */
EXTERN int		Tcl_ExprBoolean(Tcl_Interp *interp, const char *expr,
				int *ptr);
/* 136 */
EXTERN int		Tcl_ExprBooleanObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, int *ptr);
/* 137 */
EXTERN int		Tcl_ExprDouble(Tcl_Interp *interp, const char *expr,
				double *ptr);
/* 138 */
EXTERN int		Tcl_ExprDoubleObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, double *ptr);
/* 139 */
EXTERN int		Tcl_ExprLong(Tcl_Interp *interp, const char *expr,
				long *ptr);
/* 140 */
EXTERN int		Tcl_ExprLongObj(Tcl_Interp *interp, Tcl_Obj *objPtr,
				long *ptr);
/* 141 */
EXTERN int		Tcl_ExprObj(Tcl_Interp *interp, Tcl_Obj *objPtr,
				Tcl_Obj **resultPtrPtr);
/* 142 */
EXTERN int		Tcl_ExprString(Tcl_Interp *interp, const char *expr);
/* 143 */
EXTERN void		Tcl_Finalize(void);
/* Slot 144 is reserved */
/* 145 */
EXTERN Tcl_HashEntry *	Tcl_FirstHashEntry(Tcl_HashTable *tablePtr,
				Tcl_HashSearch *searchPtr);
/* 146 */
EXTERN int		Tcl_Flush(Tcl_Channel chan);
/* Slot 147 is reserved */
/* Slot 148 is reserved */
/* 149 */
EXTERN int		TclGetAliasObj(Tcl_Interp *interp,
				const char *childCmd,
				Tcl_Interp **targetInterpPtr,
				const char **targetCmdPtr, int *objcPtr,
				Tcl_Obj ***objvPtr);
/* 150 */
EXTERN void *		Tcl_GetAssocData(Tcl_Interp *interp,
				const char *name,
				Tcl_InterpDeleteProc **procPtr);
/* 151 */
EXTERN Tcl_Channel	Tcl_GetChannel(Tcl_Interp *interp,
				const char *chanName, int *modePtr);
/* 152 */
EXTERN Tcl_Size		Tcl_GetChannelBufferSize(Tcl_Channel chan);
/* 153 */
EXTERN int		Tcl_GetChannelHandle(Tcl_Channel chan, int direction,
				void **handlePtr);
/* 154 */
EXTERN void *		Tcl_GetChannelInstanceData(Tcl_Channel chan);
/* 155 */
EXTERN int		Tcl_GetChannelMode(Tcl_Channel chan);
/* 156 */
EXTERN const char *	Tcl_GetChannelName(Tcl_Channel chan);
/* 157 */
EXTERN int		Tcl_GetChannelOption(Tcl_Interp *interp,
				Tcl_Channel chan, const char *optionName,
				Tcl_DString *dsPtr);
/* 158 */
EXTERN const Tcl_ChannelType * Tcl_GetChannelType(Tcl_Channel chan);
/* 159 */
EXTERN int		Tcl_GetCommandInfo(Tcl_Interp *interp,
				const char *cmdName, Tcl_CmdInfo *infoPtr);
/* 160 */
EXTERN const char *	Tcl_GetCommandName(Tcl_Interp *interp,
				Tcl_Command command);
/* 161 */
EXTERN int		Tcl_GetErrno(void);
/* 162 */
EXTERN const char *	Tcl_GetHostName(void);
/* 163 */
EXTERN int		Tcl_GetInterpPath(Tcl_Interp *interp,
				Tcl_Interp *childInterp);
/* 164 */
EXTERN Tcl_Interp *	Tcl_GetParent(Tcl_Interp *interp);
/* 165 */
EXTERN const char *	Tcl_GetNameOfExecutable(void);
/* 166 */
EXTERN Tcl_Obj *	Tcl_GetObjResult(Tcl_Interp *interp);
/* 167 */
EXTERN int		Tcl_GetOpenFile(Tcl_Interp *interp,
				const char *chanID, int forWriting,
				int checkUsage, void **filePtr);
/* 168 */
EXTERN Tcl_PathType	Tcl_GetPathType(const char *path);
/* 169 */
EXTERN Tcl_Size		Tcl_Gets(Tcl_Channel chan, Tcl_DString *dsPtr);
/* 170 */
EXTERN Tcl_Size		Tcl_GetsObj(Tcl_Channel chan, Tcl_Obj *objPtr);
/* 171 */
EXTERN int		Tcl_GetServiceMode(void);
/* 172 */
EXTERN Tcl_Interp *	Tcl_GetChild(Tcl_Interp *interp, const char *name);
/* 173 */
EXTERN Tcl_Channel	Tcl_GetStdChannel(int type);
/* Slot 174 is reserved */
/* Slot 175 is reserved */
/* 176 */
EXTERN const char *	Tcl_GetVar2(Tcl_Interp *interp, const char *part1,
				const char *part2, int flags);
/* Slot 177 is reserved */
/* Slot 178 is reserved */
/* 179 */
EXTERN int		Tcl_HideCommand(Tcl_Interp *interp,
				const char *cmdName,
				const char *hiddenCmdToken);
/* 180 */
EXTERN int		Tcl_Init(Tcl_Interp *interp);
/* 181 */
EXTERN void		Tcl_InitHashTable(Tcl_HashTable *tablePtr,
				int keyType);
/* 182 */
EXTERN int		Tcl_InputBlocked(Tcl_Channel chan);
/* 183 */
EXTERN int		Tcl_InputBuffered(Tcl_Channel chan);
/* 184 */
EXTERN int		Tcl_InterpDeleted(Tcl_Interp *interp);
/* 185 */
EXTERN int		Tcl_IsSafe(Tcl_Interp *interp);
/* 186 */
EXTERN char *		Tcl_JoinPath(Tcl_Size argc, const char *const *argv,
				Tcl_DString *resultPtr);
/* 187 */
EXTERN int		Tcl_LinkVar(Tcl_Interp *interp, const char *varName,
				void *addr, int type);
/* Slot 188 is reserved */
/* 189 */
EXTERN Tcl_Channel	Tcl_MakeFileChannel(void *handle, int mode);
/* Slot 190 is reserved */
/* 191 */
EXTERN Tcl_Channel	Tcl_MakeTcpClientChannel(void *tcpSocket);
/* 192 */
EXTERN char *		Tcl_Merge(Tcl_Size argc, const char *const *argv);
/* 193 */
EXTERN Tcl_HashEntry *	Tcl_NextHashEntry(Tcl_HashSearch *searchPtr);
/* 194 */
EXTERN void		Tcl_NotifyChannel(Tcl_Channel channel, int mask);
/* 195 */
EXTERN Tcl_Obj *	Tcl_ObjGetVar2(Tcl_Interp *interp, Tcl_Obj *part1Ptr,
				Tcl_Obj *part2Ptr, int flags);
/* 196 */
EXTERN Tcl_Obj *	Tcl_ObjSetVar2(Tcl_Interp *interp, Tcl_Obj *part1Ptr,
				Tcl_Obj *part2Ptr, Tcl_Obj *newValuePtr,
				int flags);
/* 197 */
EXTERN Tcl_Channel	Tcl_OpenCommandChannel(Tcl_Interp *interp,
				Tcl_Size argc, const char **argv, int flags);
/* 198 */
EXTERN Tcl_Channel	Tcl_OpenFileChannel(Tcl_Interp *interp,
				const char *fileName, const char *modeString,
				int permissions);
/* 199 */
EXTERN Tcl_Channel	Tcl_OpenTcpClient(Tcl_Interp *interp, int port,
				const char *address, const char *myaddr,
				int myport, int flags);
/* 200 */
EXTERN Tcl_Channel	Tcl_OpenTcpServer(Tcl_Interp *interp, int port,
				const char *host,
				Tcl_TcpAcceptProc *acceptProc,
				void *callbackData);
/* 201 */
EXTERN void		Tcl_Preserve(void *data);
/* 202 */
EXTERN void		Tcl_PrintDouble(Tcl_Interp *interp, double value,
				char *dst);
/* 203 */
EXTERN int		Tcl_PutEnv(const char *assignment);
/* 204 */
EXTERN const char *	Tcl_PosixError(Tcl_Interp *interp);
/* 205 */
EXTERN void		Tcl_QueueEvent(Tcl_Event *evPtr, int position);
/* 206 */
EXTERN Tcl_Size		Tcl_Read(Tcl_Channel chan, char *bufPtr,
				Tcl_Size toRead);
/* 207 */
EXTERN void		Tcl_ReapDetachedProcs(void);
/* 208 */
EXTERN int		Tcl_RecordAndEval(Tcl_Interp *interp,
				const char *cmd, int flags);
/* 209 */
EXTERN int		Tcl_RecordAndEvalObj(Tcl_Interp *interp,
				Tcl_Obj *cmdPtr, int flags);
/* 210 */
EXTERN void		Tcl_RegisterChannel(Tcl_Interp *interp,
				Tcl_Channel chan);
/* 211 */
EXTERN void		Tcl_RegisterObjType(const Tcl_ObjType *typePtr);
/* 212 */
EXTERN Tcl_RegExp	Tcl_RegExpCompile(Tcl_Interp *interp,
				const char *pattern);
/* 213 */
EXTERN int		Tcl_RegExpExec(Tcl_Interp *interp, Tcl_RegExp regexp,
				const char *text, const char *start);
/* 214 */
EXTERN int		Tcl_RegExpMatch(Tcl_Interp *interp, const char *text,
				const char *pattern);
/* 215 */
EXTERN void		Tcl_RegExpRange(Tcl_RegExp regexp, Tcl_Size index,
				const char **startPtr, const char **endPtr);
/* 216 */
EXTERN void		Tcl_Release(void *clientData);
/* 217 */
EXTERN void		Tcl_ResetResult(Tcl_Interp *interp);
/* 218 */
EXTERN Tcl_Size		Tcl_ScanElement(const char *src, int *flagPtr);
/* 219 */
EXTERN Tcl_Size		Tcl_ScanCountedElement(const char *src,
				Tcl_Size length, int *flagPtr);
/* Slot 220 is reserved */
/* 221 */
EXTERN int		Tcl_ServiceAll(void);
/* 222 */
EXTERN int		Tcl_ServiceEvent(int flags);
/* 223 */
EXTERN void		Tcl_SetAssocData(Tcl_Interp *interp,
				const char *name, Tcl_InterpDeleteProc *proc,
				void *clientData);
/* 224 */
EXTERN void		Tcl_SetChannelBufferSize(Tcl_Channel chan,
				Tcl_Size sz);
/* 225 */
EXTERN int		Tcl_SetChannelOption(Tcl_Interp *interp,
				Tcl_Channel chan, const char *optionName,
				const char *newValue);
/* 226 */
EXTERN int		Tcl_SetCommandInfo(Tcl_Interp *interp,
				const char *cmdName,
				const Tcl_CmdInfo *infoPtr);
/* 227 */
EXTERN void		Tcl_SetErrno(int err);
/* 228 */
EXTERN void		Tcl_SetErrorCode(Tcl_Interp *interp, ...);
/* 229 */
EXTERN void		Tcl_SetMaxBlockTime(const Tcl_Time *timePtr);
/* Slot 230 is reserved */
/* 231 */
EXTERN Tcl_Size		Tcl_SetRecursionLimit(Tcl_Interp *interp,
				Tcl_Size depth);
/* Slot 232 is reserved */
/* 233 */
EXTERN int		Tcl_SetServiceMode(int mode);
/* 234 */
EXTERN void		Tcl_SetObjErrorCode(Tcl_Interp *interp,
				Tcl_Obj *errorObjPtr);
/* 235 */
EXTERN void		Tcl_SetObjResult(Tcl_Interp *interp,
				Tcl_Obj *resultObjPtr);
/* 236 */
EXTERN void		Tcl_SetStdChannel(Tcl_Channel channel, int type);
/* Slot 237 is reserved */
/* 238 */
EXTERN const char *	Tcl_SetVar2(Tcl_Interp *interp, const char *part1,
				const char *part2, const char *newValue,
				int flags);
/* 239 */
EXTERN const char *	Tcl_SignalId(int sig);
/* 240 */
EXTERN const char *	Tcl_SignalMsg(int sig);
/* 241 */
EXTERN void		Tcl_SourceRCFile(Tcl_Interp *interp);
/* 242 */
EXTERN int		TclSplitList(Tcl_Interp *interp, const char *listStr,
				void *argcPtr, const char ***argvPtr);
/* 243 */
EXTERN void		TclSplitPath(const char *path, void *argcPtr,
				const char ***argvPtr);
/* Slot 244 is reserved */
/* Slot 245 is reserved */
/* Slot 246 is reserved */
/* Slot 247 is reserved */
/* 248 */
EXTERN int		Tcl_TraceVar2(Tcl_Interp *interp, const char *part1,
				const char *part2, int flags,
				Tcl_VarTraceProc *proc, void *clientData);
/* 249 */
EXTERN char *		Tcl_TranslateFileName(Tcl_Interp *interp,
				const char *name, Tcl_DString *bufferPtr);
/* 250 */
EXTERN Tcl_Size		Tcl_Ungets(Tcl_Channel chan, const char *str,
				Tcl_Size len, int atHead);
/* 251 */
EXTERN void		Tcl_UnlinkVar(Tcl_Interp *interp,
				const char *varName);
/* 252 */
EXTERN int		Tcl_UnregisterChannel(Tcl_Interp *interp,
				Tcl_Channel chan);
/* Slot 253 is reserved */
/* 254 */
EXTERN int		Tcl_UnsetVar2(Tcl_Interp *interp, const char *part1,
				const char *part2, int flags);
/* Slot 255 is reserved */
/* 256 */
EXTERN void		Tcl_UntraceVar2(Tcl_Interp *interp,
				const char *part1, const char *part2,
				int flags, Tcl_VarTraceProc *proc,
				void *clientData);
/* 257 */
EXTERN void		Tcl_UpdateLinkedVar(Tcl_Interp *interp,
				const char *varName);
/* Slot 258 is reserved */
/* 259 */
EXTERN int		Tcl_UpVar2(Tcl_Interp *interp, const char *frameName,
				const char *part1, const char *part2,
				const char *localName, int flags);
/* 260 */
EXTERN int		Tcl_VarEval(Tcl_Interp *interp, ...);
/* Slot 261 is reserved */
/* 262 */
EXTERN void *		Tcl_VarTraceInfo2(Tcl_Interp *interp,
				const char *part1, const char *part2,
				int flags, Tcl_VarTraceProc *procPtr,
				void *prevClientData);
/* 263 */
EXTERN Tcl_Size		Tcl_Write(Tcl_Channel chan, const char *s,
				Tcl_Size slen);
/* 264 */
EXTERN void		Tcl_WrongNumArgs(Tcl_Interp *interp, Tcl_Size objc,
				Tcl_Obj *const objv[], const char *message);
/* 265 */
EXTERN int		Tcl_DumpActiveMemory(const char *fileName);
/* 266 */
EXTERN void		Tcl_ValidateAllMemory(const char *file, int line);
/* Slot 267 is reserved */
/* Slot 268 is reserved */
/* 269 */
EXTERN char *		Tcl_HashStats(Tcl_HashTable *tablePtr);
/* 270 */
EXTERN const char *	Tcl_ParseVar(Tcl_Interp *interp, const char *start,
				const char **termPtr);
/* Slot 271 is reserved */
/* 272 */
EXTERN const char *	Tcl_PkgPresentEx(Tcl_Interp *interp,
				const char *name, const char *version,
				int exact, void *clientDataPtr);
/* Slot 273 is reserved */
/* Slot 274 is reserved */
/* Slot 275 is reserved */
/* Slot 276 is reserved */
/* 277 */
EXTERN Tcl_Pid		Tcl_WaitPid(Tcl_Pid pid, int *statPtr, int options);
/* Slot 278 is reserved */
/* 279 */
EXTERN void		Tcl_GetVersion(int *major, int *minor,
				int *patchLevel, int *type);
/* 280 */
EXTERN void		Tcl_InitMemory(Tcl_Interp *interp);
/* 281 */
EXTERN Tcl_Channel	Tcl_StackChannel(Tcl_Interp *interp,
				const Tcl_ChannelType *typePtr,
				void *instanceData, int mask,
				Tcl_Channel prevChan);
/* 282 */
EXTERN int		Tcl_UnstackChannel(Tcl_Interp *interp,
				Tcl_Channel chan);
/* 283 */
EXTERN Tcl_Channel	Tcl_GetStackedChannel(Tcl_Channel chan);
/* 284 */
EXTERN void		Tcl_SetMainLoop(Tcl_MainLoopProc *proc);
/* 285 */
EXTERN int		Tcl_GetAliasObj(Tcl_Interp *interp,
				const char *childCmd,
				Tcl_Interp **targetInterpPtr,
				const char **targetCmdPtr, Tcl_Size *objcPtr,
				Tcl_Obj ***objvPtr);
/* 286 */
EXTERN void		Tcl_AppendObjToObj(Tcl_Obj *objPtr,
				Tcl_Obj *appendObjPtr);
/* 287 */
EXTERN Tcl_Encoding	Tcl_CreateEncoding(const Tcl_EncodingType *typePtr);
/* 288 */
EXTERN void		Tcl_CreateThreadExitHandler(Tcl_ExitProc *proc,
				void *clientData);
/* 289 */
EXTERN void		Tcl_DeleteThreadExitHandler(Tcl_ExitProc *proc,
				void *clientData);
/* Slot 290 is reserved */
/* 291 */
EXTERN int		Tcl_EvalEx(Tcl_Interp *interp, const char *script,
				Tcl_Size numBytes, int flags);
/* 292 */
EXTERN int		Tcl_EvalObjv(Tcl_Interp *interp, Tcl_Size objc,
				Tcl_Obj *const objv[], int flags);
/* 293 */
EXTERN int		Tcl_EvalObjEx(Tcl_Interp *interp, Tcl_Obj *objPtr,
				int flags);
/* 294 */
EXTERN TCL_NORETURN void Tcl_ExitThread(int status);
/* 295 */
EXTERN int		Tcl_ExternalToUtf(Tcl_Interp *interp,
				Tcl_Encoding encoding, const char *src,
				Tcl_Size srcLen, int flags,
				Tcl_EncodingState *statePtr, char *dst,
				Tcl_Size dstLen, int *srcReadPtr,
				int *dstWrotePtr, int *dstCharsPtr);
/* 296 */
EXTERN char *		Tcl_ExternalToUtfDString(Tcl_Encoding encoding,
				const char *src, Tcl_Size srcLen,
				Tcl_DString *dsPtr);
/* 297 */
EXTERN void		Tcl_FinalizeThread(void);
/* 298 */
EXTERN void		Tcl_FinalizeNotifier(void *clientData);
/* 299 */
EXTERN void		Tcl_FreeEncoding(Tcl_Encoding encoding);
/* 300 */
EXTERN Tcl_ThreadId	Tcl_GetCurrentThread(void);
/* 301 */
EXTERN Tcl_Encoding	Tcl_GetEncoding(Tcl_Interp *interp, const char *name);
/* 302 */
EXTERN const char *	Tcl_GetEncodingName(Tcl_Encoding encoding);
/* 303 */
EXTERN void		Tcl_GetEncodingNames(Tcl_Interp *interp);
/* 304 */
EXTERN int		Tcl_GetIndexFromObjStruct(Tcl_Interp *interp,
				Tcl_Obj *objPtr, const void *tablePtr,
				Tcl_Size offset, const char *msg, int flags,
				void *indexPtr);
/* 305 */
EXTERN void *		Tcl_GetThreadData(Tcl_ThreadDataKey *keyPtr,
				Tcl_Size size);
/* 306 */
EXTERN Tcl_Obj *	Tcl_GetVar2Ex(Tcl_Interp *interp, const char *part1,
				const char *part2, int flags);
/* 307 */
EXTERN void *		Tcl_InitNotifier(void);
/* 308 */
EXTERN void		Tcl_MutexLock(Tcl_Mutex *mutexPtr);
/* 309 */
EXTERN void		Tcl_MutexUnlock(Tcl_Mutex *mutexPtr);
/* 310 */
EXTERN void		Tcl_ConditionNotify(Tcl_Condition *condPtr);
/* 311 */
EXTERN void		Tcl_ConditionWait(Tcl_Condition *condPtr,
				Tcl_Mutex *mutexPtr, const Tcl_Time *timePtr);
/* 312 */
EXTERN Tcl_Size		TclNumUtfChars(const char *src, Tcl_Size length);
/* 313 */
EXTERN Tcl_Size		Tcl_ReadChars(Tcl_Channel channel, Tcl_Obj *objPtr,
				Tcl_Size charsToRead, int appendFlag);
/* Slot 314 is reserved */
/* Slot 315 is reserved */
/* 316 */
EXTERN int		Tcl_SetSystemEncoding(Tcl_Interp *interp,
				const char *name);
/* 317 */
EXTERN Tcl_Obj *	Tcl_SetVar2Ex(Tcl_Interp *interp, const char *part1,
				const char *part2, Tcl_Obj *newValuePtr,
				int flags);
/* 318 */
EXTERN void		Tcl_ThreadAlert(Tcl_ThreadId threadId);
/* 319 */
EXTERN void		Tcl_ThreadQueueEvent(Tcl_ThreadId threadId,
				Tcl_Event *evPtr, int position);
/* 320 */
EXTERN int		Tcl_UniCharAtIndex(const char *src, Tcl_Size index);
/* 321 */
EXTERN int		Tcl_UniCharToLower(int ch);
/* 322 */
EXTERN int		Tcl_UniCharToTitle(int ch);
/* 323 */
EXTERN int		Tcl_UniCharToUpper(int ch);
/* 324 */
EXTERN Tcl_Size		Tcl_UniCharToUtf(int ch, char *buf);
/* 325 */
EXTERN const char *	TclUtfAtIndex(const char *src, Tcl_Size index);
/* 326 */
EXTERN int		TclUtfCharComplete(const char *src, Tcl_Size length);
/* 327 */
EXTERN Tcl_Size		Tcl_UtfBackslash(const char *src, int *readPtr,
				char *dst);
/* 328 */
EXTERN const char *	Tcl_UtfFindFirst(const char *src, int ch);
/* 329 */
EXTERN const char *	Tcl_UtfFindLast(const char *src, int ch);
/* 330 */
EXTERN const char *	TclUtfNext(const char *src);
/* 331 */
EXTERN const char *	TclUtfPrev(const char *src, const char *start);
/* 332 */
EXTERN int		Tcl_UtfToExternal(Tcl_Interp *interp,
				Tcl_Encoding encoding, const char *src,
				Tcl_Size srcLen, int flags,
				Tcl_EncodingState *statePtr, char *dst,
				Tcl_Size dstLen, int *srcReadPtr,
				int *dstWrotePtr, int *dstCharsPtr);
/* 333 */
EXTERN char *		Tcl_UtfToExternalDString(Tcl_Encoding encoding,
				const char *src, Tcl_Size srcLen,
				Tcl_DString *dsPtr);
/* 334 */
EXTERN Tcl_Size		Tcl_UtfToLower(char *src);
/* 335 */
EXTERN Tcl_Size		Tcl_UtfToTitle(char *src);
/* 336 */
EXTERN Tcl_Size		Tcl_UtfToChar16(const char *src,
				unsigned short *chPtr);
/* 337 */
EXTERN Tcl_Size		Tcl_UtfToUpper(char *src);
/* 338 */
EXTERN Tcl_Size		Tcl_WriteChars(Tcl_Channel chan, const char *src,
				Tcl_Size srcLen);
/* 339 */
EXTERN Tcl_Size		Tcl_WriteObj(Tcl_Channel chan, Tcl_Obj *objPtr);
/* 340 */
EXTERN char *		Tcl_GetString(Tcl_Obj *objPtr);
/* Slot 341 is reserved */
/* Slot 342 is reserved */
/* 343 */
EXTERN void		Tcl_AlertNotifier(void *clientData);
/* 344 */
EXTERN void		Tcl_ServiceModeHook(int mode);
/* 345 */
EXTERN int		Tcl_UniCharIsAlnum(int ch);
/* 346 */
EXTERN int		Tcl_UniCharIsAlpha(int ch);
/* 347 */
EXTERN int		Tcl_UniCharIsDigit(int ch);
/* 348 */
EXTERN int		Tcl_UniCharIsLower(int ch);
/* 349 */
EXTERN int		Tcl_UniCharIsSpace(int ch);
/* 350 */
EXTERN int		Tcl_UniCharIsUpper(int ch);
/* 351 */
EXTERN int		Tcl_UniCharIsWordChar(int ch);
/* 352 */
EXTERN Tcl_Size		Tcl_Char16Len(const unsigned short *uniStr);
/* Slot 353 is reserved */
/* 354 */
EXTERN char *		Tcl_Char16ToUtfDString(const unsigned short *uniStr,
				Tcl_Size uniLength, Tcl_DString *dsPtr);
/* 355 */
EXTERN unsigned short *	 Tcl_UtfToChar16DString(const char *src,
				Tcl_Size length, Tcl_DString *dsPtr);
/* 356 */
EXTERN Tcl_RegExp	Tcl_GetRegExpFromObj(Tcl_Interp *interp,
				Tcl_Obj *patObj, int flags);
/* Slot 357 is reserved */
/* 358 */
EXTERN void		Tcl_FreeParse(Tcl_Parse *parsePtr);
/* 359 */
EXTERN void		Tcl_LogCommandInfo(Tcl_Interp *interp,
				const char *script, const char *command,
				Tcl_Size length);
/* 360 */
EXTERN int		Tcl_ParseBraces(Tcl_Interp *interp,
				const char *start, Tcl_Size numBytes,
				Tcl_Parse *parsePtr, int append,
				const char **termPtr);
/* 361 */
EXTERN int		Tcl_ParseCommand(Tcl_Interp *interp,
				const char *start, Tcl_Size numBytes,
				int nested, Tcl_Parse *parsePtr);
/* 362 */
EXTERN int		Tcl_ParseExpr(Tcl_Interp *interp, const char *start,
				Tcl_Size numBytes, Tcl_Parse *parsePtr);
/* 363 */
EXTERN int		Tcl_ParseQuotedString(Tcl_Interp *interp,
				const char *start, Tcl_Size numBytes,
				Tcl_Parse *parsePtr, int append,
				const char **termPtr);
/* 364 */
EXTERN int		Tcl_ParseVarName(Tcl_Interp *interp,
				const char *start, Tcl_Size numBytes,
				Tcl_Parse *parsePtr, int append);
/* 365 */
EXTERN char *		Tcl_GetCwd(Tcl_Interp *interp, Tcl_DString *cwdPtr);
/* 366 */
EXTERN int		Tcl_Chdir(const char *dirName);
/* 367 */
EXTERN int		Tcl_Access(const char *path, int mode);
/* 368 */
EXTERN int		Tcl_Stat(const char *path, struct stat *bufPtr);
/* 369 */
EXTERN int		TclUtfNcmp(const char *s1, const char *s2, size_t n);
/* 370 */
EXTERN int		TclUtfNcasecmp(const char *s1, const char *s2,
				size_t n);
/* 371 */
EXTERN int		Tcl_StringCaseMatch(const char *str,
				const char *pattern, int nocase);
/* 372 */
EXTERN int		Tcl_UniCharIsControl(int ch);
/* 373 */
EXTERN int		Tcl_UniCharIsGraph(int ch);
/* 374 */
EXTERN int		Tcl_UniCharIsPrint(int ch);
/* 375 */
EXTERN int		Tcl_UniCharIsPunct(int ch);
/* 376 */
EXTERN int		Tcl_RegExpExecObj(Tcl_Interp *interp,
				Tcl_RegExp regexp, Tcl_Obj *textObj,
				Tcl_Size offset, Tcl_Size nmatches,
				int flags);
/* 377 */
EXTERN void		Tcl_RegExpGetInfo(Tcl_RegExp regexp,
				Tcl_RegExpInfo *infoPtr);
/* 378 */
EXTERN Tcl_Obj *	Tcl_NewUnicodeObj(const Tcl_UniChar *unicode,
				Tcl_Size numChars);
/* 379 */
EXTERN void		Tcl_SetUnicodeObj(Tcl_Obj *objPtr,
				const Tcl_UniChar *unicode,
				Tcl_Size numChars);
/* 380 */
EXTERN Tcl_Size		TclGetCharLength(Tcl_Obj *objPtr);
/* 381 */
EXTERN int		TclGetUniChar(Tcl_Obj *objPtr, Tcl_Size index);
/* Slot 382 is reserved */
/* 383 */
EXTERN Tcl_Obj *	TclGetRange(Tcl_Obj *objPtr, Tcl_Size first,
				Tcl_Size last);
/* 384 */
EXTERN void		Tcl_AppendUnicodeToObj(Tcl_Obj *objPtr,
				const Tcl_UniChar *unicode, Tcl_Size length);
/* 385 */
EXTERN int		Tcl_RegExpMatchObj(Tcl_Interp *interp,
				Tcl_Obj *textObj, Tcl_Obj *patternObj);
/* 386 */
EXTERN void		Tcl_SetNotifier(
				const Tcl_NotifierProcs *notifierProcPtr);
/* 387 */
EXTERN Tcl_Mutex *	Tcl_GetAllocMutex(void);
/* 388 */
EXTERN int		Tcl_GetChannelNames(Tcl_Interp *interp);
/* 389 */
EXTERN int		Tcl_GetChannelNamesEx(Tcl_Interp *interp,
				const char *pattern);
/* 390 */
EXTERN int		Tcl_ProcObjCmd(void *clientData, Tcl_Interp *interp,
				Tcl_Size objc, Tcl_Obj *const objv[]);
/* 391 */
EXTERN void		Tcl_ConditionFinalize(Tcl_Condition *condPtr);
/* 392 */
EXTERN void		Tcl_MutexFinalize(Tcl_Mutex *mutex);
/* 393 */
EXTERN int		Tcl_CreateThread(Tcl_ThreadId *idPtr,
				Tcl_ThreadCreateProc *proc, void *clientData,
				TCL_HASH_TYPE stackSize, int flags);
/* 394 */
EXTERN Tcl_Size		Tcl_ReadRaw(Tcl_Channel chan, char *dst,
				Tcl_Size bytesToRead);
/* 395 */
EXTERN Tcl_Size		Tcl_WriteRaw(Tcl_Channel chan, const char *src,
				Tcl_Size srcLen);
/* 396 */
EXTERN Tcl_Channel	Tcl_GetTopChannel(Tcl_Channel chan);
/* 397 */
EXTERN int		Tcl_ChannelBuffered(Tcl_Channel chan);
/* 398 */
EXTERN const char *	Tcl_ChannelName(const Tcl_ChannelType *chanTypePtr);
/* 399 */
EXTERN Tcl_ChannelTypeVersion Tcl_ChannelVersion(
				const Tcl_ChannelType *chanTypePtr);
/* 400 */
EXTERN Tcl_DriverBlockModeProc * Tcl_ChannelBlockModeProc(
				const Tcl_ChannelType *chanTypePtr);
/* Slot 401 is reserved */
/* 402 */
EXTERN Tcl_DriverClose2Proc * Tcl_ChannelClose2Proc(
				const Tcl_ChannelType *chanTypePtr);
/* 403 */
EXTERN Tcl_DriverInputProc * Tcl_ChannelInputProc(
				const Tcl_ChannelType *chanTypePtr);
/* 404 */
EXTERN Tcl_DriverOutputProc * Tcl_ChannelOutputProc(
				const Tcl_ChannelType *chanTypePtr);
/* Slot 405 is reserved */
/* 406 */
EXTERN Tcl_DriverSetOptionProc * Tcl_ChannelSetOptionProc(
				const Tcl_ChannelType *chanTypePtr);
/* 407 */
EXTERN Tcl_DriverGetOptionProc * Tcl_ChannelGetOptionProc(
				const Tcl_ChannelType *chanTypePtr);
/* 408 */
EXTERN Tcl_DriverWatchProc * Tcl_ChannelWatchProc(
				const Tcl_ChannelType *chanTypePtr);
/* 409 */
EXTERN Tcl_DriverGetHandleProc * Tcl_ChannelGetHandleProc(
				const Tcl_ChannelType *chanTypePtr);
/* 410 */
EXTERN Tcl_DriverFlushProc * Tcl_ChannelFlushProc(
				const Tcl_ChannelType *chanTypePtr);
/* 411 */
EXTERN Tcl_DriverHandlerProc * Tcl_ChannelHandlerProc(
				const Tcl_ChannelType *chanTypePtr);
/* 412 */
EXTERN int		Tcl_JoinThread(Tcl_ThreadId threadId, int *result);
/* 413 */
EXTERN int		Tcl_IsChannelShared(Tcl_Channel channel);
/* 414 */
EXTERN int		Tcl_IsChannelRegistered(Tcl_Interp *interp,
				Tcl_Channel channel);
/* 415 */
EXTERN void		Tcl_CutChannel(Tcl_Channel channel);
/* 416 */
EXTERN void		Tcl_SpliceChannel(Tcl_Channel channel);
/* 417 */
EXTERN void		Tcl_ClearChannelHandlers(Tcl_Channel channel);
/* 418 */
EXTERN int		Tcl_IsChannelExisting(const char *channelName);
/* Slot 419 is reserved */
/* Slot 420 is reserved */
/* Slot 421 is reserved */
/* 422 */
EXTERN Tcl_HashEntry *	Tcl_CreateHashEntry(Tcl_HashTable *tablePtr,
				const void *key, int *newPtr);
/* 423 */
EXTERN void		Tcl_InitCustomHashTable(Tcl_HashTable *tablePtr,
				int keyType, const Tcl_HashKeyType *typePtr);
/* 424 */
EXTERN void		Tcl_InitObjHashTable(Tcl_HashTable *tablePtr);
/* 425 */
EXTERN void *		Tcl_CommandTraceInfo(Tcl_Interp *interp,
				const char *varName, int flags,
				Tcl_CommandTraceProc *procPtr,
				void *prevClientData);
/* 426 */
EXTERN int		Tcl_TraceCommand(Tcl_Interp *interp,
				const char *varName, int flags,
				Tcl_CommandTraceProc *proc, void *clientData);
/* 427 */
EXTERN void		Tcl_UntraceCommand(Tcl_Interp *interp,
				const char *varName, int flags,
				Tcl_CommandTraceProc *proc, void *clientData);
/* 428 */
EXTERN void *		Tcl_AttemptAlloc(TCL_HASH_TYPE size);
/* 429 */
EXTERN void *		Tcl_AttemptDbCkalloc(TCL_HASH_TYPE size,
				const char *file, int line);
/* 430 */
EXTERN void *		Tcl_AttemptRealloc(void *ptr, TCL_HASH_TYPE size);
/* 431 */
EXTERN void *		Tcl_AttemptDbCkrealloc(void *ptr, TCL_HASH_TYPE size,
				const char *file, int line);
/* 432 */
EXTERN int		Tcl_AttemptSetObjLength(Tcl_Obj *objPtr,
				Tcl_Size length);
/* 433 */
EXTERN Tcl_ThreadId	Tcl_GetChannelThread(Tcl_Channel channel);
/* 434 */
EXTERN Tcl_UniChar *	TclGetUnicodeFromObj(Tcl_Obj *objPtr,
				void *lengthPtr);
/* Slot 435 is reserved */
/* Slot 436 is reserved */
/* 437 */
EXTERN Tcl_Obj *	Tcl_SubstObj(Tcl_Interp *interp, Tcl_Obj *objPtr,
				int flags);
/* 438 */
EXTERN int		Tcl_DetachChannel(Tcl_Interp *interp,
				Tcl_Channel channel);
/* 439 */
EXTERN int		Tcl_IsStandardChannel(Tcl_Channel channel);
/* 440 */
EXTERN int		Tcl_FSCopyFile(Tcl_Obj *srcPathPtr,
				Tcl_Obj *destPathPtr);
/* 441 */
EXTERN int		Tcl_FSCopyDirectory(Tcl_Obj *srcPathPtr,
				Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr);
/* 442 */
EXTERN int		Tcl_FSCreateDirectory(Tcl_Obj *pathPtr);
/* 443 */
EXTERN int		Tcl_FSDeleteFile(Tcl_Obj *pathPtr);
/* 444 */
EXTERN int		Tcl_FSLoadFile(Tcl_Interp *interp, Tcl_Obj *pathPtr,
				const char *sym1, const char *sym2,
				Tcl_LibraryInitProc **proc1Ptr,
				Tcl_LibraryInitProc **proc2Ptr,
				Tcl_LoadHandle *handlePtr,
				Tcl_FSUnloadFileProc **unloadProcPtr);
/* 445 */
EXTERN int		Tcl_FSMatchInDirectory(Tcl_Interp *interp,
				Tcl_Obj *result, Tcl_Obj *pathPtr,
				const char *pattern, Tcl_GlobTypeData *types);
/* 446 */
EXTERN Tcl_Obj *	Tcl_FSLink(Tcl_Obj *pathPtr, Tcl_Obj *toPtr,
				int linkAction);
/* 447 */
EXTERN int		Tcl_FSRemoveDirectory(Tcl_Obj *pathPtr,
				int recursive, Tcl_Obj **errorPtr);
/* 448 */
EXTERN int		Tcl_FSRenameFile(Tcl_Obj *srcPathPtr,
				Tcl_Obj *destPathPtr);
/* 449 */
EXTERN int		Tcl_FSLstat(Tcl_Obj *pathPtr, Tcl_StatBuf *buf);
/* 450 */
EXTERN int		Tcl_FSUtime(Tcl_Obj *pathPtr, struct utimbuf *tval);
/* 451 */
EXTERN int		Tcl_FSFileAttrsGet(Tcl_Interp *interp, int index,
				Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef);
/* 452 */
EXTERN int		Tcl_FSFileAttrsSet(Tcl_Interp *interp, int index,
				Tcl_Obj *pathPtr, Tcl_Obj *objPtr);
/* 453 */
EXTERN const char *const * Tcl_FSFileAttrStrings(Tcl_Obj *pathPtr,
				Tcl_Obj **objPtrRef);
/* 454 */
EXTERN int		Tcl_FSStat(Tcl_Obj *pathPtr, Tcl_StatBuf *buf);
/* 455 */
EXTERN int		Tcl_FSAccess(Tcl_Obj *pathPtr, int mode);
/* 456 */
EXTERN Tcl_Channel	Tcl_FSOpenFileChannel(Tcl_Interp *interp,
				Tcl_Obj *pathPtr, const char *modeString,
				int permissions);
/* 457 */
EXTERN Tcl_Obj *	Tcl_FSGetCwd(Tcl_Interp *interp);
/* 458 */
EXTERN int		Tcl_FSChdir(Tcl_Obj *pathPtr);
/* 459 */
EXTERN int		Tcl_FSConvertToPathType(Tcl_Interp *interp,
				Tcl_Obj *pathPtr);
/* 460 */
EXTERN Tcl_Obj *	Tcl_FSJoinPath(Tcl_Obj *listObj, Tcl_Size elements);
/* 461 */
EXTERN Tcl_Obj *	TclFSSplitPath(Tcl_Obj *pathPtr, void *lenPtr);
/* 462 */
EXTERN int		Tcl_FSEqualPaths(Tcl_Obj *firstPtr,
				Tcl_Obj *secondPtr);
/* 463 */
EXTERN Tcl_Obj *	Tcl_FSGetNormalizedPath(Tcl_Interp *interp,
				Tcl_Obj *pathPtr);
/* 464 */
EXTERN Tcl_Obj *	Tcl_FSJoinToPath(Tcl_Obj *pathPtr, Tcl_Size objc,
				Tcl_Obj *const objv[]);
/* 465 */
EXTERN void *		Tcl_FSGetInternalRep(Tcl_Obj *pathPtr,
				const Tcl_Filesystem *fsPtr);
/* 466 */
EXTERN Tcl_Obj *	Tcl_FSGetTranslatedPath(Tcl_Interp *interp,
				Tcl_Obj *pathPtr);
/* 467 */
EXTERN int		Tcl_FSEvalFile(Tcl_Interp *interp, Tcl_Obj *fileName);
/* 468 */
EXTERN Tcl_Obj *	Tcl_FSNewNativePath(
				const Tcl_Filesystem *fromFilesystem,
				void *clientData);
/* 469 */
EXTERN const void *	Tcl_FSGetNativePath(Tcl_Obj *pathPtr);
/* 470 */
EXTERN Tcl_Obj *	Tcl_FSFileSystemInfo(Tcl_Obj *pathPtr);
/* 471 */
EXTERN Tcl_Obj *	Tcl_FSPathSeparator(Tcl_Obj *pathPtr);
/* 472 */
EXTERN Tcl_Obj *	Tcl_FSListVolumes(void);
/* 473 */
EXTERN int		Tcl_FSRegister(void *clientData,
				const Tcl_Filesystem *fsPtr);
/* 474 */
EXTERN int		Tcl_FSUnregister(const Tcl_Filesystem *fsPtr);
/* 475 */
EXTERN void *		Tcl_FSData(const Tcl_Filesystem *fsPtr);
/* 476 */
EXTERN const char *	Tcl_FSGetTranslatedStringPath(Tcl_Interp *interp,
				Tcl_Obj *pathPtr);
/* 477 */
EXTERN const Tcl_Filesystem * Tcl_FSGetFileSystemForPath(Tcl_Obj *pathPtr);
/* 478 */
EXTERN Tcl_PathType	Tcl_FSGetPathType(Tcl_Obj *pathPtr);
/* 479 */
EXTERN int		Tcl_OutputBuffered(Tcl_Channel chan);
/* 480 */
EXTERN void		Tcl_FSMountsChanged(const Tcl_Filesystem *fsPtr);
/* 481 */
EXTERN int		Tcl_EvalTokensStandard(Tcl_Interp *interp,
				Tcl_Token *tokenPtr, Tcl_Size count);
/* 482 */
EXTERN void		Tcl_GetTime(Tcl_Time *timeBuf);
/* 483 */
EXTERN Tcl_Trace	Tcl_CreateObjTrace(Tcl_Interp *interp,
				Tcl_Size level, int flags,
				Tcl_CmdObjTraceProc *objProc,
				void *clientData,
				Tcl_CmdObjTraceDeleteProc *delProc);
/* 484 */
EXTERN int		Tcl_GetCommandInfoFromToken(Tcl_Command token,
				Tcl_CmdInfo *infoPtr);
/* 485 */
EXTERN int		Tcl_SetCommandInfoFromToken(Tcl_Command token,
				const Tcl_CmdInfo *infoPtr);
/* 486 */
EXTERN Tcl_Obj *	Tcl_DbNewWideIntObj(Tcl_WideInt wideValue,
				const char *file, int line);
/* 487 */
EXTERN int		Tcl_GetWideIntFromObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, Tcl_WideInt *widePtr);
/* 488 */
EXTERN Tcl_Obj *	Tcl_NewWideIntObj(Tcl_WideInt wideValue);
/* 489 */
EXTERN void		Tcl_SetWideIntObj(Tcl_Obj *objPtr,
				Tcl_WideInt wideValue);
/* 490 */
EXTERN Tcl_StatBuf *	Tcl_AllocStatBuf(void);
/* 491 */
EXTERN long long	Tcl_Seek(Tcl_Channel chan, long long offset,
				int mode);
/* 492 */
EXTERN long long	Tcl_Tell(Tcl_Channel chan);
/* 493 */
EXTERN Tcl_DriverWideSeekProc * Tcl_ChannelWideSeekProc(
				const Tcl_ChannelType *chanTypePtr);
/* 494 */
EXTERN int		Tcl_DictObjPut(Tcl_Interp *interp, Tcl_Obj *dictPtr,
				Tcl_Obj *keyPtr, Tcl_Obj *valuePtr);
/* 495 */
EXTERN int		Tcl_DictObjGet(Tcl_Interp *interp, Tcl_Obj *dictPtr,
				Tcl_Obj *keyPtr, Tcl_Obj **valuePtrPtr);
/* 496 */
EXTERN int		Tcl_DictObjRemove(Tcl_Interp *interp,
				Tcl_Obj *dictPtr, Tcl_Obj *keyPtr);
/* 497 */
EXTERN int		TclDictObjSize(Tcl_Interp *interp, Tcl_Obj *dictPtr,
				void *sizePtr);
/* 498 */
EXTERN int		Tcl_DictObjFirst(Tcl_Interp *interp,
				Tcl_Obj *dictPtr, Tcl_DictSearch *searchPtr,
				Tcl_Obj **keyPtrPtr, Tcl_Obj **valuePtrPtr,
				int *donePtr);
/* 499 */
EXTERN void		Tcl_DictObjNext(Tcl_DictSearch *searchPtr,
				Tcl_Obj **keyPtrPtr, Tcl_Obj **valuePtrPtr,
				int *donePtr);
/* 500 */
EXTERN void		Tcl_DictObjDone(Tcl_DictSearch *searchPtr);
/* 501 */
EXTERN int		Tcl_DictObjPutKeyList(Tcl_Interp *interp,
				Tcl_Obj *dictPtr, Tcl_Size keyc,
				Tcl_Obj *const *keyv, Tcl_Obj *valuePtr);
/* 502 */
EXTERN int		Tcl_DictObjRemoveKeyList(Tcl_Interp *interp,
				Tcl_Obj *dictPtr, Tcl_Size keyc,
				Tcl_Obj *const *keyv);
/* 503 */
EXTERN Tcl_Obj *	Tcl_NewDictObj(void);
/* 504 */
EXTERN Tcl_Obj *	Tcl_DbNewDictObj(const char *file, int line);
/* 505 */
EXTERN void		Tcl_RegisterConfig(Tcl_Interp *interp,
				const char *pkgName,
				const Tcl_Config *configuration,
				const char *valEncoding);
/* 506 */
EXTERN Tcl_Namespace *	Tcl_CreateNamespace(Tcl_Interp *interp,
				const char *name, void *clientData,
				Tcl_NamespaceDeleteProc *deleteProc);
/* 507 */
EXTERN void		Tcl_DeleteNamespace(Tcl_Namespace *nsPtr);
/* 508 */
EXTERN int		Tcl_AppendExportList(Tcl_Interp *interp,
				Tcl_Namespace *nsPtr, Tcl_Obj *objPtr);
/* 509 */
EXTERN int		Tcl_Export(Tcl_Interp *interp, Tcl_Namespace *nsPtr,
				const char *pattern, int resetListFirst);
/* 510 */
EXTERN int		Tcl_Import(Tcl_Interp *interp, Tcl_Namespace *nsPtr,
				const char *pattern, int allowOverwrite);
/* 511 */
EXTERN int		Tcl_ForgetImport(Tcl_Interp *interp,
				Tcl_Namespace *nsPtr, const char *pattern);
/* 512 */
EXTERN Tcl_Namespace *	Tcl_GetCurrentNamespace(Tcl_Interp *interp);
/* 513 */
EXTERN Tcl_Namespace *	Tcl_GetGlobalNamespace(Tcl_Interp *interp);
/* 514 */
EXTERN Tcl_Namespace *	Tcl_FindNamespace(Tcl_Interp *interp,
				const char *name,
				Tcl_Namespace *contextNsPtr, int flags);
/* 515 */
EXTERN Tcl_Command	Tcl_FindCommand(Tcl_Interp *interp, const char *name,
				Tcl_Namespace *contextNsPtr, int flags);
/* 516 */
EXTERN Tcl_Command	Tcl_GetCommandFromObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr);
/* 517 */
EXTERN void		Tcl_GetCommandFullName(Tcl_Interp *interp,
				Tcl_Command command, Tcl_Obj *objPtr);
/* 518 */
EXTERN int		Tcl_FSEvalFileEx(Tcl_Interp *interp,
				Tcl_Obj *fileName, const char *encodingName);
/* Slot 519 is reserved */
/* 520 */
EXTERN void		Tcl_LimitAddHandler(Tcl_Interp *interp, int type,
				Tcl_LimitHandlerProc *handlerProc,
				void *clientData,
				Tcl_LimitHandlerDeleteProc *deleteProc);
/* 521 */
EXTERN void		Tcl_LimitRemoveHandler(Tcl_Interp *interp, int type,
				Tcl_LimitHandlerProc *handlerProc,
				void *clientData);
/* 522 */
EXTERN int		Tcl_LimitReady(Tcl_Interp *interp);
/* 523 */
EXTERN int		Tcl_LimitCheck(Tcl_Interp *interp);
/* 524 */
EXTERN int		Tcl_LimitExceeded(Tcl_Interp *interp);
/* 525 */
EXTERN void		Tcl_LimitSetCommands(Tcl_Interp *interp,
				Tcl_Size commandLimit);
/* 526 */
EXTERN void		Tcl_LimitSetTime(Tcl_Interp *interp,
				Tcl_Time *timeLimitPtr);
/* 527 */
EXTERN void		Tcl_LimitSetGranularity(Tcl_Interp *interp, int type,
				int granularity);
/* 528 */
EXTERN int		Tcl_LimitTypeEnabled(Tcl_Interp *interp, int type);
/* 529 */
EXTERN int		Tcl_LimitTypeExceeded(Tcl_Interp *interp, int type);
/* 530 */
EXTERN void		Tcl_LimitTypeSet(Tcl_Interp *interp, int type);
/* 531 */
EXTERN void		Tcl_LimitTypeReset(Tcl_Interp *interp, int type);
/* 532 */
EXTERN Tcl_Size		Tcl_LimitGetCommands(Tcl_Interp *interp);
/* 533 */
EXTERN void		Tcl_LimitGetTime(Tcl_Interp *interp,
				Tcl_Time *timeLimitPtr);
/* 534 */
EXTERN int		Tcl_LimitGetGranularity(Tcl_Interp *interp, int type);
/* 535 */
EXTERN Tcl_InterpState	Tcl_SaveInterpState(Tcl_Interp *interp, int status);
/* 536 */
EXTERN int		Tcl_RestoreInterpState(Tcl_Interp *interp,
				Tcl_InterpState state);
/* 537 */
EXTERN void		Tcl_DiscardInterpState(Tcl_InterpState state);
/* 538 */
EXTERN int		Tcl_SetReturnOptions(Tcl_Interp *interp,
				Tcl_Obj *options);
/* 539 */
EXTERN Tcl_Obj *	Tcl_GetReturnOptions(Tcl_Interp *interp, int result);
/* 540 */
EXTERN int		Tcl_IsEnsemble(Tcl_Command token);
/* 541 */
EXTERN Tcl_Command	Tcl_CreateEnsemble(Tcl_Interp *interp,
				const char *name,
				Tcl_Namespace *namespacePtr, int flags);
/* 542 */
EXTERN Tcl_Command	Tcl_FindEnsemble(Tcl_Interp *interp,
				Tcl_Obj *cmdNameObj, int flags);
/* 543 */
EXTERN int		Tcl_SetEnsembleSubcommandList(Tcl_Interp *interp,
				Tcl_Command token, Tcl_Obj *subcmdList);
/* 544 */
EXTERN int		Tcl_SetEnsembleMappingDict(Tcl_Interp *interp,
				Tcl_Command token, Tcl_Obj *mapDict);
/* 545 */
EXTERN int		Tcl_SetEnsembleUnknownHandler(Tcl_Interp *interp,
				Tcl_Command token, Tcl_Obj *unknownList);
/* 546 */
EXTERN int		Tcl_SetEnsembleFlags(Tcl_Interp *interp,
				Tcl_Command token, int flags);
/* 547 */
EXTERN int		Tcl_GetEnsembleSubcommandList(Tcl_Interp *interp,
				Tcl_Command token, Tcl_Obj **subcmdListPtr);
/* 548 */
EXTERN int		Tcl_GetEnsembleMappingDict(Tcl_Interp *interp,
				Tcl_Command token, Tcl_Obj **mapDictPtr);
/* 549 */
EXTERN int		Tcl_GetEnsembleUnknownHandler(Tcl_Interp *interp,
				Tcl_Command token, Tcl_Obj **unknownListPtr);
/* 550 */
EXTERN int		Tcl_GetEnsembleFlags(Tcl_Interp *interp,
				Tcl_Command token, int *flagsPtr);
/* 551 */
EXTERN int		Tcl_GetEnsembleNamespace(Tcl_Interp *interp,
				Tcl_Command token,
				Tcl_Namespace **namespacePtrPtr);
/* 552 */
EXTERN void		Tcl_SetTimeProc(Tcl_GetTimeProc *getProc,
				Tcl_ScaleTimeProc *scaleProc,
				void *clientData);
/* 553 */
EXTERN void		Tcl_QueryTimeProc(Tcl_GetTimeProc **getProc,
				Tcl_ScaleTimeProc **scaleProc,
				void **clientData);
/* 554 */
EXTERN Tcl_DriverThreadActionProc * Tcl_ChannelThreadActionProc(
				const Tcl_ChannelType *chanTypePtr);
/* 555 */
EXTERN Tcl_Obj *	Tcl_NewBignumObj(void *value);
/* 556 */
EXTERN Tcl_Obj *	Tcl_DbNewBignumObj(void *value, const char *file,
				int line);
/* 557 */
EXTERN void		Tcl_SetBignumObj(Tcl_Obj *obj, void *value);
/* 558 */
EXTERN int		Tcl_GetBignumFromObj(Tcl_Interp *interp,
				Tcl_Obj *obj, void *value);
/* 559 */
EXTERN int		Tcl_TakeBignumFromObj(Tcl_Interp *interp,
				Tcl_Obj *obj, void *value);
/* 560 */
EXTERN int		Tcl_TruncateChannel(Tcl_Channel chan,
				long long length);
/* 561 */
EXTERN Tcl_DriverTruncateProc * Tcl_ChannelTruncateProc(
				const Tcl_ChannelType *chanTypePtr);
/* 562 */
EXTERN void		Tcl_SetChannelErrorInterp(Tcl_Interp *interp,
				Tcl_Obj *msg);
/* 563 */
EXTERN void		Tcl_GetChannelErrorInterp(Tcl_Interp *interp,
				Tcl_Obj **msg);
/* 564 */
EXTERN void		Tcl_SetChannelError(Tcl_Channel chan, Tcl_Obj *msg);
/* 565 */
EXTERN void		Tcl_GetChannelError(Tcl_Channel chan, Tcl_Obj **msg);
/* 566 */
EXTERN int		Tcl_InitBignumFromDouble(Tcl_Interp *interp,
				double initval, void *toInit);
/* 567 */
EXTERN Tcl_Obj *	Tcl_GetNamespaceUnknownHandler(Tcl_Interp *interp,
				Tcl_Namespace *nsPtr);
/* 568 */
EXTERN int		Tcl_SetNamespaceUnknownHandler(Tcl_Interp *interp,
				Tcl_Namespace *nsPtr, Tcl_Obj *handlerPtr);
/* 569 */
EXTERN int		Tcl_GetEncodingFromObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, Tcl_Encoding *encodingPtr);
/* 570 */
EXTERN Tcl_Obj *	Tcl_GetEncodingSearchPath(void);
/* 571 */
EXTERN int		Tcl_SetEncodingSearchPath(Tcl_Obj *searchPath);
/* 572 */
EXTERN const char *	Tcl_GetEncodingNameFromEnvironment(
				Tcl_DString *bufPtr);
/* 573 */
EXTERN int		Tcl_PkgRequireProc(Tcl_Interp *interp,
				const char *name, Tcl_Size objc,
				Tcl_Obj *const objv[], void *clientDataPtr);
/* 574 */
EXTERN void		Tcl_AppendObjToErrorInfo(Tcl_Interp *interp,
				Tcl_Obj *objPtr);
/* 575 */
EXTERN void		Tcl_AppendLimitedToObj(Tcl_Obj *objPtr,
				const char *bytes, Tcl_Size length,
				Tcl_Size limit, const char *ellipsis);
/* 576 */
EXTERN Tcl_Obj *	Tcl_Format(Tcl_Interp *interp, const char *format,
				Tcl_Size objc, Tcl_Obj *const objv[]);
/* 577 */
EXTERN int		Tcl_AppendFormatToObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, const char *format,
				Tcl_Size objc, Tcl_Obj *const objv[]);
/* 578 */
EXTERN Tcl_Obj *	Tcl_ObjPrintf(const char *format, ...) TCL_FORMAT_PRINTF(1, 2);
/* 579 */
EXTERN void		Tcl_AppendPrintfToObj(Tcl_Obj *objPtr,
				const char *format, ...) TCL_FORMAT_PRINTF(2, 3);
/* 580 */
EXTERN int		Tcl_CancelEval(Tcl_Interp *interp,
				Tcl_Obj *resultObjPtr, void *clientData,
				int flags);
/* 581 */
EXTERN int		Tcl_Canceled(Tcl_Interp *interp, int flags);
/* 582 */
EXTERN int		Tcl_CreatePipe(Tcl_Interp *interp,
				Tcl_Channel *rchan, Tcl_Channel *wchan,
				int flags);
/* 583 */
EXTERN Tcl_Command	Tcl_NRCreateCommand(Tcl_Interp *interp,
				const char *cmdName, Tcl_ObjCmdProc *proc,
				Tcl_ObjCmdProc *nreProc, void *clientData,
				Tcl_CmdDeleteProc *deleteProc);
/* 584 */
EXTERN int		Tcl_NREvalObj(Tcl_Interp *interp, Tcl_Obj *objPtr,
				int flags);
/* 585 */
EXTERN int		Tcl_NREvalObjv(Tcl_Interp *interp, Tcl_Size objc,
				Tcl_Obj *const objv[], int flags);
/* 586 */
EXTERN int		Tcl_NRCmdSwap(Tcl_Interp *interp, Tcl_Command cmd,
				Tcl_Size objc, Tcl_Obj *const objv[],
				int flags);
/* 587 */
EXTERN void		Tcl_NRAddCallback(Tcl_Interp *interp,
				Tcl_NRPostProc *postProcPtr, void *data0,
				void *data1, void *data2, void *data3);
/* 588 */
EXTERN int		Tcl_NRCallObjProc(Tcl_Interp *interp,
				Tcl_ObjCmdProc *objProc, void *clientData,
				Tcl_Size objc, Tcl_Obj *const objv[]);
/* 589 */
EXTERN unsigned		Tcl_GetFSDeviceFromStat(const Tcl_StatBuf *statPtr);
/* 590 */
EXTERN unsigned		Tcl_GetFSInodeFromStat(const Tcl_StatBuf *statPtr);
/* 591 */
EXTERN unsigned		Tcl_GetModeFromStat(const Tcl_StatBuf *statPtr);
/* 592 */
EXTERN int		Tcl_GetLinkCountFromStat(const Tcl_StatBuf *statPtr);
/* 593 */
EXTERN int		Tcl_GetUserIdFromStat(const Tcl_StatBuf *statPtr);
/* 594 */
EXTERN int		Tcl_GetGroupIdFromStat(const Tcl_StatBuf *statPtr);
/* 595 */
EXTERN int		Tcl_GetDeviceTypeFromStat(const Tcl_StatBuf *statPtr);
/* 596 */
EXTERN long long	Tcl_GetAccessTimeFromStat(const Tcl_StatBuf *statPtr);
/* 597 */
EXTERN long long	Tcl_GetModificationTimeFromStat(
				const Tcl_StatBuf *statPtr);
/* 598 */
EXTERN long long	Tcl_GetChangeTimeFromStat(const Tcl_StatBuf *statPtr);
/* 599 */
EXTERN unsigned long long Tcl_GetSizeFromStat(const Tcl_StatBuf *statPtr);
/* 600 */
EXTERN unsigned long long Tcl_GetBlocksFromStat(const Tcl_StatBuf *statPtr);
/* 601 */
EXTERN unsigned		Tcl_GetBlockSizeFromStat(const Tcl_StatBuf *statPtr);
/* 602 */
EXTERN int		Tcl_SetEnsembleParameterList(Tcl_Interp *interp,
				Tcl_Command token, Tcl_Obj *paramList);
/* 603 */
EXTERN int		Tcl_GetEnsembleParameterList(Tcl_Interp *interp,
				Tcl_Command token, Tcl_Obj **paramListPtr);
/* 604 */
EXTERN int		TclParseArgsObjv(Tcl_Interp *interp,
				const Tcl_ArgvInfo *argTable, void *objcPtr,
				Tcl_Obj *const *objv, Tcl_Obj ***remObjv);
/* 605 */
EXTERN int		Tcl_GetErrorLine(Tcl_Interp *interp);
/* 606 */
EXTERN void		Tcl_SetErrorLine(Tcl_Interp *interp, int lineNum);
/* 607 */
EXTERN void		Tcl_TransferResult(Tcl_Interp *sourceInterp,
				int code, Tcl_Interp *targetInterp);
/* 608 */
EXTERN int		Tcl_InterpActive(Tcl_Interp *interp);
/* 609 */
EXTERN void		Tcl_BackgroundException(Tcl_Interp *interp, int code);
/* 610 */
EXTERN int		Tcl_ZlibDeflate(Tcl_Interp *interp, int format,
				Tcl_Obj *data, int level,
				Tcl_Obj *gzipHeaderDictObj);
/* 611 */
EXTERN int		Tcl_ZlibInflate(Tcl_Interp *interp, int format,
				Tcl_Obj *data, Tcl_Size buffersize,
				Tcl_Obj *gzipHeaderDictObj);
/* 612 */
EXTERN unsigned int	Tcl_ZlibCRC32(unsigned int crc,
				const unsigned char *buf, Tcl_Size len);
/* 613 */
EXTERN unsigned int	Tcl_ZlibAdler32(unsigned int adler,
				const unsigned char *buf, Tcl_Size len);
/* 614 */
EXTERN int		Tcl_ZlibStreamInit(Tcl_Interp *interp, int mode,
				int format, int level, Tcl_Obj *dictObj,
				Tcl_ZlibStream *zshandle);
/* 615 */
EXTERN Tcl_Obj *	Tcl_ZlibStreamGetCommandName(Tcl_ZlibStream zshandle);
/* 616 */
EXTERN int		Tcl_ZlibStreamEof(Tcl_ZlibStream zshandle);
/* 617 */
EXTERN int		Tcl_ZlibStreamChecksum(Tcl_ZlibStream zshandle);
/* 618 */
EXTERN int		Tcl_ZlibStreamPut(Tcl_ZlibStream zshandle,
				Tcl_Obj *data, int flush);
/* 619 */
EXTERN int		Tcl_ZlibStreamGet(Tcl_ZlibStream zshandle,
				Tcl_Obj *data, Tcl_Size count);
/* 620 */
EXTERN int		Tcl_ZlibStreamClose(Tcl_ZlibStream zshandle);
/* 621 */
EXTERN int		Tcl_ZlibStreamReset(Tcl_ZlibStream zshandle);
/* 622 */
EXTERN void		Tcl_SetStartupScript(Tcl_Obj *path,
				const char *encoding);
/* 623 */
EXTERN Tcl_Obj *	Tcl_GetStartupScript(const char **encodingPtr);
/* 624 */
EXTERN int		Tcl_CloseEx(Tcl_Interp *interp, Tcl_Channel chan,
				int flags);
/* 625 */
EXTERN int		Tcl_NRExprObj(Tcl_Interp *interp, Tcl_Obj *objPtr,
				Tcl_Obj *resultPtr);
/* 626 */
EXTERN int		Tcl_NRSubstObj(Tcl_Interp *interp, Tcl_Obj *objPtr,
				int flags);
/* 627 */
EXTERN int		Tcl_LoadFile(Tcl_Interp *interp, Tcl_Obj *pathPtr,
				const char *const symv[], int flags,
				void *procPtrs, Tcl_LoadHandle *handlePtr);
/* 628 */
EXTERN void *		Tcl_FindSymbol(Tcl_Interp *interp,
				Tcl_LoadHandle handle, const char *symbol);
/* 629 */
EXTERN int		Tcl_FSUnloadFile(Tcl_Interp *interp,
				Tcl_LoadHandle handlePtr);
/* 630 */
EXTERN void		Tcl_ZlibStreamSetCompressionDictionary(
				Tcl_ZlibStream zhandle,
				Tcl_Obj *compressionDictionaryObj);
/* 631 */
EXTERN Tcl_Channel	Tcl_OpenTcpServerEx(Tcl_Interp *interp,
				const char *service, const char *host,
				unsigned int flags, int backlog,
				Tcl_TcpAcceptProc *acceptProc,
				void *callbackData);
/* 632 */
EXTERN int		TclZipfs_Mount(Tcl_Interp *interp,
				const char *zipname, const char *mountPoint,
				const char *passwd);
/* 633 */
EXTERN int		TclZipfs_Unmount(Tcl_Interp *interp,
				const char *mountPoint);
/* 634 */
EXTERN Tcl_Obj *	TclZipfs_TclLibrary(void);
/* 635 */
EXTERN int		TclZipfs_MountBuffer(Tcl_Interp *interp,
				const void *data, size_t datalen,
				const char *mountPoint, int copy);
/* 636 */
EXTERN void		Tcl_FreeInternalRep(Tcl_Obj *objPtr);
/* 637 */
EXTERN char *		Tcl_InitStringRep(Tcl_Obj *objPtr, const char *bytes,
				TCL_HASH_TYPE numBytes);
/* 638 */
EXTERN Tcl_ObjInternalRep * Tcl_FetchInternalRep(Tcl_Obj *objPtr,
				const Tcl_ObjType *typePtr);
/* 639 */
EXTERN void		Tcl_StoreInternalRep(Tcl_Obj *objPtr,
				const Tcl_ObjType *typePtr,
				const Tcl_ObjInternalRep *irPtr);
/* 640 */
EXTERN int		Tcl_HasStringRep(Tcl_Obj *objPtr);
/* 641 */
EXTERN void		Tcl_IncrRefCount(Tcl_Obj *objPtr);
/* 642 */
EXTERN void		Tcl_DecrRefCount(Tcl_Obj *objPtr);
/* 643 */
EXTERN int		Tcl_IsShared(Tcl_Obj *objPtr);
/* 644 */
EXTERN int		Tcl_LinkArray(Tcl_Interp *interp,
				const char *varName, void *addr, int type,
				Tcl_Size size);
/* 645 */
EXTERN int		Tcl_GetIntForIndex(Tcl_Interp *interp,
				Tcl_Obj *objPtr, Tcl_Size endValue,
				Tcl_Size *indexPtr);
/* 646 */
EXTERN Tcl_Size		Tcl_UtfToUniChar(const char *src, int *chPtr);
/* 647 */
EXTERN char *		Tcl_UniCharToUtfDString(const int *uniStr,
				Tcl_Size uniLength, Tcl_DString *dsPtr);
/* 648 */
EXTERN int *		Tcl_UtfToUniCharDString(const char *src,
				Tcl_Size length, Tcl_DString *dsPtr);
/* 649 */
EXTERN unsigned char *	TclGetBytesFromObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, void *numBytesPtr);
/* 650 */
EXTERN unsigned char *	Tcl_GetBytesFromObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, Tcl_Size *numBytesPtr);
/* 651 */
EXTERN char *		Tcl_GetStringFromObj(Tcl_Obj *objPtr,
				Tcl_Size *lengthPtr);
/* 652 */
EXTERN Tcl_UniChar *	Tcl_GetUnicodeFromObj(Tcl_Obj *objPtr,
				Tcl_Size *lengthPtr);
/* 653 */
EXTERN int		Tcl_GetSizeIntFromObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, Tcl_Size *sizePtr);
/* 654 */
EXTERN int		Tcl_UtfCharComplete(const char *src, Tcl_Size length);
/* 655 */
EXTERN const char *	Tcl_UtfNext(const char *src);
/* 656 */
EXTERN const char *	Tcl_UtfPrev(const char *src, const char *start);
/* 657 */
EXTERN int		Tcl_FSTildeExpand(Tcl_Interp *interp,
				const char *path, Tcl_DString *dsPtr);
/* 658 */
EXTERN int		Tcl_ExternalToUtfDStringEx(Tcl_Interp *interp,
				Tcl_Encoding encoding, const char *src,
				Tcl_Size srcLen, int flags,
				Tcl_DString *dsPtr,
				Tcl_Size *errorLocationPtr);
/* 659 */
EXTERN int		Tcl_UtfToExternalDStringEx(Tcl_Interp *interp,
				Tcl_Encoding encoding, const char *src,
				Tcl_Size srcLen, int flags,
				Tcl_DString *dsPtr,
				Tcl_Size *errorLocationPtr);
/* 660 */
EXTERN int		Tcl_AsyncMarkFromSignal(Tcl_AsyncHandler async,
				int sigNumber);
/* 661 */
EXTERN int		Tcl_ListObjGetElements(Tcl_Interp *interp,
				Tcl_Obj *listPtr, Tcl_Size *objcPtr,
				Tcl_Obj ***objvPtr);
/* 662 */
EXTERN int		Tcl_ListObjLength(Tcl_Interp *interp,
				Tcl_Obj *listPtr, Tcl_Size *lengthPtr);
/* 663 */
EXTERN int		Tcl_DictObjSize(Tcl_Interp *interp, Tcl_Obj *dictPtr,
				Tcl_Size *sizePtr);
/* 664 */
EXTERN int		Tcl_SplitList(Tcl_Interp *interp,
				const char *listStr, Tcl_Size *argcPtr,
				const char ***argvPtr);
/* 665 */
EXTERN void		Tcl_SplitPath(const char *path, Tcl_Size *argcPtr,
				const char ***argvPtr);
/* 666 */
EXTERN Tcl_Obj *	Tcl_FSSplitPath(Tcl_Obj *pathPtr, Tcl_Size *lenPtr);
/* 667 */
EXTERN int		Tcl_ParseArgsObjv(Tcl_Interp *interp,
				const Tcl_ArgvInfo *argTable,
				Tcl_Size *objcPtr, Tcl_Obj *const *objv,
				Tcl_Obj ***remObjv);
/* 668 */
EXTERN Tcl_Size		Tcl_UniCharLen(const int *uniStr);
/* 669 */
EXTERN Tcl_Size		Tcl_NumUtfChars(const char *src, Tcl_Size length);
/* 670 */
EXTERN Tcl_Size		Tcl_GetCharLength(Tcl_Obj *objPtr);
/* 671 */
EXTERN const char *	Tcl_UtfAtIndex(const char *src, Tcl_Size index);
/* 672 */
EXTERN Tcl_Obj *	Tcl_GetRange(Tcl_Obj *objPtr, Tcl_Size first,
				Tcl_Size last);
/* 673 */
EXTERN int		Tcl_GetUniChar(Tcl_Obj *objPtr, Tcl_Size index);
/* 674 */
EXTERN int		Tcl_GetBool(Tcl_Interp *interp, const char *src,
				int flags, char *charPtr);
/* 675 */
EXTERN int		Tcl_GetBoolFromObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, int flags, char *charPtr);
/* 676 */
EXTERN Tcl_Command	Tcl_CreateObjCommand2(Tcl_Interp *interp,
				const char *cmdName, Tcl_ObjCmdProc2 *proc2,
				void *clientData,
				Tcl_CmdDeleteProc *deleteProc);
/* 677 */
EXTERN Tcl_Trace	Tcl_CreateObjTrace2(Tcl_Interp *interp,
				Tcl_Size level, int flags,
				Tcl_CmdObjTraceProc2 *objProc2,
				void *clientData,
				Tcl_CmdObjTraceDeleteProc *delProc);
/* 678 */
EXTERN Tcl_Command	Tcl_NRCreateCommand2(Tcl_Interp *interp,
				const char *cmdName, Tcl_ObjCmdProc2 *proc,
				Tcl_ObjCmdProc2 *nreProc2, void *clientData,
				Tcl_CmdDeleteProc *deleteProc);
/* 679 */
EXTERN int		Tcl_NRCallObjProc2(Tcl_Interp *interp,
				Tcl_ObjCmdProc2 *objProc2, void *clientData,
				Tcl_Size objc, Tcl_Obj *const objv[]);
/* 680 */
EXTERN int		Tcl_GetNumberFromObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, void **clientDataPtr,
				int *typePtr);
/* 681 */
EXTERN int		Tcl_GetNumber(Tcl_Interp *interp, const char *bytes,
				Tcl_Size numBytes, void **clientDataPtr,
				int *typePtr);
/* 682 */
EXTERN int		Tcl_RemoveChannelMode(Tcl_Interp *interp,
				Tcl_Channel chan, int mode);
/* 683 */
EXTERN Tcl_Size		Tcl_GetEncodingNulLength(Tcl_Encoding encoding);
/* 684 */
EXTERN int		Tcl_GetWideUIntFromObj(Tcl_Interp *interp,
				Tcl_Obj *objPtr, Tcl_WideUInt *uwidePtr);
/* 685 */
EXTERN Tcl_Obj *	Tcl_DStringToObj(Tcl_DString *dsPtr);
/* 686 */
EXTERN int		Tcl_UtfNcmp(const char *s1, const char *s2, size_t n);
/* 687 */
EXTERN int		Tcl_UtfNcasecmp(const char *s1, const char *s2,
				size_t n);
/* 688 */
EXTERN Tcl_Obj *	Tcl_NewWideUIntObj(Tcl_WideUInt wideValue);
/* 689 */
EXTERN void		Tcl_SetWideUIntObj(Tcl_Obj *objPtr,
				Tcl_WideUInt uwideValue);
/* 690 */
EXTERN void		TclUnusedStubEntry(void);

typedef struct {
    const struct TclPlatStubs *tclPlatStubs;
    const struct TclIntStubs *tclIntStubs;
    const struct TclIntPlatStubs *tclIntPlatStubs;
} TclStubHooks;

typedef struct TclStubs {
    int magic;
    const TclStubHooks *hooks;

    int (*tcl_PkgProvideEx) (Tcl_Interp *interp, const char *name, const char *version, const void *clientData); /* 0 */
    const char * (*tcl_PkgRequireEx) (Tcl_Interp *interp, const char *name, const char *version, int exact, void *clientDataPtr); /* 1 */
    TCL_NORETURN1 void (*tcl_Panic) (const char *format, ...) TCL_FORMAT_PRINTF(1, 2); /* 2 */
    void * (*tcl_Alloc) (TCL_HASH_TYPE size); /* 3 */
    void (*tcl_Free) (void *ptr); /* 4 */
    void * (*tcl_Realloc) (void *ptr, TCL_HASH_TYPE size); /* 5 */
    void * (*tcl_DbCkalloc) (TCL_HASH_TYPE size, const char *file, int line); /* 6 */
    void (*tcl_DbCkfree) (void *ptr, const char *file, int line); /* 7 */
    void * (*tcl_DbCkrealloc) (void *ptr, TCL_HASH_TYPE size, const char *file, int line); /* 8 */
    void (*tcl_CreateFileHandler) (int fd, int mask, Tcl_FileProc *proc, void *clientData); /* 9 */
    void (*tcl_DeleteFileHandler) (int fd); /* 10 */
    void (*tcl_SetTimer) (const Tcl_Time *timePtr); /* 11 */
    void (*tcl_Sleep) (int ms); /* 12 */
    int (*tcl_WaitForEvent) (const Tcl_Time *timePtr); /* 13 */
    int (*tcl_AppendAllObjTypes) (Tcl_Interp *interp, Tcl_Obj *objPtr); /* 14 */
    void (*tcl_AppendStringsToObj) (Tcl_Obj *objPtr, ...); /* 15 */
    void (*tcl_AppendToObj) (Tcl_Obj *objPtr, const char *bytes, Tcl_Size length); /* 16 */
    Tcl_Obj * (*tcl_ConcatObj) (Tcl_Size objc, Tcl_Obj *const objv[]); /* 17 */
    int (*tcl_ConvertToType) (Tcl_Interp *interp, Tcl_Obj *objPtr, const Tcl_ObjType *typePtr); /* 18 */
    void (*tcl_DbDecrRefCount) (Tcl_Obj *objPtr, const char *file, int line); /* 19 */
    void (*tcl_DbIncrRefCount) (Tcl_Obj *objPtr, const char *file, int line); /* 20 */
    int (*tcl_DbIsShared) (Tcl_Obj *objPtr, const char *file, int line); /* 21 */
    void (*reserved22)(void);
    Tcl_Obj * (*tcl_DbNewByteArrayObj) (const unsigned char *bytes, Tcl_Size numBytes, const char *file, int line); /* 23 */
    Tcl_Obj * (*tcl_DbNewDoubleObj) (double doubleValue, const char *file, int line); /* 24 */
    Tcl_Obj * (*tcl_DbNewListObj) (Tcl_Size objc, Tcl_Obj *const *objv, const char *file, int line); /* 25 */
    void (*reserved26)(void);
    Tcl_Obj * (*tcl_DbNewObj) (const char *file, int line); /* 27 */
    Tcl_Obj * (*tcl_DbNewStringObj) (const char *bytes, Tcl_Size length, const char *file, int line); /* 28 */
    Tcl_Obj * (*tcl_DuplicateObj) (Tcl_Obj *objPtr); /* 29 */
    void (*tclFreeObj) (Tcl_Obj *objPtr); /* 30 */
    int (*tcl_GetBoolean) (Tcl_Interp *interp, const char *src, int *intPtr); /* 31 */
    int (*tcl_GetBooleanFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int *intPtr); /* 32 */
    unsigned char * (*tcl_GetByteArrayFromObj) (Tcl_Obj *objPtr, Tcl_Size *numBytesPtr); /* 33 */
    int (*tcl_GetDouble) (Tcl_Interp *interp, const char *src, double *doublePtr); /* 34 */
    int (*tcl_GetDoubleFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, double *doublePtr); /* 35 */
    void (*reserved36)(void);
    int (*tcl_GetInt) (Tcl_Interp *interp, const char *src, int *intPtr); /* 37 */
    int (*tcl_GetIntFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int *intPtr); /* 38 */
    int (*tcl_GetLongFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, long *longPtr); /* 39 */
    const Tcl_ObjType * (*tcl_GetObjType) (const char *typeName); /* 40 */
    char * (*tclGetStringFromObj) (Tcl_Obj *objPtr, void *lengthPtr); /* 41 */
    void (*tcl_InvalidateStringRep) (Tcl_Obj *objPtr); /* 42 */
    int (*tcl_ListObjAppendList) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Obj *elemListPtr); /* 43 */
    int (*tcl_ListObjAppendElement) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Obj *objPtr); /* 44 */
    int (*tclListObjGetElements) (Tcl_Interp *interp, Tcl_Obj *listPtr, void *objcPtr, Tcl_Obj ***objvPtr); /* 45 */
    int (*tcl_ListObjIndex) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size index, Tcl_Obj **objPtrPtr); /* 46 */
    int (*tclListObjLength) (Tcl_Interp *interp, Tcl_Obj *listPtr, void *lengthPtr); /* 47 */
    int (*tcl_ListObjReplace) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size first, Tcl_Size count, Tcl_Size objc, Tcl_Obj *const objv[]); /* 48 */
    void (*reserved49)(void);
    Tcl_Obj * (*tcl_NewByteArrayObj) (const unsigned char *bytes, Tcl_Size numBytes); /* 50 */
    Tcl_Obj * (*tcl_NewDoubleObj) (double doubleValue); /* 51 */
    void (*reserved52)(void);
    Tcl_Obj * (*tcl_NewListObj) (Tcl_Size objc, Tcl_Obj *const objv[]); /* 53 */
    void (*reserved54)(void);
    Tcl_Obj * (*tcl_NewObj) (void); /* 55 */
    Tcl_Obj * (*tcl_NewStringObj) (const char *bytes, Tcl_Size length); /* 56 */
    void (*reserved57)(void);
    unsigned char * (*tcl_SetByteArrayLength) (Tcl_Obj *objPtr, Tcl_Size numBytes); /* 58 */
    void (*tcl_SetByteArrayObj) (Tcl_Obj *objPtr, const unsigned char *bytes, Tcl_Size numBytes); /* 59 */
    void (*tcl_SetDoubleObj) (Tcl_Obj *objPtr, double doubleValue); /* 60 */
    void (*reserved61)(void);
    void (*tcl_SetListObj) (Tcl_Obj *objPtr, Tcl_Size objc, Tcl_Obj *const objv[]); /* 62 */
    void (*reserved63)(void);
    void (*tcl_SetObjLength) (Tcl_Obj *objPtr, Tcl_Size length); /* 64 */
    void (*tcl_SetStringObj) (Tcl_Obj *objPtr, const char *bytes, Tcl_Size length); /* 65 */
    void (*reserved66)(void);
    void (*reserved67)(void);
    void (*tcl_AllowExceptions) (Tcl_Interp *interp); /* 68 */
    void (*tcl_AppendElement) (Tcl_Interp *interp, const char *element); /* 69 */
    void (*tcl_AppendResult) (Tcl_Interp *interp, ...); /* 70 */
    Tcl_AsyncHandler (*tcl_AsyncCreate) (Tcl_AsyncProc *proc, void *clientData); /* 71 */
    void (*tcl_AsyncDelete) (Tcl_AsyncHandler async); /* 72 */
    int (*tcl_AsyncInvoke) (Tcl_Interp *interp, int code); /* 73 */
    void (*tcl_AsyncMark) (Tcl_AsyncHandler async); /* 74 */
    int (*tcl_AsyncReady) (void); /* 75 */
    void (*reserved76)(void);
    void (*reserved77)(void);
    int (*tcl_BadChannelOption) (Tcl_Interp *interp, const char *optionName, const char *optionList); /* 78 */
    void (*tcl_CallWhenDeleted) (Tcl_Interp *interp, Tcl_InterpDeleteProc *proc, void *clientData); /* 79 */
    void (*tcl_CancelIdleCall) (Tcl_IdleProc *idleProc, void *clientData); /* 80 */
    int (*tcl_Close) (Tcl_Interp *interp, Tcl_Channel chan); /* 81 */
    int (*tcl_CommandComplete) (const char *cmd); /* 82 */
    char * (*tcl_Concat) (Tcl_Size argc, const char *const *argv); /* 83 */
    Tcl_Size (*tcl_ConvertElement) (const char *src, char *dst, int flags); /* 84 */
    Tcl_Size (*tcl_ConvertCountedElement) (const char *src, Tcl_Size length, char *dst, int flags); /* 85 */
    int (*tcl_CreateAlias) (Tcl_Interp *childInterp, const char *childCmd, Tcl_Interp *target, const char *targetCmd, Tcl_Size argc, const char *const *argv); /* 86 */
    int (*tcl_CreateAliasObj) (Tcl_Interp *childInterp, const char *childCmd, Tcl_Interp *target, const char *targetCmd, Tcl_Size objc, Tcl_Obj *const objv[]); /* 87 */
    Tcl_Channel (*tcl_CreateChannel) (const Tcl_ChannelType *typePtr, const char *chanName, void *instanceData, int mask); /* 88 */
    void (*tcl_CreateChannelHandler) (Tcl_Channel chan, int mask, Tcl_ChannelProc *proc, void *clientData); /* 89 */
    void (*tcl_CreateCloseHandler) (Tcl_Channel chan, Tcl_CloseProc *proc, void *clientData); /* 90 */
    Tcl_Command (*tcl_CreateCommand) (Tcl_Interp *interp, const char *cmdName, Tcl_CmdProc *proc, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 91 */
    void (*tcl_CreateEventSource) (Tcl_EventSetupProc *setupProc, Tcl_EventCheckProc *checkProc, void *clientData); /* 92 */
    void (*tcl_CreateExitHandler) (Tcl_ExitProc *proc, void *clientData); /* 93 */
    Tcl_Interp * (*tcl_CreateInterp) (void); /* 94 */
    void (*reserved95)(void);
    Tcl_Command (*tcl_CreateObjCommand) (Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc *proc, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 96 */
    Tcl_Interp * (*tcl_CreateChild) (Tcl_Interp *interp, const char *name, int isSafe); /* 97 */
    Tcl_TimerToken (*tcl_CreateTimerHandler) (int milliseconds, Tcl_TimerProc *proc, void *clientData); /* 98 */
    Tcl_Trace (*tcl_CreateTrace) (Tcl_Interp *interp, Tcl_Size level, Tcl_CmdTraceProc *proc, void *clientData); /* 99 */
    void (*tcl_DeleteAssocData) (Tcl_Interp *interp, const char *name); /* 100 */
    void (*tcl_DeleteChannelHandler) (Tcl_Channel chan, Tcl_ChannelProc *proc, void *clientData); /* 101 */
    void (*tcl_DeleteCloseHandler) (Tcl_Channel chan, Tcl_CloseProc *proc, void *clientData); /* 102 */
    int (*tcl_DeleteCommand) (Tcl_Interp *interp, const char *cmdName); /* 103 */
    int (*tcl_DeleteCommandFromToken) (Tcl_Interp *interp, Tcl_Command command); /* 104 */
    void (*tcl_DeleteEvents) (Tcl_EventDeleteProc *proc, void *clientData); /* 105 */
    void (*tcl_DeleteEventSource) (Tcl_EventSetupProc *setupProc, Tcl_EventCheckProc *checkProc, void *clientData); /* 106 */
    void (*tcl_DeleteExitHandler) (Tcl_ExitProc *proc, void *clientData); /* 107 */
    void (*tcl_DeleteHashEntry) (Tcl_HashEntry *entryPtr); /* 108 */
    void (*tcl_DeleteHashTable) (Tcl_HashTable *tablePtr); /* 109 */
    void (*tcl_DeleteInterp) (Tcl_Interp *interp); /* 110 */
    void (*tcl_DetachPids) (Tcl_Size numPids, Tcl_Pid *pidPtr); /* 111 */
    void (*tcl_DeleteTimerHandler) (Tcl_TimerToken token); /* 112 */
    void (*tcl_DeleteTrace) (Tcl_Interp *interp, Tcl_Trace trace); /* 113 */
    void (*tcl_DontCallWhenDeleted) (Tcl_Interp *interp, Tcl_InterpDeleteProc *proc, void *clientData); /* 114 */
    int (*tcl_DoOneEvent) (int flags); /* 115 */
    void (*tcl_DoWhenIdle) (Tcl_IdleProc *proc, void *clientData); /* 116 */
    char * (*tcl_DStringAppend) (Tcl_DString *dsPtr, const char *bytes, Tcl_Size length); /* 117 */
    char * (*tcl_DStringAppendElement) (Tcl_DString *dsPtr, const char *element); /* 118 */
    void (*tcl_DStringEndSublist) (Tcl_DString *dsPtr); /* 119 */
    void (*tcl_DStringFree) (Tcl_DString *dsPtr); /* 120 */
    void (*tcl_DStringGetResult) (Tcl_Interp *interp, Tcl_DString *dsPtr); /* 121 */
    void (*tcl_DStringInit) (Tcl_DString *dsPtr); /* 122 */
    void (*tcl_DStringResult) (Tcl_Interp *interp, Tcl_DString *dsPtr); /* 123 */
    void (*tcl_DStringSetLength) (Tcl_DString *dsPtr, Tcl_Size length); /* 124 */
    void (*tcl_DStringStartSublist) (Tcl_DString *dsPtr); /* 125 */
    int (*tcl_Eof) (Tcl_Channel chan); /* 126 */
    const char * (*tcl_ErrnoId) (void); /* 127 */
    const char * (*tcl_ErrnoMsg) (int err); /* 128 */
    void (*reserved129)(void);
    int (*tcl_EvalFile) (Tcl_Interp *interp, const char *fileName); /* 130 */
    void (*reserved131)(void);
    void (*tcl_EventuallyFree) (void *clientData, Tcl_FreeProc *freeProc); /* 132 */
    TCL_NORETURN1 void (*tcl_Exit) (int status); /* 133 */
    int (*tcl_ExposeCommand) (Tcl_Interp *interp, const char *hiddenCmdToken, const char *cmdName); /* 134 */
    int (*tcl_ExprBoolean) (Tcl_Interp *interp, const char *expr, int *ptr); /* 135 */
    int (*tcl_ExprBooleanObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int *ptr); /* 136 */
    int (*tcl_ExprDouble) (Tcl_Interp *interp, const char *expr, double *ptr); /* 137 */
    int (*tcl_ExprDoubleObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, double *ptr); /* 138 */
    int (*tcl_ExprLong) (Tcl_Interp *interp, const char *expr, long *ptr); /* 139 */
    int (*tcl_ExprLongObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, long *ptr); /* 140 */
    int (*tcl_ExprObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj **resultPtrPtr); /* 141 */
    int (*tcl_ExprString) (Tcl_Interp *interp, const char *expr); /* 142 */
    void (*tcl_Finalize) (void); /* 143 */
    void (*reserved144)(void);
    Tcl_HashEntry * (*tcl_FirstHashEntry) (Tcl_HashTable *tablePtr, Tcl_HashSearch *searchPtr); /* 145 */
    int (*tcl_Flush) (Tcl_Channel chan); /* 146 */
    void (*reserved147)(void);
    void (*reserved148)(void);
    int (*tclGetAliasObj) (Tcl_Interp *interp, const char *childCmd, Tcl_Interp **targetInterpPtr, const char **targetCmdPtr, int *objcPtr, Tcl_Obj ***objvPtr); /* 149 */
    void * (*tcl_GetAssocData) (Tcl_Interp *interp, const char *name, Tcl_InterpDeleteProc **procPtr); /* 150 */
    Tcl_Channel (*tcl_GetChannel) (Tcl_Interp *interp, const char *chanName, int *modePtr); /* 151 */
    Tcl_Size (*tcl_GetChannelBufferSize) (Tcl_Channel chan); /* 152 */
    int (*tcl_GetChannelHandle) (Tcl_Channel chan, int direction, void **handlePtr); /* 153 */
    void * (*tcl_GetChannelInstanceData) (Tcl_Channel chan); /* 154 */
    int (*tcl_GetChannelMode) (Tcl_Channel chan); /* 155 */
    const char * (*tcl_GetChannelName) (Tcl_Channel chan); /* 156 */
    int (*tcl_GetChannelOption) (Tcl_Interp *interp, Tcl_Channel chan, const char *optionName, Tcl_DString *dsPtr); /* 157 */
    const Tcl_ChannelType * (*tcl_GetChannelType) (Tcl_Channel chan); /* 158 */
    int (*tcl_GetCommandInfo) (Tcl_Interp *interp, const char *cmdName, Tcl_CmdInfo *infoPtr); /* 159 */
    const char * (*tcl_GetCommandName) (Tcl_Interp *interp, Tcl_Command command); /* 160 */
    int (*tcl_GetErrno) (void); /* 161 */
    const char * (*tcl_GetHostName) (void); /* 162 */
    int (*tcl_GetInterpPath) (Tcl_Interp *interp, Tcl_Interp *childInterp); /* 163 */
    Tcl_Interp * (*tcl_GetParent) (Tcl_Interp *interp); /* 164 */
    const char * (*tcl_GetNameOfExecutable) (void); /* 165 */
    Tcl_Obj * (*tcl_GetObjResult) (Tcl_Interp *interp); /* 166 */
    int (*tcl_GetOpenFile) (Tcl_Interp *interp, const char *chanID, int forWriting, int checkUsage, void **filePtr); /* 167 */
    Tcl_PathType (*tcl_GetPathType) (const char *path); /* 168 */
    Tcl_Size (*tcl_Gets) (Tcl_Channel chan, Tcl_DString *dsPtr); /* 169 */
    Tcl_Size (*tcl_GetsObj) (Tcl_Channel chan, Tcl_Obj *objPtr); /* 170 */
    int (*tcl_GetServiceMode) (void); /* 171 */
    Tcl_Interp * (*tcl_GetChild) (Tcl_Interp *interp, const char *name); /* 172 */
    Tcl_Channel (*tcl_GetStdChannel) (int type); /* 173 */
    void (*reserved174)(void);
    void (*reserved175)(void);
    const char * (*tcl_GetVar2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags); /* 176 */
    void (*reserved177)(void);
    void (*reserved178)(void);
    int (*tcl_HideCommand) (Tcl_Interp *interp, const char *cmdName, const char *hiddenCmdToken); /* 179 */
    int (*tcl_Init) (Tcl_Interp *interp); /* 180 */
    void (*tcl_InitHashTable) (Tcl_HashTable *tablePtr, int keyType); /* 181 */
    int (*tcl_InputBlocked) (Tcl_Channel chan); /* 182 */
    int (*tcl_InputBuffered) (Tcl_Channel chan); /* 183 */
    int (*tcl_InterpDeleted) (Tcl_Interp *interp); /* 184 */
    int (*tcl_IsSafe) (Tcl_Interp *interp); /* 185 */
    char * (*tcl_JoinPath) (Tcl_Size argc, const char *const *argv, Tcl_DString *resultPtr); /* 186 */
    int (*tcl_LinkVar) (Tcl_Interp *interp, const char *varName, void *addr, int type); /* 187 */
    void (*reserved188)(void);
    Tcl_Channel (*tcl_MakeFileChannel) (void *handle, int mode); /* 189 */
    void (*reserved190)(void);
    Tcl_Channel (*tcl_MakeTcpClientChannel) (void *tcpSocket); /* 191 */
    char * (*tcl_Merge) (Tcl_Size argc, const char *const *argv); /* 192 */
    Tcl_HashEntry * (*tcl_NextHashEntry) (Tcl_HashSearch *searchPtr); /* 193 */
    void (*tcl_NotifyChannel) (Tcl_Channel channel, int mask); /* 194 */
    Tcl_Obj * (*tcl_ObjGetVar2) (Tcl_Interp *interp, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, int flags); /* 195 */
    Tcl_Obj * (*tcl_ObjSetVar2) (Tcl_Interp *interp, Tcl_Obj *part1Ptr, Tcl_Obj *part2Ptr, Tcl_Obj *newValuePtr, int flags); /* 196 */
    Tcl_Channel (*tcl_OpenCommandChannel) (Tcl_Interp *interp, Tcl_Size argc, const char **argv, int flags); /* 197 */
    Tcl_Channel (*tcl_OpenFileChannel) (Tcl_Interp *interp, const char *fileName, const char *modeString, int permissions); /* 198 */
    Tcl_Channel (*tcl_OpenTcpClient) (Tcl_Interp *interp, int port, const char *address, const char *myaddr, int myport, int flags); /* 199 */
    Tcl_Channel (*tcl_OpenTcpServer) (Tcl_Interp *interp, int port, const char *host, Tcl_TcpAcceptProc *acceptProc, void *callbackData); /* 200 */
    void (*tcl_Preserve) (void *data); /* 201 */
    void (*tcl_PrintDouble) (Tcl_Interp *interp, double value, char *dst); /* 202 */
    int (*tcl_PutEnv) (const char *assignment); /* 203 */
    const char * (*tcl_PosixError) (Tcl_Interp *interp); /* 204 */
    void (*tcl_QueueEvent) (Tcl_Event *evPtr, int position); /* 205 */
    Tcl_Size (*tcl_Read) (Tcl_Channel chan, char *bufPtr, Tcl_Size toRead); /* 206 */
    void (*tcl_ReapDetachedProcs) (void); /* 207 */
    int (*tcl_RecordAndEval) (Tcl_Interp *interp, const char *cmd, int flags); /* 208 */
    int (*tcl_RecordAndEvalObj) (Tcl_Interp *interp, Tcl_Obj *cmdPtr, int flags); /* 209 */
    void (*tcl_RegisterChannel) (Tcl_Interp *interp, Tcl_Channel chan); /* 210 */
    void (*tcl_RegisterObjType) (const Tcl_ObjType *typePtr); /* 211 */
    Tcl_RegExp (*tcl_RegExpCompile) (Tcl_Interp *interp, const char *pattern); /* 212 */
    int (*tcl_RegExpExec) (Tcl_Interp *interp, Tcl_RegExp regexp, const char *text, const char *start); /* 213 */
    int (*tcl_RegExpMatch) (Tcl_Interp *interp, const char *text, const char *pattern); /* 214 */
    void (*tcl_RegExpRange) (Tcl_RegExp regexp, Tcl_Size index, const char **startPtr, const char **endPtr); /* 215 */
    void (*tcl_Release) (void *clientData); /* 216 */
    void (*tcl_ResetResult) (Tcl_Interp *interp); /* 217 */
    Tcl_Size (*tcl_ScanElement) (const char *src, int *flagPtr); /* 218 */
    Tcl_Size (*tcl_ScanCountedElement) (const char *src, Tcl_Size length, int *flagPtr); /* 219 */
    void (*reserved220)(void);
    int (*tcl_ServiceAll) (void); /* 221 */
    int (*tcl_ServiceEvent) (int flags); /* 222 */
    void (*tcl_SetAssocData) (Tcl_Interp *interp, const char *name, Tcl_InterpDeleteProc *proc, void *clientData); /* 223 */
    void (*tcl_SetChannelBufferSize) (Tcl_Channel chan, Tcl_Size sz); /* 224 */
    int (*tcl_SetChannelOption) (Tcl_Interp *interp, Tcl_Channel chan, const char *optionName, const char *newValue); /* 225 */
    int (*tcl_SetCommandInfo) (Tcl_Interp *interp, const char *cmdName, const Tcl_CmdInfo *infoPtr); /* 226 */
    void (*tcl_SetErrno) (int err); /* 227 */
    void (*tcl_SetErrorCode) (Tcl_Interp *interp, ...); /* 228 */
    void (*tcl_SetMaxBlockTime) (const Tcl_Time *timePtr); /* 229 */
    void (*reserved230)(void);
    Tcl_Size (*tcl_SetRecursionLimit) (Tcl_Interp *interp, Tcl_Size depth); /* 231 */
    void (*reserved232)(void);
    int (*tcl_SetServiceMode) (int mode); /* 233 */
    void (*tcl_SetObjErrorCode) (Tcl_Interp *interp, Tcl_Obj *errorObjPtr); /* 234 */
    void (*tcl_SetObjResult) (Tcl_Interp *interp, Tcl_Obj *resultObjPtr); /* 235 */
    void (*tcl_SetStdChannel) (Tcl_Channel channel, int type); /* 236 */
    void (*reserved237)(void);
    const char * (*tcl_SetVar2) (Tcl_Interp *interp, const char *part1, const char *part2, const char *newValue, int flags); /* 238 */
    const char * (*tcl_SignalId) (int sig); /* 239 */
    const char * (*tcl_SignalMsg) (int sig); /* 240 */
    void (*tcl_SourceRCFile) (Tcl_Interp *interp); /* 241 */
    int (*tclSplitList) (Tcl_Interp *interp, const char *listStr, void *argcPtr, const char ***argvPtr); /* 242 */
    void (*tclSplitPath) (const char *path, void *argcPtr, const char ***argvPtr); /* 243 */
    void (*reserved244)(void);
    void (*reserved245)(void);
    void (*reserved246)(void);
    void (*reserved247)(void);
    int (*tcl_TraceVar2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *proc, void *clientData); /* 248 */
    char * (*tcl_TranslateFileName) (Tcl_Interp *interp, const char *name, Tcl_DString *bufferPtr); /* 249 */
    Tcl_Size (*tcl_Ungets) (Tcl_Channel chan, const char *str, Tcl_Size len, int atHead); /* 250 */
    void (*tcl_UnlinkVar) (Tcl_Interp *interp, const char *varName); /* 251 */
    int (*tcl_UnregisterChannel) (Tcl_Interp *interp, Tcl_Channel chan); /* 252 */
    void (*reserved253)(void);
    int (*tcl_UnsetVar2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags); /* 254 */
    void (*reserved255)(void);
    void (*tcl_UntraceVar2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *proc, void *clientData); /* 256 */
    void (*tcl_UpdateLinkedVar) (Tcl_Interp *interp, const char *varName); /* 257 */
    void (*reserved258)(void);
    int (*tcl_UpVar2) (Tcl_Interp *interp, const char *frameName, const char *part1, const char *part2, const char *localName, int flags); /* 259 */
    int (*tcl_VarEval) (Tcl_Interp *interp, ...); /* 260 */
    void (*reserved261)(void);
    void * (*tcl_VarTraceInfo2) (Tcl_Interp *interp, const char *part1, const char *part2, int flags, Tcl_VarTraceProc *procPtr, void *prevClientData); /* 262 */
    Tcl_Size (*tcl_Write) (Tcl_Channel chan, const char *s, Tcl_Size slen); /* 263 */
    void (*tcl_WrongNumArgs) (Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], const char *message); /* 264 */
    int (*tcl_DumpActiveMemory) (const char *fileName); /* 265 */
    void (*tcl_ValidateAllMemory) (const char *file, int line); /* 266 */
    void (*reserved267)(void);
    void (*reserved268)(void);
    char * (*tcl_HashStats) (Tcl_HashTable *tablePtr); /* 269 */
    const char * (*tcl_ParseVar) (Tcl_Interp *interp, const char *start, const char **termPtr); /* 270 */
    void (*reserved271)(void);
    const char * (*tcl_PkgPresentEx) (Tcl_Interp *interp, const char *name, const char *version, int exact, void *clientDataPtr); /* 272 */
    void (*reserved273)(void);
    void (*reserved274)(void);
    void (*reserved275)(void);
    void (*reserved276)(void);
    Tcl_Pid (*tcl_WaitPid) (Tcl_Pid pid, int *statPtr, int options); /* 277 */
    void (*reserved278)(void);
    void (*tcl_GetVersion) (int *major, int *minor, int *patchLevel, int *type); /* 279 */
    void (*tcl_InitMemory) (Tcl_Interp *interp); /* 280 */
    Tcl_Channel (*tcl_StackChannel) (Tcl_Interp *interp, const Tcl_ChannelType *typePtr, void *instanceData, int mask, Tcl_Channel prevChan); /* 281 */
    int (*tcl_UnstackChannel) (Tcl_Interp *interp, Tcl_Channel chan); /* 282 */
    Tcl_Channel (*tcl_GetStackedChannel) (Tcl_Channel chan); /* 283 */
    void (*tcl_SetMainLoop) (Tcl_MainLoopProc *proc); /* 284 */
    int (*tcl_GetAliasObj) (Tcl_Interp *interp, const char *childCmd, Tcl_Interp **targetInterpPtr, const char **targetCmdPtr, Tcl_Size *objcPtr, Tcl_Obj ***objvPtr); /* 285 */
    void (*tcl_AppendObjToObj) (Tcl_Obj *objPtr, Tcl_Obj *appendObjPtr); /* 286 */
    Tcl_Encoding (*tcl_CreateEncoding) (const Tcl_EncodingType *typePtr); /* 287 */
    void (*tcl_CreateThreadExitHandler) (Tcl_ExitProc *proc, void *clientData); /* 288 */
    void (*tcl_DeleteThreadExitHandler) (Tcl_ExitProc *proc, void *clientData); /* 289 */
    void (*reserved290)(void);
    int (*tcl_EvalEx) (Tcl_Interp *interp, const char *script, Tcl_Size numBytes, int flags); /* 291 */
    int (*tcl_EvalObjv) (Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags); /* 292 */
    int (*tcl_EvalObjEx) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 293 */
    TCL_NORETURN1 void (*tcl_ExitThread) (int status); /* 294 */
    int (*tcl_ExternalToUtf) (Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, Tcl_Size dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr); /* 295 */
    char * (*tcl_ExternalToUtfDString) (Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, Tcl_DString *dsPtr); /* 296 */
    void (*tcl_FinalizeThread) (void); /* 297 */
    void (*tcl_FinalizeNotifier) (void *clientData); /* 298 */
    void (*tcl_FreeEncoding) (Tcl_Encoding encoding); /* 299 */
    Tcl_ThreadId (*tcl_GetCurrentThread) (void); /* 300 */
    Tcl_Encoding (*tcl_GetEncoding) (Tcl_Interp *interp, const char *name); /* 301 */
    const char * (*tcl_GetEncodingName) (Tcl_Encoding encoding); /* 302 */
    void (*tcl_GetEncodingNames) (Tcl_Interp *interp); /* 303 */
    int (*tcl_GetIndexFromObjStruct) (Tcl_Interp *interp, Tcl_Obj *objPtr, const void *tablePtr, Tcl_Size offset, const char *msg, int flags, void *indexPtr); /* 304 */
    void * (*tcl_GetThreadData) (Tcl_ThreadDataKey *keyPtr, Tcl_Size size); /* 305 */
    Tcl_Obj * (*tcl_GetVar2Ex) (Tcl_Interp *interp, const char *part1, const char *part2, int flags); /* 306 */
    void * (*tcl_InitNotifier) (void); /* 307 */
    void (*tcl_MutexLock) (Tcl_Mutex *mutexPtr); /* 308 */
    void (*tcl_MutexUnlock) (Tcl_Mutex *mutexPtr); /* 309 */
    void (*tcl_ConditionNotify) (Tcl_Condition *condPtr); /* 310 */
    void (*tcl_ConditionWait) (Tcl_Condition *condPtr, Tcl_Mutex *mutexPtr, const Tcl_Time *timePtr); /* 311 */
    Tcl_Size (*tclNumUtfChars) (const char *src, Tcl_Size length); /* 312 */
    Tcl_Size (*tcl_ReadChars) (Tcl_Channel channel, Tcl_Obj *objPtr, Tcl_Size charsToRead, int appendFlag); /* 313 */
    void (*reserved314)(void);
    void (*reserved315)(void);
    int (*tcl_SetSystemEncoding) (Tcl_Interp *interp, const char *name); /* 316 */
    Tcl_Obj * (*tcl_SetVar2Ex) (Tcl_Interp *interp, const char *part1, const char *part2, Tcl_Obj *newValuePtr, int flags); /* 317 */
    void (*tcl_ThreadAlert) (Tcl_ThreadId threadId); /* 318 */
    void (*tcl_ThreadQueueEvent) (Tcl_ThreadId threadId, Tcl_Event *evPtr, int position); /* 319 */
    int (*tcl_UniCharAtIndex) (const char *src, Tcl_Size index); /* 320 */
    int (*tcl_UniCharToLower) (int ch); /* 321 */
    int (*tcl_UniCharToTitle) (int ch); /* 322 */
    int (*tcl_UniCharToUpper) (int ch); /* 323 */
    Tcl_Size (*tcl_UniCharToUtf) (int ch, char *buf); /* 324 */
    const char * (*tclUtfAtIndex) (const char *src, Tcl_Size index); /* 325 */
    int (*tclUtfCharComplete) (const char *src, Tcl_Size length); /* 326 */
    Tcl_Size (*tcl_UtfBackslash) (const char *src, int *readPtr, char *dst); /* 327 */
    const char * (*tcl_UtfFindFirst) (const char *src, int ch); /* 328 */
    const char * (*tcl_UtfFindLast) (const char *src, int ch); /* 329 */
    const char * (*tclUtfNext) (const char *src); /* 330 */
    const char * (*tclUtfPrev) (const char *src, const char *start); /* 331 */
    int (*tcl_UtfToExternal) (Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_EncodingState *statePtr, char *dst, Tcl_Size dstLen, int *srcReadPtr, int *dstWrotePtr, int *dstCharsPtr); /* 332 */
    char * (*tcl_UtfToExternalDString) (Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, Tcl_DString *dsPtr); /* 333 */
    Tcl_Size (*tcl_UtfToLower) (char *src); /* 334 */
    Tcl_Size (*tcl_UtfToTitle) (char *src); /* 335 */
    Tcl_Size (*tcl_UtfToChar16) (const char *src, unsigned short *chPtr); /* 336 */
    Tcl_Size (*tcl_UtfToUpper) (char *src); /* 337 */
    Tcl_Size (*tcl_WriteChars) (Tcl_Channel chan, const char *src, Tcl_Size srcLen); /* 338 */
    Tcl_Size (*tcl_WriteObj) (Tcl_Channel chan, Tcl_Obj *objPtr); /* 339 */
    char * (*tcl_GetString) (Tcl_Obj *objPtr); /* 340 */
    void (*reserved341)(void);
    void (*reserved342)(void);
    void (*tcl_AlertNotifier) (void *clientData); /* 343 */
    void (*tcl_ServiceModeHook) (int mode); /* 344 */
    int (*tcl_UniCharIsAlnum) (int ch); /* 345 */
    int (*tcl_UniCharIsAlpha) (int ch); /* 346 */
    int (*tcl_UniCharIsDigit) (int ch); /* 347 */
    int (*tcl_UniCharIsLower) (int ch); /* 348 */
    int (*tcl_UniCharIsSpace) (int ch); /* 349 */
    int (*tcl_UniCharIsUpper) (int ch); /* 350 */
    int (*tcl_UniCharIsWordChar) (int ch); /* 351 */
    Tcl_Size (*tcl_Char16Len) (const unsigned short *uniStr); /* 352 */
    void (*reserved353)(void);
    char * (*tcl_Char16ToUtfDString) (const unsigned short *uniStr, Tcl_Size uniLength, Tcl_DString *dsPtr); /* 354 */
    unsigned short * (*tcl_UtfToChar16DString) (const char *src, Tcl_Size length, Tcl_DString *dsPtr); /* 355 */
    Tcl_RegExp (*tcl_GetRegExpFromObj) (Tcl_Interp *interp, Tcl_Obj *patObj, int flags); /* 356 */
    void (*reserved357)(void);
    void (*tcl_FreeParse) (Tcl_Parse *parsePtr); /* 358 */
    void (*tcl_LogCommandInfo) (Tcl_Interp *interp, const char *script, const char *command, Tcl_Size length); /* 359 */
    int (*tcl_ParseBraces) (Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr, int append, const char **termPtr); /* 360 */
    int (*tcl_ParseCommand) (Tcl_Interp *interp, const char *start, Tcl_Size numBytes, int nested, Tcl_Parse *parsePtr); /* 361 */
    int (*tcl_ParseExpr) (Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr); /* 362 */
    int (*tcl_ParseQuotedString) (Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr, int append, const char **termPtr); /* 363 */
    int (*tcl_ParseVarName) (Tcl_Interp *interp, const char *start, Tcl_Size numBytes, Tcl_Parse *parsePtr, int append); /* 364 */
    char * (*tcl_GetCwd) (Tcl_Interp *interp, Tcl_DString *cwdPtr); /* 365 */
    int (*tcl_Chdir) (const char *dirName); /* 366 */
    int (*tcl_Access) (const char *path, int mode); /* 367 */
    int (*tcl_Stat) (const char *path, struct stat *bufPtr); /* 368 */
    int (*tclUtfNcmp) (const char *s1, const char *s2, size_t n); /* 369 */
    int (*tclUtfNcasecmp) (const char *s1, const char *s2, size_t n); /* 370 */
    int (*tcl_StringCaseMatch) (const char *str, const char *pattern, int nocase); /* 371 */
    int (*tcl_UniCharIsControl) (int ch); /* 372 */
    int (*tcl_UniCharIsGraph) (int ch); /* 373 */
    int (*tcl_UniCharIsPrint) (int ch); /* 374 */
    int (*tcl_UniCharIsPunct) (int ch); /* 375 */
    int (*tcl_RegExpExecObj) (Tcl_Interp *interp, Tcl_RegExp regexp, Tcl_Obj *textObj, Tcl_Size offset, Tcl_Size nmatches, int flags); /* 376 */
    void (*tcl_RegExpGetInfo) (Tcl_RegExp regexp, Tcl_RegExpInfo *infoPtr); /* 377 */
    Tcl_Obj * (*tcl_NewUnicodeObj) (const Tcl_UniChar *unicode, Tcl_Size numChars); /* 378 */
    void (*tcl_SetUnicodeObj) (Tcl_Obj *objPtr, const Tcl_UniChar *unicode, Tcl_Size numChars); /* 379 */
    Tcl_Size (*tclGetCharLength) (Tcl_Obj *objPtr); /* 380 */
    int (*tclGetUniChar) (Tcl_Obj *objPtr, Tcl_Size index); /* 381 */
    void (*reserved382)(void);
    Tcl_Obj * (*tclGetRange) (Tcl_Obj *objPtr, Tcl_Size first, Tcl_Size last); /* 383 */
    void (*tcl_AppendUnicodeToObj) (Tcl_Obj *objPtr, const Tcl_UniChar *unicode, Tcl_Size length); /* 384 */
    int (*tcl_RegExpMatchObj) (Tcl_Interp *interp, Tcl_Obj *textObj, Tcl_Obj *patternObj); /* 385 */
    void (*tcl_SetNotifier) (const Tcl_NotifierProcs *notifierProcPtr); /* 386 */
    Tcl_Mutex * (*tcl_GetAllocMutex) (void); /* 387 */
    int (*tcl_GetChannelNames) (Tcl_Interp *interp); /* 388 */
    int (*tcl_GetChannelNamesEx) (Tcl_Interp *interp, const char *pattern); /* 389 */
    int (*tcl_ProcObjCmd) (void *clientData, Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[]); /* 390 */
    void (*tcl_ConditionFinalize) (Tcl_Condition *condPtr); /* 391 */
    void (*tcl_MutexFinalize) (Tcl_Mutex *mutex); /* 392 */
    int (*tcl_CreateThread) (Tcl_ThreadId *idPtr, Tcl_ThreadCreateProc *proc, void *clientData, TCL_HASH_TYPE stackSize, int flags); /* 393 */
    Tcl_Size (*tcl_ReadRaw) (Tcl_Channel chan, char *dst, Tcl_Size bytesToRead); /* 394 */
    Tcl_Size (*tcl_WriteRaw) (Tcl_Channel chan, const char *src, Tcl_Size srcLen); /* 395 */
    Tcl_Channel (*tcl_GetTopChannel) (Tcl_Channel chan); /* 396 */
    int (*tcl_ChannelBuffered) (Tcl_Channel chan); /* 397 */
    const char * (*tcl_ChannelName) (const Tcl_ChannelType *chanTypePtr); /* 398 */
    Tcl_ChannelTypeVersion (*tcl_ChannelVersion) (const Tcl_ChannelType *chanTypePtr); /* 399 */
    Tcl_DriverBlockModeProc * (*tcl_ChannelBlockModeProc) (const Tcl_ChannelType *chanTypePtr); /* 400 */
    void (*reserved401)(void);
    Tcl_DriverClose2Proc * (*tcl_ChannelClose2Proc) (const Tcl_ChannelType *chanTypePtr); /* 402 */
    Tcl_DriverInputProc * (*tcl_ChannelInputProc) (const Tcl_ChannelType *chanTypePtr); /* 403 */
    Tcl_DriverOutputProc * (*tcl_ChannelOutputProc) (const Tcl_ChannelType *chanTypePtr); /* 404 */
    void (*reserved405)(void);
    Tcl_DriverSetOptionProc * (*tcl_ChannelSetOptionProc) (const Tcl_ChannelType *chanTypePtr); /* 406 */
    Tcl_DriverGetOptionProc * (*tcl_ChannelGetOptionProc) (const Tcl_ChannelType *chanTypePtr); /* 407 */
    Tcl_DriverWatchProc * (*tcl_ChannelWatchProc) (const Tcl_ChannelType *chanTypePtr); /* 408 */
    Tcl_DriverGetHandleProc * (*tcl_ChannelGetHandleProc) (const Tcl_ChannelType *chanTypePtr); /* 409 */
    Tcl_DriverFlushProc * (*tcl_ChannelFlushProc) (const Tcl_ChannelType *chanTypePtr); /* 410 */
    Tcl_DriverHandlerProc * (*tcl_ChannelHandlerProc) (const Tcl_ChannelType *chanTypePtr); /* 411 */
    int (*tcl_JoinThread) (Tcl_ThreadId threadId, int *result); /* 412 */
    int (*tcl_IsChannelShared) (Tcl_Channel channel); /* 413 */
    int (*tcl_IsChannelRegistered) (Tcl_Interp *interp, Tcl_Channel channel); /* 414 */
    void (*tcl_CutChannel) (Tcl_Channel channel); /* 415 */
    void (*tcl_SpliceChannel) (Tcl_Channel channel); /* 416 */
    void (*tcl_ClearChannelHandlers) (Tcl_Channel channel); /* 417 */
    int (*tcl_IsChannelExisting) (const char *channelName); /* 418 */
    void (*reserved419)(void);
    void (*reserved420)(void);
    void (*reserved421)(void);
    Tcl_HashEntry * (*tcl_CreateHashEntry) (Tcl_HashTable *tablePtr, const void *key, int *newPtr); /* 422 */
    void (*tcl_InitCustomHashTable) (Tcl_HashTable *tablePtr, int keyType, const Tcl_HashKeyType *typePtr); /* 423 */
    void (*tcl_InitObjHashTable) (Tcl_HashTable *tablePtr); /* 424 */
    void * (*tcl_CommandTraceInfo) (Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *procPtr, void *prevClientData); /* 425 */
    int (*tcl_TraceCommand) (Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *proc, void *clientData); /* 426 */
    void (*tcl_UntraceCommand) (Tcl_Interp *interp, const char *varName, int flags, Tcl_CommandTraceProc *proc, void *clientData); /* 427 */
    void * (*tcl_AttemptAlloc) (TCL_HASH_TYPE size); /* 428 */
    void * (*tcl_AttemptDbCkalloc) (TCL_HASH_TYPE size, const char *file, int line); /* 429 */
    void * (*tcl_AttemptRealloc) (void *ptr, TCL_HASH_TYPE size); /* 430 */
    void * (*tcl_AttemptDbCkrealloc) (void *ptr, TCL_HASH_TYPE size, const char *file, int line); /* 431 */
    int (*tcl_AttemptSetObjLength) (Tcl_Obj *objPtr, Tcl_Size length); /* 432 */
    Tcl_ThreadId (*tcl_GetChannelThread) (Tcl_Channel channel); /* 433 */
    Tcl_UniChar * (*tclGetUnicodeFromObj) (Tcl_Obj *objPtr, void *lengthPtr); /* 434 */
    void (*reserved435)(void);
    void (*reserved436)(void);
    Tcl_Obj * (*tcl_SubstObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 437 */
    int (*tcl_DetachChannel) (Tcl_Interp *interp, Tcl_Channel channel); /* 438 */
    int (*tcl_IsStandardChannel) (Tcl_Channel channel); /* 439 */
    int (*tcl_FSCopyFile) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); /* 440 */
    int (*tcl_FSCopyDirectory) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr, Tcl_Obj **errorPtr); /* 441 */
    int (*tcl_FSCreateDirectory) (Tcl_Obj *pathPtr); /* 442 */
    int (*tcl_FSDeleteFile) (Tcl_Obj *pathPtr); /* 443 */
    int (*tcl_FSLoadFile) (Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *sym1, const char *sym2, Tcl_LibraryInitProc **proc1Ptr, Tcl_LibraryInitProc **proc2Ptr, Tcl_LoadHandle *handlePtr, Tcl_FSUnloadFileProc **unloadProcPtr); /* 444 */
    int (*tcl_FSMatchInDirectory) (Tcl_Interp *interp, Tcl_Obj *result, Tcl_Obj *pathPtr, const char *pattern, Tcl_GlobTypeData *types); /* 445 */
    Tcl_Obj * (*tcl_FSLink) (Tcl_Obj *pathPtr, Tcl_Obj *toPtr, int linkAction); /* 446 */
    int (*tcl_FSRemoveDirectory) (Tcl_Obj *pathPtr, int recursive, Tcl_Obj **errorPtr); /* 447 */
    int (*tcl_FSRenameFile) (Tcl_Obj *srcPathPtr, Tcl_Obj *destPathPtr); /* 448 */
    int (*tcl_FSLstat) (Tcl_Obj *pathPtr, Tcl_StatBuf *buf); /* 449 */
    int (*tcl_FSUtime) (Tcl_Obj *pathPtr, struct utimbuf *tval); /* 450 */
    int (*tcl_FSFileAttrsGet) (Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef); /* 451 */
    int (*tcl_FSFileAttrsSet) (Tcl_Interp *interp, int index, Tcl_Obj *pathPtr, Tcl_Obj *objPtr); /* 452 */
    const char *const * (*tcl_FSFileAttrStrings) (Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef); /* 453 */
    int (*tcl_FSStat) (Tcl_Obj *pathPtr, Tcl_StatBuf *buf); /* 454 */
    int (*tcl_FSAccess) (Tcl_Obj *pathPtr, int mode); /* 455 */
    Tcl_Channel (*tcl_FSOpenFileChannel) (Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *modeString, int permissions); /* 456 */
    Tcl_Obj * (*tcl_FSGetCwd) (Tcl_Interp *interp); /* 457 */
    int (*tcl_FSChdir) (Tcl_Obj *pathPtr); /* 458 */
    int (*tcl_FSConvertToPathType) (Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 459 */
    Tcl_Obj * (*tcl_FSJoinPath) (Tcl_Obj *listObj, Tcl_Size elements); /* 460 */
    Tcl_Obj * (*tclFSSplitPath) (Tcl_Obj *pathPtr, void *lenPtr); /* 461 */
    int (*tcl_FSEqualPaths) (Tcl_Obj *firstPtr, Tcl_Obj *secondPtr); /* 462 */
    Tcl_Obj * (*tcl_FSGetNormalizedPath) (Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 463 */
    Tcl_Obj * (*tcl_FSJoinToPath) (Tcl_Obj *pathPtr, Tcl_Size objc, Tcl_Obj *const objv[]); /* 464 */
    void * (*tcl_FSGetInternalRep) (Tcl_Obj *pathPtr, const Tcl_Filesystem *fsPtr); /* 465 */
    Tcl_Obj * (*tcl_FSGetTranslatedPath) (Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 466 */
    int (*tcl_FSEvalFile) (Tcl_Interp *interp, Tcl_Obj *fileName); /* 467 */
    Tcl_Obj * (*tcl_FSNewNativePath) (const Tcl_Filesystem *fromFilesystem, void *clientData); /* 468 */
    const void * (*tcl_FSGetNativePath) (Tcl_Obj *pathPtr); /* 469 */
    Tcl_Obj * (*tcl_FSFileSystemInfo) (Tcl_Obj *pathPtr); /* 470 */
    Tcl_Obj * (*tcl_FSPathSeparator) (Tcl_Obj *pathPtr); /* 471 */
    Tcl_Obj * (*tcl_FSListVolumes) (void); /* 472 */
    int (*tcl_FSRegister) (void *clientData, const Tcl_Filesystem *fsPtr); /* 473 */
    int (*tcl_FSUnregister) (const Tcl_Filesystem *fsPtr); /* 474 */
    void * (*tcl_FSData) (const Tcl_Filesystem *fsPtr); /* 475 */
    const char * (*tcl_FSGetTranslatedStringPath) (Tcl_Interp *interp, Tcl_Obj *pathPtr); /* 476 */
    const Tcl_Filesystem * (*tcl_FSGetFileSystemForPath) (Tcl_Obj *pathPtr); /* 477 */
    Tcl_PathType (*tcl_FSGetPathType) (Tcl_Obj *pathPtr); /* 478 */
    int (*tcl_OutputBuffered) (Tcl_Channel chan); /* 479 */
    void (*tcl_FSMountsChanged) (const Tcl_Filesystem *fsPtr); /* 480 */
    int (*tcl_EvalTokensStandard) (Tcl_Interp *interp, Tcl_Token *tokenPtr, Tcl_Size count); /* 481 */
    void (*tcl_GetTime) (Tcl_Time *timeBuf); /* 482 */
    Tcl_Trace (*tcl_CreateObjTrace) (Tcl_Interp *interp, Tcl_Size level, int flags, Tcl_CmdObjTraceProc *objProc, void *clientData, Tcl_CmdObjTraceDeleteProc *delProc); /* 483 */
    int (*tcl_GetCommandInfoFromToken) (Tcl_Command token, Tcl_CmdInfo *infoPtr); /* 484 */
    int (*tcl_SetCommandInfoFromToken) (Tcl_Command token, const Tcl_CmdInfo *infoPtr); /* 485 */
    Tcl_Obj * (*tcl_DbNewWideIntObj) (Tcl_WideInt wideValue, const char *file, int line); /* 486 */
    int (*tcl_GetWideIntFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_WideInt *widePtr); /* 487 */
    Tcl_Obj * (*tcl_NewWideIntObj) (Tcl_WideInt wideValue); /* 488 */
    void (*tcl_SetWideIntObj) (Tcl_Obj *objPtr, Tcl_WideInt wideValue); /* 489 */
    Tcl_StatBuf * (*tcl_AllocStatBuf) (void); /* 490 */
    long long (*tcl_Seek) (Tcl_Channel chan, long long offset, int mode); /* 491 */
    long long (*tcl_Tell) (Tcl_Channel chan); /* 492 */
    Tcl_DriverWideSeekProc * (*tcl_ChannelWideSeekProc) (const Tcl_ChannelType *chanTypePtr); /* 493 */
    int (*tcl_DictObjPut) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr, Tcl_Obj *valuePtr); /* 494 */
    int (*tcl_DictObjGet) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr, Tcl_Obj **valuePtrPtr); /* 495 */
    int (*tcl_DictObjRemove) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Obj *keyPtr); /* 496 */
    int (*tclDictObjSize) (Tcl_Interp *interp, Tcl_Obj *dictPtr, void *sizePtr); /* 497 */
    int (*tcl_DictObjFirst) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_DictSearch *searchPtr, Tcl_Obj **keyPtrPtr, Tcl_Obj **valuePtrPtr, int *donePtr); /* 498 */
    void (*tcl_DictObjNext) (Tcl_DictSearch *searchPtr, Tcl_Obj **keyPtrPtr, Tcl_Obj **valuePtrPtr, int *donePtr); /* 499 */
    void (*tcl_DictObjDone) (Tcl_DictSearch *searchPtr); /* 500 */
    int (*tcl_DictObjPutKeyList) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size keyc, Tcl_Obj *const *keyv, Tcl_Obj *valuePtr); /* 501 */
    int (*tcl_DictObjRemoveKeyList) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size keyc, Tcl_Obj *const *keyv); /* 502 */
    Tcl_Obj * (*tcl_NewDictObj) (void); /* 503 */
    Tcl_Obj * (*tcl_DbNewDictObj) (const char *file, int line); /* 504 */
    void (*tcl_RegisterConfig) (Tcl_Interp *interp, const char *pkgName, const Tcl_Config *configuration, const char *valEncoding); /* 505 */
    Tcl_Namespace * (*tcl_CreateNamespace) (Tcl_Interp *interp, const char *name, void *clientData, Tcl_NamespaceDeleteProc *deleteProc); /* 506 */
    void (*tcl_DeleteNamespace) (Tcl_Namespace *nsPtr); /* 507 */
    int (*tcl_AppendExportList) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *objPtr); /* 508 */
    int (*tcl_Export) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern, int resetListFirst); /* 509 */
    int (*tcl_Import) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern, int allowOverwrite); /* 510 */
    int (*tcl_ForgetImport) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, const char *pattern); /* 511 */
    Tcl_Namespace * (*tcl_GetCurrentNamespace) (Tcl_Interp *interp); /* 512 */
    Tcl_Namespace * (*tcl_GetGlobalNamespace) (Tcl_Interp *interp); /* 513 */
    Tcl_Namespace * (*tcl_FindNamespace) (Tcl_Interp *interp, const char *name, Tcl_Namespace *contextNsPtr, int flags); /* 514 */
    Tcl_Command (*tcl_FindCommand) (Tcl_Interp *interp, const char *name, Tcl_Namespace *contextNsPtr, int flags); /* 515 */
    Tcl_Command (*tcl_GetCommandFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr); /* 516 */
    void (*tcl_GetCommandFullName) (Tcl_Interp *interp, Tcl_Command command, Tcl_Obj *objPtr); /* 517 */
    int (*tcl_FSEvalFileEx) (Tcl_Interp *interp, Tcl_Obj *fileName, const char *encodingName); /* 518 */
    void (*reserved519)(void);
    void (*tcl_LimitAddHandler) (Tcl_Interp *interp, int type, Tcl_LimitHandlerProc *handlerProc, void *clientData, Tcl_LimitHandlerDeleteProc *deleteProc); /* 520 */
    void (*tcl_LimitRemoveHandler) (Tcl_Interp *interp, int type, Tcl_LimitHandlerProc *handlerProc, void *clientData); /* 521 */
    int (*tcl_LimitReady) (Tcl_Interp *interp); /* 522 */
    int (*tcl_LimitCheck) (Tcl_Interp *interp); /* 523 */
    int (*tcl_LimitExceeded) (Tcl_Interp *interp); /* 524 */
    void (*tcl_LimitSetCommands) (Tcl_Interp *interp, Tcl_Size commandLimit); /* 525 */
    void (*tcl_LimitSetTime) (Tcl_Interp *interp, Tcl_Time *timeLimitPtr); /* 526 */
    void (*tcl_LimitSetGranularity) (Tcl_Interp *interp, int type, int granularity); /* 527 */
    int (*tcl_LimitTypeEnabled) (Tcl_Interp *interp, int type); /* 528 */
    int (*tcl_LimitTypeExceeded) (Tcl_Interp *interp, int type); /* 529 */
    void (*tcl_LimitTypeSet) (Tcl_Interp *interp, int type); /* 530 */
    void (*tcl_LimitTypeReset) (Tcl_Interp *interp, int type); /* 531 */
    Tcl_Size (*tcl_LimitGetCommands) (Tcl_Interp *interp); /* 532 */
    void (*tcl_LimitGetTime) (Tcl_Interp *interp, Tcl_Time *timeLimitPtr); /* 533 */
    int (*tcl_LimitGetGranularity) (Tcl_Interp *interp, int type); /* 534 */
    Tcl_InterpState (*tcl_SaveInterpState) (Tcl_Interp *interp, int status); /* 535 */
    int (*tcl_RestoreInterpState) (Tcl_Interp *interp, Tcl_InterpState state); /* 536 */
    void (*tcl_DiscardInterpState) (Tcl_InterpState state); /* 537 */
    int (*tcl_SetReturnOptions) (Tcl_Interp *interp, Tcl_Obj *options); /* 538 */
    Tcl_Obj * (*tcl_GetReturnOptions) (Tcl_Interp *interp, int result); /* 539 */
    int (*tcl_IsEnsemble) (Tcl_Command token); /* 540 */
    Tcl_Command (*tcl_CreateEnsemble) (Tcl_Interp *interp, const char *name, Tcl_Namespace *namespacePtr, int flags); /* 541 */
    Tcl_Command (*tcl_FindEnsemble) (Tcl_Interp *interp, Tcl_Obj *cmdNameObj, int flags); /* 542 */
    int (*tcl_SetEnsembleSubcommandList) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *subcmdList); /* 543 */
    int (*tcl_SetEnsembleMappingDict) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *mapDict); /* 544 */
    int (*tcl_SetEnsembleUnknownHandler) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *unknownList); /* 545 */
    int (*tcl_SetEnsembleFlags) (Tcl_Interp *interp, Tcl_Command token, int flags); /* 546 */
    int (*tcl_GetEnsembleSubcommandList) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **subcmdListPtr); /* 547 */
    int (*tcl_GetEnsembleMappingDict) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **mapDictPtr); /* 548 */
    int (*tcl_GetEnsembleUnknownHandler) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **unknownListPtr); /* 549 */
    int (*tcl_GetEnsembleFlags) (Tcl_Interp *interp, Tcl_Command token, int *flagsPtr); /* 550 */
    int (*tcl_GetEnsembleNamespace) (Tcl_Interp *interp, Tcl_Command token, Tcl_Namespace **namespacePtrPtr); /* 551 */
    void (*tcl_SetTimeProc) (Tcl_GetTimeProc *getProc, Tcl_ScaleTimeProc *scaleProc, void *clientData); /* 552 */
    void (*tcl_QueryTimeProc) (Tcl_GetTimeProc **getProc, Tcl_ScaleTimeProc **scaleProc, void **clientData); /* 553 */
    Tcl_DriverThreadActionProc * (*tcl_ChannelThreadActionProc) (const Tcl_ChannelType *chanTypePtr); /* 554 */
    Tcl_Obj * (*tcl_NewBignumObj) (void *value); /* 555 */
    Tcl_Obj * (*tcl_DbNewBignumObj) (void *value, const char *file, int line); /* 556 */
    void (*tcl_SetBignumObj) (Tcl_Obj *obj, void *value); /* 557 */
    int (*tcl_GetBignumFromObj) (Tcl_Interp *interp, Tcl_Obj *obj, void *value); /* 558 */
    int (*tcl_TakeBignumFromObj) (Tcl_Interp *interp, Tcl_Obj *obj, void *value); /* 559 */
    int (*tcl_TruncateChannel) (Tcl_Channel chan, long long length); /* 560 */
    Tcl_DriverTruncateProc * (*tcl_ChannelTruncateProc) (const Tcl_ChannelType *chanTypePtr); /* 561 */
    void (*tcl_SetChannelErrorInterp) (Tcl_Interp *interp, Tcl_Obj *msg); /* 562 */
    void (*tcl_GetChannelErrorInterp) (Tcl_Interp *interp, Tcl_Obj **msg); /* 563 */
    void (*tcl_SetChannelError) (Tcl_Channel chan, Tcl_Obj *msg); /* 564 */
    void (*tcl_GetChannelError) (Tcl_Channel chan, Tcl_Obj **msg); /* 565 */
    int (*tcl_InitBignumFromDouble) (Tcl_Interp *interp, double initval, void *toInit); /* 566 */
    Tcl_Obj * (*tcl_GetNamespaceUnknownHandler) (Tcl_Interp *interp, Tcl_Namespace *nsPtr); /* 567 */
    int (*tcl_SetNamespaceUnknownHandler) (Tcl_Interp *interp, Tcl_Namespace *nsPtr, Tcl_Obj *handlerPtr); /* 568 */
    int (*tcl_GetEncodingFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Encoding *encodingPtr); /* 569 */
    Tcl_Obj * (*tcl_GetEncodingSearchPath) (void); /* 570 */
    int (*tcl_SetEncodingSearchPath) (Tcl_Obj *searchPath); /* 571 */
    const char * (*tcl_GetEncodingNameFromEnvironment) (Tcl_DString *bufPtr); /* 572 */
    int (*tcl_PkgRequireProc) (Tcl_Interp *interp, const char *name, Tcl_Size objc, Tcl_Obj *const objv[], void *clientDataPtr); /* 573 */
    void (*tcl_AppendObjToErrorInfo) (Tcl_Interp *interp, Tcl_Obj *objPtr); /* 574 */
    void (*tcl_AppendLimitedToObj) (Tcl_Obj *objPtr, const char *bytes, Tcl_Size length, Tcl_Size limit, const char *ellipsis); /* 575 */
    Tcl_Obj * (*tcl_Format) (Tcl_Interp *interp, const char *format, Tcl_Size objc, Tcl_Obj *const objv[]); /* 576 */
    int (*tcl_AppendFormatToObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, const char *format, Tcl_Size objc, Tcl_Obj *const objv[]); /* 577 */
    Tcl_Obj * (*tcl_ObjPrintf) (const char *format, ...) TCL_FORMAT_PRINTF(1, 2); /* 578 */
    void (*tcl_AppendPrintfToObj) (Tcl_Obj *objPtr, const char *format, ...) TCL_FORMAT_PRINTF(2, 3); /* 579 */
    int (*tcl_CancelEval) (Tcl_Interp *interp, Tcl_Obj *resultObjPtr, void *clientData, int flags); /* 580 */
    int (*tcl_Canceled) (Tcl_Interp *interp, int flags); /* 581 */
    int (*tcl_CreatePipe) (Tcl_Interp *interp, Tcl_Channel *rchan, Tcl_Channel *wchan, int flags); /* 582 */
    Tcl_Command (*tcl_NRCreateCommand) (Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc *proc, Tcl_ObjCmdProc *nreProc, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 583 */
    int (*tcl_NREvalObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 584 */
    int (*tcl_NREvalObjv) (Tcl_Interp *interp, Tcl_Size objc, Tcl_Obj *const objv[], int flags); /* 585 */
    int (*tcl_NRCmdSwap) (Tcl_Interp *interp, Tcl_Command cmd, Tcl_Size objc, Tcl_Obj *const objv[], int flags); /* 586 */
    void (*tcl_NRAddCallback) (Tcl_Interp *interp, Tcl_NRPostProc *postProcPtr, void *data0, void *data1, void *data2, void *data3); /* 587 */
    int (*tcl_NRCallObjProc) (Tcl_Interp *interp, Tcl_ObjCmdProc *objProc, void *clientData, Tcl_Size objc, Tcl_Obj *const objv[]); /* 588 */
    unsigned (*tcl_GetFSDeviceFromStat) (const Tcl_StatBuf *statPtr); /* 589 */
    unsigned (*tcl_GetFSInodeFromStat) (const Tcl_StatBuf *statPtr); /* 590 */
    unsigned (*tcl_GetModeFromStat) (const Tcl_StatBuf *statPtr); /* 591 */
    int (*tcl_GetLinkCountFromStat) (const Tcl_StatBuf *statPtr); /* 592 */
    int (*tcl_GetUserIdFromStat) (const Tcl_StatBuf *statPtr); /* 593 */
    int (*tcl_GetGroupIdFromStat) (const Tcl_StatBuf *statPtr); /* 594 */
    int (*tcl_GetDeviceTypeFromStat) (const Tcl_StatBuf *statPtr); /* 595 */
    long long (*tcl_GetAccessTimeFromStat) (const Tcl_StatBuf *statPtr); /* 596 */
    long long (*tcl_GetModificationTimeFromStat) (const Tcl_StatBuf *statPtr); /* 597 */
    long long (*tcl_GetChangeTimeFromStat) (const Tcl_StatBuf *statPtr); /* 598 */
    unsigned long long (*tcl_GetSizeFromStat) (const Tcl_StatBuf *statPtr); /* 599 */
    unsigned long long (*tcl_GetBlocksFromStat) (const Tcl_StatBuf *statPtr); /* 600 */
    unsigned (*tcl_GetBlockSizeFromStat) (const Tcl_StatBuf *statPtr); /* 601 */
    int (*tcl_SetEnsembleParameterList) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj *paramList); /* 602 */
    int (*tcl_GetEnsembleParameterList) (Tcl_Interp *interp, Tcl_Command token, Tcl_Obj **paramListPtr); /* 603 */
    int (*tclParseArgsObjv) (Tcl_Interp *interp, const Tcl_ArgvInfo *argTable, void *objcPtr, Tcl_Obj *const *objv, Tcl_Obj ***remObjv); /* 604 */
    int (*tcl_GetErrorLine) (Tcl_Interp *interp); /* 605 */
    void (*tcl_SetErrorLine) (Tcl_Interp *interp, int lineNum); /* 606 */
    void (*tcl_TransferResult) (Tcl_Interp *sourceInterp, int code, Tcl_Interp *targetInterp); /* 607 */
    int (*tcl_InterpActive) (Tcl_Interp *interp); /* 608 */
    void (*tcl_BackgroundException) (Tcl_Interp *interp, int code); /* 609 */
    int (*tcl_ZlibDeflate) (Tcl_Interp *interp, int format, Tcl_Obj *data, int level, Tcl_Obj *gzipHeaderDictObj); /* 610 */
    int (*tcl_ZlibInflate) (Tcl_Interp *interp, int format, Tcl_Obj *data, Tcl_Size buffersize, Tcl_Obj *gzipHeaderDictObj); /* 611 */
    unsigned int (*tcl_ZlibCRC32) (unsigned int crc, const unsigned char *buf, Tcl_Size len); /* 612 */
    unsigned int (*tcl_ZlibAdler32) (unsigned int adler, const unsigned char *buf, Tcl_Size len); /* 613 */
    int (*tcl_ZlibStreamInit) (Tcl_Interp *interp, int mode, int format, int level, Tcl_Obj *dictObj, Tcl_ZlibStream *zshandle); /* 614 */
    Tcl_Obj * (*tcl_ZlibStreamGetCommandName) (Tcl_ZlibStream zshandle); /* 615 */
    int (*tcl_ZlibStreamEof) (Tcl_ZlibStream zshandle); /* 616 */
    int (*tcl_ZlibStreamChecksum) (Tcl_ZlibStream zshandle); /* 617 */
    int (*tcl_ZlibStreamPut) (Tcl_ZlibStream zshandle, Tcl_Obj *data, int flush); /* 618 */
    int (*tcl_ZlibStreamGet) (Tcl_ZlibStream zshandle, Tcl_Obj *data, Tcl_Size count); /* 619 */
    int (*tcl_ZlibStreamClose) (Tcl_ZlibStream zshandle); /* 620 */
    int (*tcl_ZlibStreamReset) (Tcl_ZlibStream zshandle); /* 621 */
    void (*tcl_SetStartupScript) (Tcl_Obj *path, const char *encoding); /* 622 */
    Tcl_Obj * (*tcl_GetStartupScript) (const char **encodingPtr); /* 623 */
    int (*tcl_CloseEx) (Tcl_Interp *interp, Tcl_Channel chan, int flags); /* 624 */
    int (*tcl_NRExprObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Obj *resultPtr); /* 625 */
    int (*tcl_NRSubstObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags); /* 626 */
    int (*tcl_LoadFile) (Tcl_Interp *interp, Tcl_Obj *pathPtr, const char *const symv[], int flags, void *procPtrs, Tcl_LoadHandle *handlePtr); /* 627 */
    void * (*tcl_FindSymbol) (Tcl_Interp *interp, Tcl_LoadHandle handle, const char *symbol); /* 628 */
    int (*tcl_FSUnloadFile) (Tcl_Interp *interp, Tcl_LoadHandle handlePtr); /* 629 */
    void (*tcl_ZlibStreamSetCompressionDictionary) (Tcl_ZlibStream zhandle, Tcl_Obj *compressionDictionaryObj); /* 630 */
    Tcl_Channel (*tcl_OpenTcpServerEx) (Tcl_Interp *interp, const char *service, const char *host, unsigned int flags, int backlog, Tcl_TcpAcceptProc *acceptProc, void *callbackData); /* 631 */
    int (*tclZipfs_Mount) (Tcl_Interp *interp, const char *zipname, const char *mountPoint, const char *passwd); /* 632 */
    int (*tclZipfs_Unmount) (Tcl_Interp *interp, const char *mountPoint); /* 633 */
    Tcl_Obj * (*tclZipfs_TclLibrary) (void); /* 634 */
    int (*tclZipfs_MountBuffer) (Tcl_Interp *interp, const void *data, size_t datalen, const char *mountPoint, int copy); /* 635 */
    void (*tcl_FreeInternalRep) (Tcl_Obj *objPtr); /* 636 */
    char * (*tcl_InitStringRep) (Tcl_Obj *objPtr, const char *bytes, TCL_HASH_TYPE numBytes); /* 637 */
    Tcl_ObjInternalRep * (*tcl_FetchInternalRep) (Tcl_Obj *objPtr, const Tcl_ObjType *typePtr); /* 638 */
    void (*tcl_StoreInternalRep) (Tcl_Obj *objPtr, const Tcl_ObjType *typePtr, const Tcl_ObjInternalRep *irPtr); /* 639 */
    int (*tcl_HasStringRep) (Tcl_Obj *objPtr); /* 640 */
    void (*tcl_IncrRefCount) (Tcl_Obj *objPtr); /* 641 */
    void (*tcl_DecrRefCount) (Tcl_Obj *objPtr); /* 642 */
    int (*tcl_IsShared) (Tcl_Obj *objPtr); /* 643 */
    int (*tcl_LinkArray) (Tcl_Interp *interp, const char *varName, void *addr, int type, Tcl_Size size); /* 644 */
    int (*tcl_GetIntForIndex) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size endValue, Tcl_Size *indexPtr); /* 645 */
    Tcl_Size (*tcl_UtfToUniChar) (const char *src, int *chPtr); /* 646 */
    char * (*tcl_UniCharToUtfDString) (const int *uniStr, Tcl_Size uniLength, Tcl_DString *dsPtr); /* 647 */
    int * (*tcl_UtfToUniCharDString) (const char *src, Tcl_Size length, Tcl_DString *dsPtr); /* 648 */
    unsigned char * (*tclGetBytesFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, void *numBytesPtr); /* 649 */
    unsigned char * (*tcl_GetBytesFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size *numBytesPtr); /* 650 */
    char * (*tcl_GetStringFromObj) (Tcl_Obj *objPtr, Tcl_Size *lengthPtr); /* 651 */
    Tcl_UniChar * (*tcl_GetUnicodeFromObj) (Tcl_Obj *objPtr, Tcl_Size *lengthPtr); /* 652 */
    int (*tcl_GetSizeIntFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_Size *sizePtr); /* 653 */
    int (*tcl_UtfCharComplete) (const char *src, Tcl_Size length); /* 654 */
    const char * (*tcl_UtfNext) (const char *src); /* 655 */
    const char * (*tcl_UtfPrev) (const char *src, const char *start); /* 656 */
    int (*tcl_FSTildeExpand) (Tcl_Interp *interp, const char *path, Tcl_DString *dsPtr); /* 657 */
    int (*tcl_ExternalToUtfDStringEx) (Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_DString *dsPtr, Tcl_Size *errorLocationPtr); /* 658 */
    int (*tcl_UtfToExternalDStringEx) (Tcl_Interp *interp, Tcl_Encoding encoding, const char *src, Tcl_Size srcLen, int flags, Tcl_DString *dsPtr, Tcl_Size *errorLocationPtr); /* 659 */
    int (*tcl_AsyncMarkFromSignal) (Tcl_AsyncHandler async, int sigNumber); /* 660 */
    int (*tcl_ListObjGetElements) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size *objcPtr, Tcl_Obj ***objvPtr); /* 661 */
    int (*tcl_ListObjLength) (Tcl_Interp *interp, Tcl_Obj *listPtr, Tcl_Size *lengthPtr); /* 662 */
    int (*tcl_DictObjSize) (Tcl_Interp *interp, Tcl_Obj *dictPtr, Tcl_Size *sizePtr); /* 663 */
    int (*tcl_SplitList) (Tcl_Interp *interp, const char *listStr, Tcl_Size *argcPtr, const char ***argvPtr); /* 664 */
    void (*tcl_SplitPath) (const char *path, Tcl_Size *argcPtr, const char ***argvPtr); /* 665 */
    Tcl_Obj * (*tcl_FSSplitPath) (Tcl_Obj *pathPtr, Tcl_Size *lenPtr); /* 666 */
    int (*tcl_ParseArgsObjv) (Tcl_Interp *interp, const Tcl_ArgvInfo *argTable, Tcl_Size *objcPtr, Tcl_Obj *const *objv, Tcl_Obj ***remObjv); /* 667 */
    Tcl_Size (*tcl_UniCharLen) (const int *uniStr); /* 668 */
    Tcl_Size (*tcl_NumUtfChars) (const char *src, Tcl_Size length); /* 669 */
    Tcl_Size (*tcl_GetCharLength) (Tcl_Obj *objPtr); /* 670 */
    const char * (*tcl_UtfAtIndex) (const char *src, Tcl_Size index); /* 671 */
    Tcl_Obj * (*tcl_GetRange) (Tcl_Obj *objPtr, Tcl_Size first, Tcl_Size last); /* 672 */
    int (*tcl_GetUniChar) (Tcl_Obj *objPtr, Tcl_Size index); /* 673 */
    int (*tcl_GetBool) (Tcl_Interp *interp, const char *src, int flags, char *charPtr); /* 674 */
    int (*tcl_GetBoolFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, int flags, char *charPtr); /* 675 */
    Tcl_Command (*tcl_CreateObjCommand2) (Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc2 *proc2, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 676 */
    Tcl_Trace (*tcl_CreateObjTrace2) (Tcl_Interp *interp, Tcl_Size level, int flags, Tcl_CmdObjTraceProc2 *objProc2, void *clientData, Tcl_CmdObjTraceDeleteProc *delProc); /* 677 */
    Tcl_Command (*tcl_NRCreateCommand2) (Tcl_Interp *interp, const char *cmdName, Tcl_ObjCmdProc2 *proc, Tcl_ObjCmdProc2 *nreProc2, void *clientData, Tcl_CmdDeleteProc *deleteProc); /* 678 */
    int (*tcl_NRCallObjProc2) (Tcl_Interp *interp, Tcl_ObjCmdProc2 *objProc2, void *clientData, Tcl_Size objc, Tcl_Obj *const objv[]); /* 679 */
    int (*tcl_GetNumberFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, void **clientDataPtr, int *typePtr); /* 680 */
    int (*tcl_GetNumber) (Tcl_Interp *interp, const char *bytes, Tcl_Size numBytes, void **clientDataPtr, int *typePtr); /* 681 */
    int (*tcl_RemoveChannelMode) (Tcl_Interp *interp, Tcl_Channel chan, int mode); /* 682 */
    Tcl_Size (*tcl_GetEncodingNulLength) (Tcl_Encoding encoding); /* 683 */
    int (*tcl_GetWideUIntFromObj) (Tcl_Interp *interp, Tcl_Obj *objPtr, Tcl_WideUInt *uwidePtr); /* 684 */
    Tcl_Obj * (*tcl_DStringToObj) (Tcl_DString *dsPtr); /* 685 */
    int (*tcl_UtfNcmp) (const char *s1, const char *s2, size_t n); /* 686 */
    int (*tcl_UtfNcasecmp) (const char *s1, const char *s2, size_t n); /* 687 */
    Tcl_Obj * (*tcl_NewWideUIntObj) (Tcl_WideUInt wideValue); /* 688 */
    void (*tcl_SetWideUIntObj) (Tcl_Obj *objPtr, Tcl_WideUInt uwideValue); /* 689 */
    void (*tclUnusedStubEntry) (void); /* 690 */
} TclStubs;

extern const TclStubs *tclStubsPtr;

#ifdef __cplusplus
}
#endif

#if defined(USE_TCL_STUBS)

/*
 * Inline function declarations:
 */

#define Tcl_PkgProvideEx \
	(tclStubsPtr->tcl_PkgProvideEx) /* 0 */
#define Tcl_PkgRequireEx \
	(tclStubsPtr->tcl_PkgRequireEx) /* 1 */
#define Tcl_Panic \
	(tclStubsPtr->tcl_Panic) /* 2 */
#define Tcl_Alloc \
	(tclStubsPtr->tcl_Alloc) /* 3 */
#define Tcl_Free \
	(tclStubsPtr->tcl_Free) /* 4 */
#define Tcl_Realloc \
	(tclStubsPtr->tcl_Realloc) /* 5 */
#define Tcl_DbCkalloc \
	(tclStubsPtr->tcl_DbCkalloc) /* 6 */
#define Tcl_DbCkfree \
	(tclStubsPtr->tcl_DbCkfree) /* 7 */
#define Tcl_DbCkrealloc \
	(tclStubsPtr->tcl_DbCkrealloc) /* 8 */
#define Tcl_CreateFileHandler \
	(tclStubsPtr->tcl_CreateFileHandler) /* 9 */
#define Tcl_DeleteFileHandler \
	(tclStubsPtr->tcl_DeleteFileHandler) /* 10 */
#define Tcl_SetTimer \
	(tclStubsPtr->tcl_SetTimer) /* 11 */
#define Tcl_Sleep \
	(tclStubsPtr->tcl_Sleep) /* 12 */
#define Tcl_WaitForEvent \
	(tclStubsPtr->tcl_WaitForEvent) /* 13 */
#define Tcl_AppendAllObjTypes \
	(tclStubsPtr->tcl_AppendAllObjTypes) /* 14 */
#define Tcl_AppendStringsToObj \
	(tclStubsPtr->tcl_AppendStringsToObj) /* 15 */
#define Tcl_AppendToObj \
	(tclStubsPtr->tcl_AppendToObj) /* 16 */
#define Tcl_ConcatObj \
	(tclStubsPtr->tcl_ConcatObj) /* 17 */
#define Tcl_ConvertToType \
	(tclStubsPtr->tcl_ConvertToType) /* 18 */
#define Tcl_DbDecrRefCount \
	(tclStubsPtr->tcl_DbDecrRefCount) /* 19 */
#define Tcl_DbIncrRefCount \
	(tclStubsPtr->tcl_DbIncrRefCount) /* 20 */
#define Tcl_DbIsShared \
	(tclStubsPtr->tcl_DbIsShared) /* 21 */
/* Slot 22 is reserved */
#define Tcl_DbNewByteArrayObj \
	(tclStubsPtr->tcl_DbNewByteArrayObj) /* 23 */
#define Tcl_DbNewDoubleObj \
	(tclStubsPtr->tcl_DbNewDoubleObj) /* 24 */
#define Tcl_DbNewListObj \
	(tclStubsPtr->tcl_DbNewListObj) /* 25 */
/* Slot 26 is reserved */
#define Tcl_DbNewObj \
	(tclStubsPtr->tcl_DbNewObj) /* 27 */
#define Tcl_DbNewStringObj \
	(tclStubsPtr->tcl_DbNewStringObj) /* 28 */
#define Tcl_DuplicateObj \
	(tclStubsPtr->tcl_DuplicateObj) /* 29 */
#define TclFreeObj \
	(tclStubsPtr->tclFreeObj) /* 30 */
#define Tcl_GetBoolean \
	(tclStubsPtr->tcl_GetBoolean) /* 31 */
#define Tcl_GetBooleanFromObj \
	(tclStubsPtr->tcl_GetBooleanFromObj) /* 32 */
#define Tcl_GetByteArrayFromObj \
	(tclStubsPtr->tcl_GetByteArrayFromObj) /* 33 */
#define Tcl_GetDouble \
	(tclStubsPtr->tcl_GetDouble) /* 34 */
#define Tcl_GetDoubleFromObj \
	(tclStubsPtr->tcl_GetDoubleFromObj) /* 35 */
/* Slot 36 is reserved */
#define Tcl_GetInt \
	(tclStubsPtr->tcl_GetInt) /* 37 */
#define Tcl_GetIntFromObj \
	(tclStubsPtr->tcl_GetIntFromObj) /* 38 */
#define Tcl_GetLongFromObj \
	(tclStubsPtr->tcl_GetLongFromObj) /* 39 */
#define Tcl_GetObjType \
	(tclStubsPtr->tcl_GetObjType) /* 40 */
#define TclGetStringFromObj \
	(tclStubsPtr->tclGetStringFromObj) /* 41 */
#define Tcl_InvalidateStringRep \
	(tclStubsPtr->tcl_InvalidateStringRep) /* 42 */
#define Tcl_ListObjAppendList \
	(tclStubsPtr->tcl_ListObjAppendList) /* 43 */
#define Tcl_ListObjAppendElement \
	(tclStubsPtr->tcl_ListObjAppendElement) /* 44 */
#define TclListObjGetElements \
	(tclStubsPtr->tclListObjGetElements) /* 45 */
#define Tcl_ListObjIndex \
	(tclStubsPtr->tcl_ListObjIndex) /* 46 */
#define TclListObjLength \
	(tclStubsPtr->tclListObjLength) /* 47 */
#define Tcl_ListObjReplace \
	(tclStubsPtr->tcl_ListObjReplace) /* 48 */
/* Slot 49 is reserved */
#define Tcl_NewByteArrayObj \
	(tclStubsPtr->tcl_NewByteArrayObj) /* 50 */
#define Tcl_NewDoubleObj \
	(tclStubsPtr->tcl_NewDoubleObj) /* 51 */
/* Slot 52 is reserved */
#define Tcl_NewListObj \
	(tclStubsPtr->tcl_NewListObj) /* 53 */
/* Slot 54 is reserved */
#define Tcl_NewObj \
	(tclStubsPtr->tcl_NewObj) /* 55 */
#define Tcl_NewStringObj \
	(tclStubsPtr->tcl_NewStringObj) /* 56 */
/* Slot 57 is reserved */
#define Tcl_SetByteArrayLength \
	(tclStubsPtr->tcl_SetByteArrayLength) /* 58 */
#define Tcl_SetByteArrayObj \
	(tclStubsPtr->tcl_SetByteArrayObj) /* 59 */
#define Tcl_SetDoubleObj \
	(tclStubsPtr->tcl_SetDoubleObj) /* 60 */
/* Slot 61 is reserved */
#define Tcl_SetListObj \
	(tclStubsPtr->tcl_SetListObj) /* 62 */
/* Slot 63 is reserved */
#define Tcl_SetObjLength \
	(tclStubsPtr->tcl_SetObjLength) /* 64 */
#define Tcl_SetStringObj \
	(tclStubsPtr->tcl_SetStringObj) /* 65 */
/* Slot 66 is reserved */
/* Slot 67 is reserved */
#define Tcl_AllowExceptions \
	(tclStubsPtr->tcl_AllowExceptions) /* 68 */
#define Tcl_AppendElement \
	(tclStubsPtr->tcl_AppendElement) /* 69 */
#define Tcl_AppendResult \
	(tclStubsPtr->tcl_AppendResult) /* 70 */
#define Tcl_AsyncCreate \
	(tclStubsPtr->tcl_AsyncCreate) /* 71 */
#define Tcl_AsyncDelete \
	(tclStubsPtr->tcl_AsyncDelete) /* 72 */
#define Tcl_AsyncInvoke \
	(tclStubsPtr->tcl_AsyncInvoke) /* 73 */
#define Tcl_AsyncMark \
	(tclStubsPtr->tcl_AsyncMark) /* 74 */
#define Tcl_AsyncReady \
	(tclStubsPtr->tcl_AsyncReady) /* 75 */
/* Slot 76 is reserved */
/* Slot 77 is reserved */
#define Tcl_BadChannelOption \
	(tclStubsPtr->tcl_BadChannelOption) /* 78 */
#define Tcl_CallWhenDeleted \
	(tclStubsPtr->tcl_CallWhenDeleted) /* 79 */
#define Tcl_CancelIdleCall \
	(tclStubsPtr->tcl_CancelIdleCall) /* 80 */
#define Tcl_Close \
	(tclStubsPtr->tcl_Close) /* 81 */
#define Tcl_CommandComplete \
	(tclStubsPtr->tcl_CommandComplete) /* 82 */
#define Tcl_Concat \
	(tclStubsPtr->tcl_Concat) /* 83 */
#define Tcl_ConvertElement \
	(tclStubsPtr->tcl_ConvertElement) /* 84 */
#define Tcl_ConvertCountedElement \
	(tclStubsPtr->tcl_ConvertCountedElement) /* 85 */
#define Tcl_CreateAlias \
	(tclStubsPtr->tcl_CreateAlias) /* 86 */
#define Tcl_CreateAliasObj \
	(tclStubsPtr->tcl_CreateAliasObj) /* 87 */
#define Tcl_CreateChannel \
	(tclStubsPtr->tcl_CreateChannel) /* 88 */
#define Tcl_CreateChannelHandler \
	(tclStubsPtr->tcl_CreateChannelHandler) /* 89 */
#define Tcl_CreateCloseHandler \
	(tclStubsPtr->tcl_CreateCloseHandler) /* 90 */
#define Tcl_CreateCommand \
	(tclStubsPtr->tcl_CreateCommand) /* 91 */
#define Tcl_CreateEventSource \
	(tclStubsPtr->tcl_CreateEventSource) /* 92 */
#define Tcl_CreateExitHandler \
	(tclStubsPtr->tcl_CreateExitHandler) /* 93 */
#define Tcl_CreateInterp \
	(tclStubsPtr->tcl_CreateInterp) /* 94 */
/* Slot 95 is reserved */
#define Tcl_CreateObjCommand \
	(tclStubsPtr->tcl_CreateObjCommand) /* 96 */
#define Tcl_CreateChild \
	(tclStubsPtr->tcl_CreateChild) /* 97 */
#define Tcl_CreateTimerHandler \
	(tclStubsPtr->tcl_CreateTimerHandler) /* 98 */
#define Tcl_CreateTrace \
	(tclStubsPtr->tcl_CreateTrace) /* 99 */
#define Tcl_DeleteAssocData \
	(tclStubsPtr->tcl_DeleteAssocData) /* 100 */
#define Tcl_DeleteChannelHandler \
	(tclStubsPtr->tcl_DeleteChannelHandler) /* 101 */
#define Tcl_DeleteCloseHandler \
	(tclStubsPtr->tcl_DeleteCloseHandler) /* 102 */
#define Tcl_DeleteCommand \
	(tclStubsPtr->tcl_DeleteCommand) /* 103 */
#define Tcl_DeleteCommandFromToken \
	(tclStubsPtr->tcl_DeleteCommandFromToken) /* 104 */
#define Tcl_DeleteEvents \
	(tclStubsPtr->tcl_DeleteEvents) /* 105 */
#define Tcl_DeleteEventSource \
	(tclStubsPtr->tcl_DeleteEventSource) /* 106 */
#define Tcl_DeleteExitHandler \
	(tclStubsPtr->tcl_DeleteExitHandler) /* 107 */
#define Tcl_DeleteHashEntry \
	(tclStubsPtr->tcl_DeleteHashEntry) /* 108 */
#define Tcl_DeleteHashTable \
	(tclStubsPtr->tcl_DeleteHashTable) /* 109 */
#define Tcl_DeleteInterp \
	(tclStubsPtr->tcl_DeleteInterp) /* 110 */
#define Tcl_DetachPids \
	(tclStubsPtr->tcl_DetachPids) /* 111 */
#define Tcl_DeleteTimerHandler \
	(tclStubsPtr->tcl_DeleteTimerHandler) /* 112 */
#define Tcl_DeleteTrace \
	(tclStubsPtr->tcl_DeleteTrace) /* 113 */
#define Tcl_DontCallWhenDeleted \
	(tclStubsPtr->tcl_DontCallWhenDeleted) /* 114 */
#define Tcl_DoOneEvent \
	(tclStubsPtr->tcl_DoOneEvent) /* 115 */
#define Tcl_DoWhenIdle \
	(tclStubsPtr->tcl_DoWhenIdle) /* 116 */
#define Tcl_DStringAppend \
	(tclStubsPtr->tcl_DStringAppend) /* 117 */
#define Tcl_DStringAppendElement \
	(tclStubsPtr->tcl_DStringAppendElement) /* 118 */
#define Tcl_DStringEndSublist \
	(tclStubsPtr->tcl_DStringEndSublist) /* 119 */
#define Tcl_DStringFree \
	(tclStubsPtr->tcl_DStringFree) /* 120 */
#define Tcl_DStringGetResult \
	(tclStubsPtr->tcl_DStringGetResult) /* 121 */
#define Tcl_DStringInit \
	(tclStubsPtr->tcl_DStringInit) /* 122 */
#define Tcl_DStringResult \
	(tclStubsPtr->tcl_DStringResult) /* 123 */
#define Tcl_DStringSetLength \
	(tclStubsPtr->tcl_DStringSetLength) /* 124 */
#define Tcl_DStringStartSublist \
	(tclStubsPtr->tcl_DStringStartSublist) /* 125 */
#define Tcl_Eof \
	(tclStubsPtr->tcl_Eof) /* 126 */
#define Tcl_ErrnoId \
	(tclStubsPtr->tcl_ErrnoId) /* 127 */
#define Tcl_ErrnoMsg \
	(tclStubsPtr->tcl_ErrnoMsg) /* 128 */
/* Slot 129 is reserved */
#define Tcl_EvalFile \
	(tclStubsPtr->tcl_EvalFile) /* 130 */
/* Slot 131 is reserved */
#define Tcl_EventuallyFree \
	(tclStubsPtr->tcl_EventuallyFree) /* 132 */
#define Tcl_Exit \
	(tclStubsPtr->tcl_Exit) /* 133 */
#define Tcl_ExposeCommand \
	(tclStubsPtr->tcl_ExposeCommand) /* 134 */
#define Tcl_ExprBoolean \
	(tclStubsPtr->tcl_ExprBoolean) /* 135 */
#define Tcl_ExprBooleanObj \
	(tclStubsPtr->tcl_ExprBooleanObj) /* 136 */
#define Tcl_ExprDouble \
	(tclStubsPtr->tcl_ExprDouble) /* 137 */
#define Tcl_ExprDoubleObj \
	(tclStubsPtr->tcl_ExprDoubleObj) /* 138 */
#define Tcl_ExprLong \
	(tclStubsPtr->tcl_ExprLong) /* 139 */
#define Tcl_ExprLongObj \
	(tclStubsPtr->tcl_ExprLongObj) /* 140 */
#define Tcl_ExprObj \
	(tclStubsPtr->tcl_ExprObj) /* 141 */
#define Tcl_ExprString \
	(tclStubsPtr->tcl_ExprString) /* 142 */
#define Tcl_Finalize \
	(tclStubsPtr->tcl_Finalize) /* 143 */
/* Slot 144 is reserved */
#define Tcl_FirstHashEntry \
	(tclStubsPtr->tcl_FirstHashEntry) /* 145 */
#define Tcl_Flush \
	(tclStubsPtr->tcl_Flush) /* 146 */
/* Slot 147 is reserved */
/* Slot 148 is reserved */
#define TclGetAliasObj \
	(tclStubsPtr->tclGetAliasObj) /* 149 */
#define Tcl_GetAssocData \
	(tclStubsPtr->tcl_GetAssocData) /* 150 */
#define Tcl_GetChannel \
	(tclStubsPtr->tcl_GetChannel) /* 151 */
#define Tcl_GetChannelBufferSize \
	(tclStubsPtr->tcl_GetChannelBufferSize) /* 152 */
#define Tcl_GetChannelHandle \
	(tclStubsPtr->tcl_GetChannelHandle) /* 153 */
#define Tcl_GetChannelInstanceData \
	(tclStubsPtr->tcl_GetChannelInstanceData) /* 154 */
#define Tcl_GetChannelMode \
	(tclStubsPtr->tcl_GetChannelMode) /* 155 */
#define Tcl_GetChannelName \
	(tclStubsPtr->tcl_GetChannelName) /* 156 */
#define Tcl_GetChannelOption \
	(tclStubsPtr->tcl_GetChannelOption) /* 157 */
#define Tcl_GetChannelType \
	(tclStubsPtr->tcl_GetChannelType) /* 158 */
#define Tcl_GetCommandInfo \
	(tclStubsPtr->tcl_GetCommandInfo) /* 159 */
#define Tcl_GetCommandName \
	(tclStubsPtr->tcl_GetCommandName) /* 160 */
#define Tcl_GetErrno \
	(tclStubsPtr->tcl_GetErrno) /* 161 */
#define Tcl_GetHostName \
	(tclStubsPtr->tcl_GetHostName) /* 162 */
#define Tcl_GetInterpPath \
	(tclStubsPtr->tcl_GetInterpPath) /* 163 */
#define Tcl_GetParent \
	(tclStubsPtr->tcl_GetParent) /* 164 */
#define Tcl_GetNameOfExecutable \
	(tclStubsPtr->tcl_GetNameOfExecutable) /* 165 */
#define Tcl_GetObjResult \
	(tclStubsPtr->tcl_GetObjResult) /* 166 */
#define Tcl_GetOpenFile \
	(tclStubsPtr->tcl_GetOpenFile) /* 167 */
#define Tcl_GetPathType \
	(tclStubsPtr->tcl_GetPathType) /* 168 */
#define Tcl_Gets \
	(tclStubsPtr->tcl_Gets) /* 169 */
#define Tcl_GetsObj \
	(tclStubsPtr->tcl_GetsObj) /* 170 */
#define Tcl_GetServiceMode \
	(tclStubsPtr->tcl_GetServiceMode) /* 171 */
#define Tcl_GetChild \
	(tclStubsPtr->tcl_GetChild) /* 172 */
#define Tcl_GetStdChannel \
	(tclStubsPtr->tcl_GetStdChannel) /* 173 */
/* Slot 174 is reserved */
/* Slot 175 is reserved */
#define Tcl_GetVar2 \
	(tclStubsPtr->tcl_GetVar2) /* 176 */
/* Slot 177 is reserved */
/* Slot 178 is reserved */
#define Tcl_HideCommand \
	(tclStubsPtr->tcl_HideCommand) /* 179 */
#define Tcl_Init \
	(tclStubsPtr->tcl_Init) /* 180 */
#define Tcl_InitHashTable \
	(tclStubsPtr->tcl_InitHashTable) /* 181 */
#define Tcl_InputBlocked \
	(tclStubsPtr->tcl_InputBlocked) /* 182 */
#define Tcl_InputBuffered \
	(tclStubsPtr->tcl_InputBuffered) /* 183 */
#define Tcl_InterpDeleted \
	(tclStubsPtr->tcl_InterpDeleted) /* 184 */
#define Tcl_IsSafe \
	(tclStubsPtr->tcl_IsSafe) /* 185 */
#define Tcl_JoinPath \
	(tclStubsPtr->tcl_JoinPath) /* 186 */
#define Tcl_LinkVar \
	(tclStubsPtr->tcl_LinkVar) /* 187 */
/* Slot 188 is reserved */
#define Tcl_MakeFileChannel \
	(tclStubsPtr->tcl_MakeFileChannel) /* 189 */
/* Slot 190 is reserved */
#define Tcl_MakeTcpClientChannel \
	(tclStubsPtr->tcl_MakeTcpClientChannel) /* 191 */
#define Tcl_Merge \
	(tclStubsPtr->tcl_Merge) /* 192 */
#define Tcl_NextHashEntry \
	(tclStubsPtr->tcl_NextHashEntry) /* 193 */
#define Tcl_NotifyChannel \
	(tclStubsPtr->tcl_NotifyChannel) /* 194 */
#define Tcl_ObjGetVar2 \
	(tclStubsPtr->tcl_ObjGetVar2) /* 195 */
#define Tcl_ObjSetVar2 \
	(tclStubsPtr->tcl_ObjSetVar2) /* 196 */
#define Tcl_OpenCommandChannel \
	(tclStubsPtr->tcl_OpenCommandChannel) /* 197 */
#define Tcl_OpenFileChannel \
	(tclStubsPtr->tcl_OpenFileChannel) /* 198 */
#define Tcl_OpenTcpClient \
	(tclStubsPtr->tcl_OpenTcpClient) /* 199 */
#define Tcl_OpenTcpServer \
	(tclStubsPtr->tcl_OpenTcpServer) /* 200 */
#define Tcl_Preserve \
	(tclStubsPtr->tcl_Preserve) /* 201 */
#define Tcl_PrintDouble \
	(tclStubsPtr->tcl_PrintDouble) /* 202 */
#define Tcl_PutEnv \
	(tclStubsPtr->tcl_PutEnv) /* 203 */
#define Tcl_PosixError \
	(tclStubsPtr->tcl_PosixError) /* 204 */
#define Tcl_QueueEvent \
	(tclStubsPtr->tcl_QueueEvent) /* 205 */
#define Tcl_Read \
	(tclStubsPtr->tcl_Read) /* 206 */
#define Tcl_ReapDetachedProcs \
	(tclStubsPtr->tcl_ReapDetachedProcs) /* 207 */
#define Tcl_RecordAndEval \
	(tclStubsPtr->tcl_RecordAndEval) /* 208 */
#define Tcl_RecordAndEvalObj \
	(tclStubsPtr->tcl_RecordAndEvalObj) /* 209 */
#define Tcl_RegisterChannel \
	(tclStubsPtr->tcl_RegisterChannel) /* 210 */
#define Tcl_RegisterObjType \
	(tclStubsPtr->tcl_RegisterObjType) /* 211 */
#define Tcl_RegExpCompile \
	(tclStubsPtr->tcl_RegExpCompile) /* 212 */
#define Tcl_RegExpExec \
	(tclStubsPtr->tcl_RegExpExec) /* 213 */
#define Tcl_RegExpMatch \
	(tclStubsPtr->tcl_RegExpMatch) /* 214 */
#define Tcl_RegExpRange \
	(tclStubsPtr->tcl_RegExpRange) /* 215 */
#define Tcl_Release \
	(tclStubsPtr->tcl_Release) /* 216 */
#define Tcl_ResetResult \
	(tclStubsPtr->tcl_ResetResult) /* 217 */
#define Tcl_ScanElement \
	(tclStubsPtr->tcl_ScanElement) /* 218 */
#define Tcl_ScanCountedElement \
	(tclStubsPtr->tcl_ScanCountedElement) /* 219 */
/* Slot 220 is reserved */
#define Tcl_ServiceAll \
	(tclStubsPtr->tcl_ServiceAll) /* 221 */
#define Tcl_ServiceEvent \
	(tclStubsPtr->tcl_ServiceEvent) /* 222 */
#define Tcl_SetAssocData \
	(tclStubsPtr->tcl_SetAssocData) /* 223 */
#define Tcl_SetChannelBufferSize \
	(tclStubsPtr->tcl_SetChannelBufferSize) /* 224 */
#define Tcl_SetChannelOption \
	(tclStubsPtr->tcl_SetChannelOption) /* 225 */
#define Tcl_SetCommandInfo \
	(tclStubsPtr->tcl_SetCommandInfo) /* 226 */
#define Tcl_SetErrno \
	(tclStubsPtr->tcl_SetErrno) /* 227 */
#define Tcl_SetErrorCode \
	(tclStubsPtr->tcl_SetErrorCode) /* 228 */
#define Tcl_SetMaxBlockTime \
	(tclStubsPtr->tcl_SetMaxBlockTime) /* 229 */
/* Slot 230 is reserved */
#define Tcl_SetRecursionLimit \
	(tclStubsPtr->tcl_SetRecursionLimit) /* 231 */
/* Slot 232 is reserved */
#define Tcl_SetServiceMode \
	(tclStubsPtr->tcl_SetServiceMode) /* 233 */
#define Tcl_SetObjErrorCode \
	(tclStubsPtr->tcl_SetObjErrorCode) /* 234 */
#define Tcl_SetObjResult \
	(tclStubsPtr->tcl_SetObjResult) /* 235 */
#define Tcl_SetStdChannel \
	(tclStubsPtr->tcl_SetStdChannel) /* 236 */
/* Slot 237 is reserved */
#define Tcl_SetVar2 \
	(tclStubsPtr->tcl_SetVar2) /* 238 */
#define Tcl_SignalId \
	(tclStubsPtr->tcl_SignalId) /* 239 */
#define Tcl_SignalMsg \
	(tclStubsPtr->tcl_SignalMsg) /* 240 */
#define Tcl_SourceRCFile \
	(tclStubsPtr->tcl_SourceRCFile) /* 241 */
#define TclSplitList \
	(tclStubsPtr->tclSplitList) /* 242 */
#define TclSplitPath \
	(tclStubsPtr->tclSplitPath) /* 243 */
/* Slot 244 is reserved */
/* Slot 245 is reserved */
/* Slot 246 is reserved */
/* Slot 247 is reserved */
#define Tcl_TraceVar2 \
	(tclStubsPtr->tcl_TraceVar2) /* 248 */
#define Tcl_TranslateFileName \
	(tclStubsPtr->tcl_TranslateFileName) /* 249 */
#define Tcl_Ungets \
	(tclStubsPtr->tcl_Ungets) /* 250 */
#define Tcl_UnlinkVar \
	(tclStubsPtr->tcl_UnlinkVar) /* 251 */
#define Tcl_UnregisterChannel \
	(tclStubsPtr->tcl_UnregisterChannel) /* 252 */
/* Slot 253 is reserved */
#define Tcl_UnsetVar2 \
	(tclStubsPtr->tcl_UnsetVar2) /* 254 */
/* Slot 255 is reserved */
#define Tcl_UntraceVar2 \
	(tclStubsPtr->tcl_UntraceVar2) /* 256 */
#define Tcl_UpdateLinkedVar \
	(tclStubsPtr->tcl_UpdateLinkedVar) /* 257 */
/* Slot 258 is reserved */
#define Tcl_UpVar2 \
	(tclStubsPtr->tcl_UpVar2) /* 259 */
#define Tcl_VarEval \
	(tclStubsPtr->tcl_VarEval) /* 260 */
/* Slot 261 is reserved */
#define Tcl_VarTraceInfo2 \
	(tclStubsPtr->tcl_VarTraceInfo2) /* 262 */
#define Tcl_Write \
	(tclStubsPtr->tcl_Write) /* 263 */
#define Tcl_WrongNumArgs \
	(tclStubsPtr->tcl_WrongNumArgs) /* 264 */
#define Tcl_DumpActiveMemory \
	(tclStubsPtr->tcl_DumpActiveMemory) /* 265 */
#define Tcl_ValidateAllMemory \
	(tclStubsPtr->tcl_ValidateAllMemory) /* 266 */
/* Slot 267 is reserved */
/* Slot 268 is reserved */
#define Tcl_HashStats \
	(tclStubsPtr->tcl_HashStats) /* 269 */
#define Tcl_ParseVar \
	(tclStubsPtr->tcl_ParseVar) /* 270 */
/* Slot 271 is reserved */
#define Tcl_PkgPresentEx \
	(tclStubsPtr->tcl_PkgPresentEx) /* 272 */
/* Slot 273 is reserved */
/* Slot 274 is reserved */
/* Slot 275 is reserved */
/* Slot 276 is reserved */
#define Tcl_WaitPid \
	(tclStubsPtr->tcl_WaitPid) /* 277 */
/* Slot 278 is reserved */
#define Tcl_GetVersion \
	(tclStubsPtr->tcl_GetVersion) /* 279 */
#define Tcl_InitMemory \
	(tclStubsPtr->tcl_InitMemory) /* 280 */
#define Tcl_StackChannel \
	(tclStubsPtr->tcl_StackChannel) /* 281 */
#define Tcl_UnstackChannel \
	(tclStubsPtr->tcl_UnstackChannel) /* 282 */
#define Tcl_GetStackedChannel \
	(tclStubsPtr->tcl_GetStackedChannel) /* 283 */
#define Tcl_SetMainLoop \
	(tclStubsPtr->tcl_SetMainLoop) /* 284 */
#define Tcl_GetAliasObj \
	(tclStubsPtr->tcl_GetAliasObj) /* 285 */
#define Tcl_AppendObjToObj \
	(tclStubsPtr->tcl_AppendObjToObj) /* 286 */
#define Tcl_CreateEncoding \
	(tclStubsPtr->tcl_CreateEncoding) /* 287 */
#define Tcl_CreateThreadExitHandler \
	(tclStubsPtr->tcl_CreateThreadExitHandler) /* 288 */
#define Tcl_DeleteThreadExitHandler \
	(tclStubsPtr->tcl_DeleteThreadExitHandler) /* 289 */
/* Slot 290 is reserved */
#define Tcl_EvalEx \
	(tclStubsPtr->tcl_EvalEx) /* 291 */
#define Tcl_EvalObjv \
	(tclStubsPtr->tcl_EvalObjv) /* 292 */
#define Tcl_EvalObjEx \
	(tclStubsPtr->tcl_EvalObjEx) /* 293 */
#define Tcl_ExitThread \
	(tclStubsPtr->tcl_ExitThread) /* 294 */
#define Tcl_ExternalToUtf \
	(tclStubsPtr->tcl_ExternalToUtf) /* 295 */
#define Tcl_ExternalToUtfDString \
	(tclStubsPtr->tcl_ExternalToUtfDString) /* 296 */
#define Tcl_FinalizeThread \
	(tclStubsPtr->tcl_FinalizeThread) /* 297 */
#define Tcl_FinalizeNotifier \
	(tclStubsPtr->tcl_FinalizeNotifier) /* 298 */
#define Tcl_FreeEncoding \
	(tclStubsPtr->tcl_FreeEncoding) /* 299 */
#define Tcl_GetCurrentThread \
	(tclStubsPtr->tcl_GetCurrentThread) /* 300 */
#define Tcl_GetEncoding \
	(tclStubsPtr->tcl_GetEncoding) /* 301 */
#define Tcl_GetEncodingName \
	(tclStubsPtr->tcl_GetEncodingName) /* 302 */
#define Tcl_GetEncodingNames \
	(tclStubsPtr->tcl_GetEncodingNames) /* 303 */
#define Tcl_GetIndexFromObjStruct \
	(tclStubsPtr->tcl_GetIndexFromObjStruct) /* 304 */
#define Tcl_GetThreadData \
	(tclStubsPtr->tcl_GetThreadData) /* 305 */
#define Tcl_GetVar2Ex \
	(tclStubsPtr->tcl_GetVar2Ex) /* 306 */
#define Tcl_InitNotifier \
	(tclStubsPtr->tcl_InitNotifier) /* 307 */
#define Tcl_MutexLock \
	(tclStubsPtr->tcl_MutexLock) /* 308 */
#define Tcl_MutexUnlock \
	(tclStubsPtr->tcl_MutexUnlock) /* 309 */
#define Tcl_ConditionNotify \
	(tclStubsPtr->tcl_ConditionNotify) /* 310 */
#define Tcl_ConditionWait \
	(tclStubsPtr->tcl_ConditionWait) /* 311 */
#define TclNumUtfChars \
	(tclStubsPtr->tclNumUtfChars) /* 312 */
#define Tcl_ReadChars \
	(tclStubsPtr->tcl_ReadChars) /* 313 */
/* Slot 314 is reserved */
/* Slot 315 is reserved */
#define Tcl_SetSystemEncoding \
	(tclStubsPtr->tcl_SetSystemEncoding) /* 316 */
#define Tcl_SetVar2Ex \
	(tclStubsPtr->tcl_SetVar2Ex) /* 317 */
#define Tcl_ThreadAlert \
	(tclStubsPtr->tcl_ThreadAlert) /* 318 */
#define Tcl_ThreadQueueEvent \
	(tclStubsPtr->tcl_ThreadQueueEvent) /* 319 */
#define Tcl_UniCharAtIndex \
	(tclStubsPtr->tcl_UniCharAtIndex) /* 320 */
#define Tcl_UniCharToLower \
	(tclStubsPtr->tcl_UniCharToLower) /* 321 */
#define Tcl_UniCharToTitle \
	(tclStubsPtr->tcl_UniCharToTitle) /* 322 */
#define Tcl_UniCharToUpper \
	(tclStubsPtr->tcl_UniCharToUpper) /* 323 */
#define Tcl_UniCharToUtf \
	(tclStubsPtr->tcl_UniCharToUtf) /* 324 */
#define TclUtfAtIndex \
	(tclStubsPtr->tclUtfAtIndex) /* 325 */
#define TclUtfCharComplete \
	(tclStubsPtr->tclUtfCharComplete) /* 326 */
#define Tcl_UtfBackslash \
	(tclStubsPtr->tcl_UtfBackslash) /* 327 */
#define Tcl_UtfFindFirst \
	(tclStubsPtr->tcl_UtfFindFirst) /* 328 */
#define Tcl_UtfFindLast \
	(tclStubsPtr->tcl_UtfFindLast) /* 329 */
#define TclUtfNext \
	(tclStubsPtr->tclUtfNext) /* 330 */
#define TclUtfPrev \
	(tclStubsPtr->tclUtfPrev) /* 331 */
#define Tcl_UtfToExternal \
	(tclStubsPtr->tcl_UtfToExternal) /* 332 */
#define Tcl_UtfToExternalDString \
	(tclStubsPtr->tcl_UtfToExternalDString) /* 333 */
#define Tcl_UtfToLower \
	(tclStubsPtr->tcl_UtfToLower) /* 334 */
#define Tcl_UtfToTitle \
	(tclStubsPtr->tcl_UtfToTitle) /* 335 */
#define Tcl_UtfToChar16 \
	(tclStubsPtr->tcl_UtfToChar16) /* 336 */
#define Tcl_UtfToUpper \
	(tclStubsPtr->tcl_UtfToUpper) /* 337 */
#define Tcl_WriteChars \
	(tclStubsPtr->tcl_WriteChars) /* 338 */
#define Tcl_WriteObj \
	(tclStubsPtr->tcl_WriteObj) /* 339 */
#define Tcl_GetString \
	(tclStubsPtr->tcl_GetString) /* 340 */
/* Slot 341 is reserved */
/* Slot 342 is reserved */
#define Tcl_AlertNotifier \
	(tclStubsPtr->tcl_AlertNotifier) /* 343 */
#define Tcl_ServiceModeHook \
	(tclStubsPtr->tcl_ServiceModeHook) /* 344 */
#define Tcl_UniCharIsAlnum \
	(tclStubsPtr->tcl_UniCharIsAlnum) /* 345 */
#define Tcl_UniCharIsAlpha \
	(tclStubsPtr->tcl_UniCharIsAlpha) /* 346 */
#define Tcl_UniCharIsDigit \
	(tclStubsPtr->tcl_UniCharIsDigit) /* 347 */
#define Tcl_UniCharIsLower \
	(tclStubsPtr->tcl_UniCharIsLower) /* 348 */
#define Tcl_UniCharIsSpace \
	(tclStubsPtr->tcl_UniCharIsSpace) /* 349 */
#define Tcl_UniCharIsUpper \
	(tclStubsPtr->tcl_UniCharIsUpper) /* 350 */
#define Tcl_UniCharIsWordChar \
	(tclStubsPtr->tcl_UniCharIsWordChar) /* 351 */
#define Tcl_Char16Len \
	(tclStubsPtr->tcl_Char16Len) /* 352 */
/* Slot 353 is reserved */
#define Tcl_Char16ToUtfDString \
	(tclStubsPtr->tcl_Char16ToUtfDString) /* 354 */
#define Tcl_UtfToChar16DString \
	(tclStubsPtr->tcl_UtfToChar16DString) /* 355 */
#define Tcl_GetRegExpFromObj \
	(tclStubsPtr->tcl_GetRegExpFromObj) /* 356 */
/* Slot 357 is reserved */
#define Tcl_FreeParse \
	(tclStubsPtr->tcl_FreeParse) /* 358 */
#define Tcl_LogCommandInfo \
	(tclStubsPtr->tcl_LogCommandInfo) /* 359 */
#define Tcl_ParseBraces \
	(tclStubsPtr->tcl_ParseBraces) /* 360 */
#define Tcl_ParseCommand \
	(tclStubsPtr->tcl_ParseCommand) /* 361 */
#define Tcl_ParseExpr \
	(tclStubsPtr->tcl_ParseExpr) /* 362 */
#define Tcl_ParseQuotedString \
	(tclStubsPtr->tcl_ParseQuotedString) /* 363 */
#define Tcl_ParseVarName \
	(tclStubsPtr->tcl_ParseVarName) /* 364 */
#define Tcl_GetCwd \
	(tclStubsPtr->tcl_GetCwd) /* 365 */
#define Tcl_Chdir \
	(tclStubsPtr->tcl_Chdir) /* 366 */
#define Tcl_Access \
	(tclStubsPtr->tcl_Access) /* 367 */
#define Tcl_Stat \
	(tclStubsPtr->tcl_Stat) /* 368 */
#define TclUtfNcmp \
	(tclStubsPtr->tclUtfNcmp) /* 369 */
#define TclUtfNcasecmp \
	(tclStubsPtr->tclUtfNcasecmp) /* 370 */
#define Tcl_StringCaseMatch \
	(tclStubsPtr->tcl_StringCaseMatch) /* 371 */
#define Tcl_UniCharIsControl \
	(tclStubsPtr->tcl_UniCharIsControl) /* 372 */
#define Tcl_UniCharIsGraph \
	(tclStubsPtr->tcl_UniCharIsGraph) /* 373 */
#define Tcl_UniCharIsPrint \
	(tclStubsPtr->tcl_UniCharIsPrint) /* 374 */
#define Tcl_UniCharIsPunct \
	(tclStubsPtr->tcl_UniCharIsPunct) /* 375 */
#define Tcl_RegExpExecObj \
	(tclStubsPtr->tcl_RegExpExecObj) /* 376 */
#define Tcl_RegExpGetInfo \
	(tclStubsPtr->tcl_RegExpGetInfo) /* 377 */
#define Tcl_NewUnicodeObj \
	(tclStubsPtr->tcl_NewUnicodeObj) /* 378 */
#define Tcl_SetUnicodeObj \
	(tclStubsPtr->tcl_SetUnicodeObj) /* 379 */
#define TclGetCharLength \
	(tclStubsPtr->tclGetCharLength) /* 380 */
#define TclGetUniChar \
	(tclStubsPtr->tclGetUniChar) /* 381 */
/* Slot 382 is reserved */
#define TclGetRange \
	(tclStubsPtr->tclGetRange) /* 383 */
#define Tcl_AppendUnicodeToObj \
	(tclStubsPtr->tcl_AppendUnicodeToObj) /* 384 */
#define Tcl_RegExpMatchObj \
	(tclStubsPtr->tcl_RegExpMatchObj) /* 385 */
#define Tcl_SetNotifier \
	(tclStubsPtr->tcl_SetNotifier) /* 386 */
#define Tcl_GetAllocMutex \
	(tclStubsPtr->tcl_GetAllocMutex) /* 387 */
#define Tcl_GetChannelNames \
	(tclStubsPtr->tcl_GetChannelNames) /* 388 */
#define Tcl_GetChannelNamesEx \
	(tclStubsPtr->tcl_GetChannelNamesEx) /* 389 */
#define Tcl_ProcObjCmd \
	(tclStubsPtr->tcl_ProcObjCmd) /* 390 */
#define Tcl_ConditionFinalize \
	(tclStubsPtr->tcl_ConditionFinalize) /* 391 */
#define Tcl_MutexFinalize \
	(tclStubsPtr->tcl_MutexFinalize) /* 392 */
#define Tcl_CreateThread \
	(tclStubsPtr->tcl_CreateThread) /* 393 */
#define Tcl_ReadRaw \
	(tclStubsPtr->tcl_ReadRaw) /* 394 */
#define Tcl_WriteRaw \
	(tclStubsPtr->tcl_WriteRaw) /* 395 */
#define Tcl_GetTopChannel \
	(tclStubsPtr->tcl_GetTopChannel) /* 396 */
#define Tcl_ChannelBuffered \
	(tclStubsPtr->tcl_ChannelBuffered) /* 397 */
#define Tcl_ChannelName \
	(tclStubsPtr->tcl_ChannelName) /* 398 */
#define Tcl_ChannelVersion \
	(tclStubsPtr->tcl_ChannelVersion) /* 399 */
#define Tcl_ChannelBlockModeProc \
	(tclStubsPtr->tcl_ChannelBlockModeProc) /* 400 */
/* Slot 401 is reserved */
#define Tcl_ChannelClose2Proc \
	(tclStubsPtr->tcl_ChannelClose2Proc) /* 402 */
#define Tcl_ChannelInputProc \
	(tclStubsPtr->tcl_ChannelInputProc) /* 403 */
#define Tcl_ChannelOutputProc \
	(tclStubsPtr->tcl_ChannelOutputProc) /* 404 */
/* Slot 405 is reserved */
#define Tcl_ChannelSetOptionProc \
	(tclStubsPtr->tcl_ChannelSetOptionProc) /* 406 */
#define Tcl_ChannelGetOptionProc \
	(tclStubsPtr->tcl_ChannelGetOptionProc) /* 407 */
#define Tcl_ChannelWatchProc \
	(tclStubsPtr->tcl_ChannelWatchProc) /* 408 */
#define Tcl_ChannelGetHandleProc \
	(tclStubsPtr->tcl_ChannelGetHandleProc) /* 409 */
#define Tcl_ChannelFlushProc \
	(tclStubsPtr->tcl_ChannelFlushProc) /* 410 */
#define Tcl_ChannelHandlerProc \
	(tclStubsPtr->tcl_ChannelHandlerProc) /* 411 */
#define Tcl_JoinThread \
	(tclStubsPtr->tcl_JoinThread) /* 412 */
#define Tcl_IsChannelShared \
	(tclStubsPtr->tcl_IsChannelShared) /* 413 */
#define Tcl_IsChannelRegistered \
	(tclStubsPtr->tcl_IsChannelRegistered) /* 414 */
#define Tcl_CutChannel \
	(tclStubsPtr->tcl_CutChannel) /* 415 */
#define Tcl_SpliceChannel \
	(tclStubsPtr->tcl_SpliceChannel) /* 416 */
#define Tcl_ClearChannelHandlers \
	(tclStubsPtr->tcl_ClearChannelHandlers) /* 417 */
#define Tcl_IsChannelExisting \
	(tclStubsPtr->tcl_IsChannelExisting) /* 418 */
/* Slot 419 is reserved */
/* Slot 420 is reserved */
/* Slot 421 is reserved */
#define Tcl_CreateHashEntry \
	(tclStubsPtr->tcl_CreateHashEntry) /* 422 */
#define Tcl_InitCustomHashTable \
	(tclStubsPtr->tcl_InitCustomHashTable) /* 423 */
#define Tcl_InitObjHashTable \
	(tclStubsPtr->tcl_InitObjHashTable) /* 424 */
#define Tcl_CommandTraceInfo \
	(tclStubsPtr->tcl_CommandTraceInfo) /* 425 */
#define Tcl_TraceCommand \
	(tclStubsPtr->tcl_TraceCommand) /* 426 */
#define Tcl_UntraceCommand \
	(tclStubsPtr->tcl_UntraceCommand) /* 427 */
#define Tcl_AttemptAlloc \
	(tclStubsPtr->tcl_AttemptAlloc) /* 428 */
#define Tcl_AttemptDbCkalloc \
	(tclStubsPtr->tcl_AttemptDbCkalloc) /* 429 */
#define Tcl_AttemptRealloc \
	(tclStubsPtr->tcl_AttemptRealloc) /* 430 */
#define Tcl_AttemptDbCkrealloc \
	(tclStubsPtr->tcl_AttemptDbCkrealloc) /* 431 */
#define Tcl_AttemptSetObjLength \
	(tclStubsPtr->tcl_AttemptSetObjLength) /* 432 */
#define Tcl_GetChannelThread \
	(tclStubsPtr->tcl_GetChannelThread) /* 433 */
#define TclGetUnicodeFromObj \
	(tclStubsPtr->tclGetUnicodeFromObj) /* 434 */
/* Slot 435 is reserved */
/* Slot 436 is reserved */
#define Tcl_SubstObj \
	(tclStubsPtr->tcl_SubstObj) /* 437 */
#define Tcl_DetachChannel \
	(tclStubsPtr->tcl_DetachChannel) /* 438 */
#define Tcl_IsStandardChannel \
	(tclStubsPtr->tcl_IsStandardChannel) /* 439 */
#define Tcl_FSCopyFile \
	(tclStubsPtr->tcl_FSCopyFile) /* 440 */
#define Tcl_FSCopyDirectory \
	(tclStubsPtr->tcl_FSCopyDirectory) /* 441 */
#define Tcl_FSCreateDirectory \
	(tclStubsPtr->tcl_FSCreateDirectory) /* 442 */
#define Tcl_FSDeleteFile \
	(tclStubsPtr->tcl_FSDeleteFile) /* 443 */
#define Tcl_FSLoadFile \
	(tclStubsPtr->tcl_FSLoadFile) /* 444 */
#define Tcl_FSMatchInDirectory \
	(tclStubsPtr->tcl_FSMatchInDirectory) /* 445 */
#define Tcl_FSLink \
	(tclStubsPtr->tcl_FSLink) /* 446 */
#define Tcl_FSRemoveDirectory \
	(tclStubsPtr->tcl_FSRemoveDirectory) /* 447 */
#define Tcl_FSRenameFile \
	(tclStubsPtr->tcl_FSRenameFile) /* 448 */
#define Tcl_FSLstat \
	(tclStubsPtr->tcl_FSLstat) /* 449 */
#define Tcl_FSUtime \
	(tclStubsPtr->tcl_FSUtime) /* 450 */
#define Tcl_FSFileAttrsGet \
	(tclStubsPtr->tcl_FSFileAttrsGet) /* 451 */
#define Tcl_FSFileAttrsSet \
	(tclStubsPtr->tcl_FSFileAttrsSet) /* 452 */
#define Tcl_FSFileAttrStrings \
	(tclStubsPtr->tcl_FSFileAttrStrings) /* 453 */
#define Tcl_FSStat \
	(tclStubsPtr->tcl_FSStat) /* 454 */
#define Tcl_FSAccess \
	(tclStubsPtr->tcl_FSAccess) /* 455 */
#define Tcl_FSOpenFileChannel \
	(tclStubsPtr->tcl_FSOpenFileChannel) /* 456 */
#define Tcl_FSGetCwd \
	(tclStubsPtr->tcl_FSGetCwd) /* 457 */
#define Tcl_FSChdir \
	(tclStubsPtr->tcl_FSChdir) /* 458 */
#define Tcl_FSConvertToPathType \
	(tclStubsPtr->tcl_FSConvertToPathType) /* 459 */
#define Tcl_FSJoinPath \
	(tclStubsPtr->tcl_FSJoinPath) /* 460 */
#define TclFSSplitPath \
	(tclStubsPtr->tclFSSplitPath) /* 461 */
#define Tcl_FSEqualPaths \
	(tclStubsPtr->tcl_FSEqualPaths) /* 462 */
#define Tcl_FSGetNormalizedPath \
	(tclStubsPtr->tcl_FSGetNormalizedPath) /* 463 */
#define Tcl_FSJoinToPath \
	(tclStubsPtr->tcl_FSJoinToPath) /* 464 */
#define Tcl_FSGetInternalRep \
	(tclStubsPtr->tcl_FSGetInternalRep) /* 465 */
#define Tcl_FSGetTranslatedPath \
	(tclStubsPtr->tcl_FSGetTranslatedPath) /* 466 */
#define Tcl_FSEvalFile \
	(tclStubsPtr->tcl_FSEvalFile) /* 467 */
#define Tcl_FSNewNativePath \
	(tclStubsPtr->tcl_FSNewNativePath) /* 468 */
#define Tcl_FSGetNativePath \
	(tclStubsPtr->tcl_FSGetNativePath) /* 469 */
#define Tcl_FSFileSystemInfo \
	(tclStubsPtr->tcl_FSFileSystemInfo) /* 470 */
#define Tcl_FSPathSeparator \
	(tclStubsPtr->tcl_FSPathSeparator) /* 471 */
#define Tcl_FSListVolumes \
	(tclStubsPtr->tcl_FSListVolumes) /* 472 */
#define Tcl_FSRegister \
	(tclStubsPtr->tcl_FSRegister) /* 473 */
#define Tcl_FSUnregister \
	(tclStubsPtr->tcl_FSUnregister) /* 474 */
#define Tcl_FSData \
	(tclStubsPtr->tcl_FSData) /* 475 */
#define Tcl_FSGetTranslatedStringPath \
	(tclStubsPtr->tcl_FSGetTranslatedStringPath) /* 476 */
#define Tcl_FSGetFileSystemForPath \
	(tclStubsPtr->tcl_FSGetFileSystemForPath) /* 477 */
#define Tcl_FSGetPathType \
	(tclStubsPtr->tcl_FSGetPathType) /* 478 */
#define Tcl_OutputBuffered \
	(tclStubsPtr->tcl_OutputBuffered) /* 479 */
#define Tcl_FSMountsChanged \
	(tclStubsPtr->tcl_FSMountsChanged) /* 480 */
#define Tcl_EvalTokensStandard \
	(tclStubsPtr->tcl_EvalTokensStandard) /* 481 */
#define Tcl_GetTime \
	(tclStubsPtr->tcl_GetTime) /* 482 */
#define Tcl_CreateObjTrace \
	(tclStubsPtr->tcl_CreateObjTrace) /* 483 */
#define Tcl_GetCommandInfoFromToken \
	(tclStubsPtr->tcl_GetCommandInfoFromToken) /* 484 */
#define Tcl_SetCommandInfoFromToken \
	(tclStubsPtr->tcl_SetCommandInfoFromToken) /* 485 */
#define Tcl_DbNewWideIntObj \
	(tclStubsPtr->tcl_DbNewWideIntObj) /* 486 */
#define Tcl_GetWideIntFromObj \
	(tclStubsPtr->tcl_GetWideIntFromObj) /* 487 */
#define Tcl_NewWideIntObj \
	(tclStubsPtr->tcl_NewWideIntObj) /* 488 */
#define Tcl_SetWideIntObj \
	(tclStubsPtr->tcl_SetWideIntObj) /* 489 */
#define Tcl_AllocStatBuf \
	(tclStubsPtr->tcl_AllocStatBuf) /* 490 */
#define Tcl_Seek \
	(tclStubsPtr->tcl_Seek) /* 491 */
#define Tcl_Tell \
	(tclStubsPtr->tcl_Tell) /* 492 */
#define Tcl_ChannelWideSeekProc \
	(tclStubsPtr->tcl_ChannelWideSeekProc) /* 493 */
#define Tcl_DictObjPut \
	(tclStubsPtr->tcl_DictObjPut) /* 494 */
#define Tcl_DictObjGet \
	(tclStubsPtr->tcl_DictObjGet) /* 495 */
#define Tcl_DictObjRemove \
	(tclStubsPtr->tcl_DictObjRemove) /* 496 */
#define TclDictObjSize \
	(tclStubsPtr->tclDictObjSize) /* 497 */
#define Tcl_DictObjFirst \
	(tclStubsPtr->tcl_DictObjFirst) /* 498 */
#define Tcl_DictObjNext \
	(tclStubsPtr->tcl_DictObjNext) /* 499 */
#define Tcl_DictObjDone \
	(tclStubsPtr->tcl_DictObjDone) /* 500 */
#define Tcl_DictObjPutKeyList \
	(tclStubsPtr->tcl_DictObjPutKeyList) /* 501 */
#define Tcl_DictObjRemoveKeyList \
	(tclStubsPtr->tcl_DictObjRemoveKeyList) /* 502 */
#define Tcl_NewDictObj \
	(tclStubsPtr->tcl_NewDictObj) /* 503 */
#define Tcl_DbNewDictObj \
	(tclStubsPtr->tcl_DbNewDictObj) /* 504 */
#define Tcl_RegisterConfig \
	(tclStubsPtr->tcl_RegisterConfig) /* 505 */
#define Tcl_CreateNamespace \
	(tclStubsPtr->tcl_CreateNamespace) /* 506 */
#define Tcl_DeleteNamespace \
	(tclStubsPtr->tcl_DeleteNamespace) /* 507 */
#define Tcl_AppendExportList \
	(tclStubsPtr->tcl_AppendExportList) /* 508 */
#define Tcl_Export \
	(tclStubsPtr->tcl_Export) /* 509 */
#define Tcl_Import \
	(tclStubsPtr->tcl_Import) /* 510 */
#define Tcl_ForgetImport \
	(tclStubsPtr->tcl_ForgetImport) /* 511 */
#define Tcl_GetCurrentNamespace \
	(tclStubsPtr->tcl_GetCurrentNamespace) /* 512 */
#define Tcl_GetGlobalNamespace \
	(tclStubsPtr->tcl_GetGlobalNamespace) /* 513 */
#define Tcl_FindNamespace \
	(tclStubsPtr->tcl_FindNamespace) /* 514 */
#define Tcl_FindCommand \
	(tclStubsPtr->tcl_FindCommand) /* 515 */
#define Tcl_GetCommandFromObj \
	(tclStubsPtr->tcl_GetCommandFromObj) /* 516 */
#define Tcl_GetCommandFullName \
	(tclStubsPtr->tcl_GetCommandFullName) /* 517 */
#define Tcl_FSEvalFileEx \
	(tclStubsPtr->tcl_FSEvalFileEx) /* 518 */
/* Slot 519 is reserved */
#define Tcl_LimitAddHandler \
	(tclStubsPtr->tcl_LimitAddHandler) /* 520 */
#define Tcl_LimitRemoveHandler \
	(tclStubsPtr->tcl_LimitRemoveHandler) /* 521 */
#define Tcl_LimitReady \
	(tclStubsPtr->tcl_LimitReady) /* 522 */
#define Tcl_LimitCheck \
	(tclStubsPtr->tcl_LimitCheck) /* 523 */
#define Tcl_LimitExceeded \
	(tclStubsPtr->tcl_LimitExceeded) /* 524 */
#define Tcl_LimitSetCommands \
	(tclStubsPtr->tcl_LimitSetCommands) /* 525 */
#define Tcl_LimitSetTime \
	(tclStubsPtr->tcl_LimitSetTime) /* 526 */
#define Tcl_LimitSetGranularity \
	(tclStubsPtr->tcl_LimitSetGranularity) /* 527 */
#define Tcl_LimitTypeEnabled \
	(tclStubsPtr->tcl_LimitTypeEnabled) /* 528 */
#define Tcl_LimitTypeExceeded \
	(tclStubsPtr->tcl_LimitTypeExceeded) /* 529 */
#define Tcl_LimitTypeSet \
	(tclStubsPtr->tcl_LimitTypeSet) /* 530 */
#define Tcl_LimitTypeReset \
	(tclStubsPtr->tcl_LimitTypeReset) /* 531 */
#define Tcl_LimitGetCommands \
	(tclStubsPtr->tcl_LimitGetCommands) /* 532 */
#define Tcl_LimitGetTime \
	(tclStubsPtr->tcl_LimitGetTime) /* 533 */
#define Tcl_LimitGetGranularity \
	(tclStubsPtr->tcl_LimitGetGranularity) /* 534 */
#define Tcl_SaveInterpState \
	(tclStubsPtr->tcl_SaveInterpState) /* 535 */
#define Tcl_RestoreInterpState \
	(tclStubsPtr->tcl_RestoreInterpState) /* 536 */
#define Tcl_DiscardInterpState \
	(tclStubsPtr->tcl_DiscardInterpState) /* 537 */
#define Tcl_SetReturnOptions \
	(tclStubsPtr->tcl_SetReturnOptions) /* 538 */
#define Tcl_GetReturnOptions \
	(tclStubsPtr->tcl_GetReturnOptions) /* 539 */
#define Tcl_IsEnsemble \
	(tclStubsPtr->tcl_IsEnsemble) /* 540 */
#define Tcl_CreateEnsemble \
	(tclStubsPtr->tcl_CreateEnsemble) /* 541 */
#define Tcl_FindEnsemble \
	(tclStubsPtr->tcl_FindEnsemble) /* 542 */
#define Tcl_SetEnsembleSubcommandList \
	(tclStubsPtr->tcl_SetEnsembleSubcommandList) /* 543 */
#define Tcl_SetEnsembleMappingDict \
	(tclStubsPtr->tcl_SetEnsembleMappingDict) /* 544 */
#define Tcl_SetEnsembleUnknownHandler \
	(tclStubsPtr->tcl_SetEnsembleUnknownHandler) /* 545 */
#define Tcl_SetEnsembleFlags \
	(tclStubsPtr->tcl_SetEnsembleFlags) /* 546 */
#define Tcl_GetEnsembleSubcommandList \
	(tclStubsPtr->tcl_GetEnsembleSubcommandList) /* 547 */
#define Tcl_GetEnsembleMappingDict \
	(tclStubsPtr->tcl_GetEnsembleMappingDict) /* 548 */
#define Tcl_GetEnsembleUnknownHandler \
	(tclStubsPtr->tcl_GetEnsembleUnknownHandler) /* 549 */
#define Tcl_GetEnsembleFlags \
	(tclStubsPtr->tcl_GetEnsembleFlags) /* 550 */
#define Tcl_GetEnsembleNamespace \
	(tclStubsPtr->tcl_GetEnsembleNamespace) /* 551 */
#define Tcl_SetTimeProc \
	(tclStubsPtr->tcl_SetTimeProc) /* 552 */
#define Tcl_QueryTimeProc \
	(tclStubsPtr->tcl_QueryTimeProc) /* 553 */
#define Tcl_ChannelThreadActionProc \
	(tclStubsPtr->tcl_ChannelThreadActionProc) /* 554 */
#define Tcl_NewBignumObj \
	(tclStubsPtr->tcl_NewBignumObj) /* 555 */
#define Tcl_DbNewBignumObj \
	(tclStubsPtr->tcl_DbNewBignumObj) /* 556 */
#define Tcl_SetBignumObj \
	(tclStubsPtr->tcl_SetBignumObj) /* 557 */
#define Tcl_GetBignumFromObj \
	(tclStubsPtr->tcl_GetBignumFromObj) /* 558 */
#define Tcl_TakeBignumFromObj \
	(tclStubsPtr->tcl_TakeBignumFromObj) /* 559 */
#define Tcl_TruncateChannel \
	(tclStubsPtr->tcl_TruncateChannel) /* 560 */
#define Tcl_ChannelTruncateProc \
	(tclStubsPtr->tcl_ChannelTruncateProc) /* 561 */
#define Tcl_SetChannelErrorInterp \
	(tclStubsPtr->tcl_SetChannelErrorInterp) /* 562 */
#define Tcl_GetChannelErrorInterp \
	(tclStubsPtr->tcl_GetChannelErrorInterp) /* 563 */
#define Tcl_SetChannelError \
	(tclStubsPtr->tcl_SetChannelError) /* 564 */
#define Tcl_GetChannelError \
	(tclStubsPtr->tcl_GetChannelError) /* 565 */
#define Tcl_InitBignumFromDouble \
	(tclStubsPtr->tcl_InitBignumFromDouble) /* 566 */
#define Tcl_GetNamespaceUnknownHandler \
	(tclStubsPtr->tcl_GetNamespaceUnknownHandler) /* 567 */
#define Tcl_SetNamespaceUnknownHandler \
	(tclStubsPtr->tcl_SetNamespaceUnknownHandler) /* 568 */
#define Tcl_GetEncodingFromObj \
	(tclStubsPtr->tcl_GetEncodingFromObj) /* 569 */
#define Tcl_GetEncodingSearchPath \
	(tclStubsPtr->tcl_GetEncodingSearchPath) /* 570 */
#define Tcl_SetEncodingSearchPath \
	(tclStubsPtr->tcl_SetEncodingSearchPath) /* 571 */
#define Tcl_GetEncodingNameFromEnvironment \
	(tclStubsPtr->tcl_GetEncodingNameFromEnvironment) /* 572 */
#define Tcl_PkgRequireProc \
	(tclStubsPtr->tcl_PkgRequireProc) /* 573 */
#define Tcl_AppendObjToErrorInfo \
	(tclStubsPtr->tcl_AppendObjToErrorInfo) /* 574 */
#define Tcl_AppendLimitedToObj \
	(tclStubsPtr->tcl_AppendLimitedToObj) /* 575 */
#define Tcl_Format \
	(tclStubsPtr->tcl_Format) /* 576 */
#define Tcl_AppendFormatToObj \
	(tclStubsPtr->tcl_AppendFormatToObj) /* 577 */
#define Tcl_ObjPrintf \
	(tclStubsPtr->tcl_ObjPrintf) /* 578 */
#define Tcl_AppendPrintfToObj \
	(tclStubsPtr->tcl_AppendPrintfToObj) /* 579 */
#define Tcl_CancelEval \
	(tclStubsPtr->tcl_CancelEval) /* 580 */
#define Tcl_Canceled \
	(tclStubsPtr->tcl_Canceled) /* 581 */
#define Tcl_CreatePipe \
	(tclStubsPtr->tcl_CreatePipe) /* 582 */
#define Tcl_NRCreateCommand \
	(tclStubsPtr->tcl_NRCreateCommand) /* 583 */
#define Tcl_NREvalObj \
	(tclStubsPtr->tcl_NREvalObj) /* 584 */
#define Tcl_NREvalObjv \
	(tclStubsPtr->tcl_NREvalObjv) /* 585 */
#define Tcl_NRCmdSwap \
	(tclStubsPtr->tcl_NRCmdSwap) /* 586 */
#define Tcl_NRAddCallback \
	(tclStubsPtr->tcl_NRAddCallback) /* 587 */
#define Tcl_NRCallObjProc \
	(tclStubsPtr->tcl_NRCallObjProc) /* 588 */
#define Tcl_GetFSDeviceFromStat \
	(tclStubsPtr->tcl_GetFSDeviceFromStat) /* 589 */
#define Tcl_GetFSInodeFromStat \
	(tclStubsPtr->tcl_GetFSInodeFromStat) /* 590 */
#define Tcl_GetModeFromStat \
	(tclStubsPtr->tcl_GetModeFromStat) /* 591 */
#define Tcl_GetLinkCountFromStat \
	(tclStubsPtr->tcl_GetLinkCountFromStat) /* 592 */
#define Tcl_GetUserIdFromStat \
	(tclStubsPtr->tcl_GetUserIdFromStat) /* 593 */
#define Tcl_GetGroupIdFromStat \
	(tclStubsPtr->tcl_GetGroupIdFromStat) /* 594 */
#define Tcl_GetDeviceTypeFromStat \
	(tclStubsPtr->tcl_GetDeviceTypeFromStat) /* 595 */
#define Tcl_GetAccessTimeFromStat \
	(tclStubsPtr->tcl_GetAccessTimeFromStat) /* 596 */
#define Tcl_GetModificationTimeFromStat \
	(tclStubsPtr->tcl_GetModificationTimeFromStat) /* 597 */
#define Tcl_GetChangeTimeFromStat \
	(tclStubsPtr->tcl_GetChangeTimeFromStat) /* 598 */
#define Tcl_GetSizeFromStat \
	(tclStubsPtr->tcl_GetSizeFromStat) /* 599 */
#define Tcl_GetBlocksFromStat \
	(tclStubsPtr->tcl_GetBlocksFromStat) /* 600 */
#define Tcl_GetBlockSizeFromStat \
	(tclStubsPtr->tcl_GetBlockSizeFromStat) /* 601 */
#define Tcl_SetEnsembleParameterList \
	(tclStubsPtr->tcl_SetEnsembleParameterList) /* 602 */
#define Tcl_GetEnsembleParameterList \
	(tclStubsPtr->tcl_GetEnsembleParameterList) /* 603 */
#define TclParseArgsObjv \
	(tclStubsPtr->tclParseArgsObjv) /* 604 */
#define Tcl_GetErrorLine \
	(tclStubsPtr->tcl_GetErrorLine) /* 605 */
#define Tcl_SetErrorLine \
	(tclStubsPtr->tcl_SetErrorLine) /* 606 */
#define Tcl_TransferResult \
	(tclStubsPtr->tcl_TransferResult) /* 607 */
#define Tcl_InterpActive \
	(tclStubsPtr->tcl_InterpActive) /* 608 */
#define Tcl_BackgroundException \
	(tclStubsPtr->tcl_BackgroundException) /* 609 */
#define Tcl_ZlibDeflate \
	(tclStubsPtr->tcl_ZlibDeflate) /* 610 */
#define Tcl_ZlibInflate \
	(tclStubsPtr->tcl_ZlibInflate) /* 611 */
#define Tcl_ZlibCRC32 \
	(tclStubsPtr->tcl_ZlibCRC32) /* 612 */
#define Tcl_ZlibAdler32 \
	(tclStubsPtr->tcl_ZlibAdler32) /* 613 */
#define Tcl_ZlibStreamInit \
	(tclStubsPtr->tcl_ZlibStreamInit) /* 614 */
#define Tcl_ZlibStreamGetCommandName \
	(tclStubsPtr->tcl_ZlibStreamGetCommandName) /* 615 */
#define Tcl_ZlibStreamEof \
	(tclStubsPtr->tcl_ZlibStreamEof) /* 616 */
#define Tcl_ZlibStreamChecksum \
	(tclStubsPtr->tcl_ZlibStreamChecksum) /* 617 */
#define Tcl_ZlibStreamPut \
	(tclStubsPtr->tcl_ZlibStreamPut) /* 618 */
#define Tcl_ZlibStreamGet \
	(tclStubsPtr->tcl_ZlibStreamGet) /* 619 */
#define Tcl_ZlibStreamClose \
	(tclStubsPtr->tcl_ZlibStreamClose) /* 620 */
#define Tcl_ZlibStreamReset \
	(tclStubsPtr->tcl_ZlibStreamReset) /* 621 */
#define Tcl_SetStartupScript \
	(tclStubsPtr->tcl_SetStartupScript) /* 622 */
#define Tcl_GetStartupScript \
	(tclStubsPtr->tcl_GetStartupScript) /* 623 */
#define Tcl_CloseEx \
	(tclStubsPtr->tcl_CloseEx) /* 624 */
#define Tcl_NRExprObj \
	(tclStubsPtr->tcl_NRExprObj) /* 625 */
#define Tcl_NRSubstObj \
	(tclStubsPtr->tcl_NRSubstObj) /* 626 */
#define Tcl_LoadFile \
	(tclStubsPtr->tcl_LoadFile) /* 627 */
#define Tcl_FindSymbol \
	(tclStubsPtr->tcl_FindSymbol) /* 628 */
#define Tcl_FSUnloadFile \
	(tclStubsPtr->tcl_FSUnloadFile) /* 629 */
#define Tcl_ZlibStreamSetCompressionDictionary \
	(tclStubsPtr->tcl_ZlibStreamSetCompressionDictionary) /* 630 */
#define Tcl_OpenTcpServerEx \
	(tclStubsPtr->tcl_OpenTcpServerEx) /* 631 */
#define TclZipfs_Mount \
	(tclStubsPtr->tclZipfs_Mount) /* 632 */
#define TclZipfs_Unmount \
	(tclStubsPtr->tclZipfs_Unmount) /* 633 */
#define TclZipfs_TclLibrary \
	(tclStubsPtr->tclZipfs_TclLibrary) /* 634 */
#define TclZipfs_MountBuffer \
	(tclStubsPtr->tclZipfs_MountBuffer) /* 635 */
#define Tcl_FreeInternalRep \
	(tclStubsPtr->tcl_FreeInternalRep) /* 636 */
#define Tcl_InitStringRep \
	(tclStubsPtr->tcl_InitStringRep) /* 637 */
#define Tcl_FetchInternalRep \
	(tclStubsPtr->tcl_FetchInternalRep) /* 638 */
#define Tcl_StoreInternalRep \
	(tclStubsPtr->tcl_StoreInternalRep) /* 639 */
#define Tcl_HasStringRep \
	(tclStubsPtr->tcl_HasStringRep) /* 640 */
#define Tcl_IncrRefCount \
	(tclStubsPtr->tcl_IncrRefCount) /* 641 */
#define Tcl_DecrRefCount \
	(tclStubsPtr->tcl_DecrRefCount) /* 642 */
#define Tcl_IsShared \
	(tclStubsPtr->tcl_IsShared) /* 643 */
#define Tcl_LinkArray \
	(tclStubsPtr->tcl_LinkArray) /* 644 */
#define Tcl_GetIntForIndex \
	(tclStubsPtr->tcl_GetIntForIndex) /* 645 */
#define Tcl_UtfToUniChar \
	(tclStubsPtr->tcl_UtfToUniChar) /* 646 */
#define Tcl_UniCharToUtfDString \
	(tclStubsPtr->tcl_UniCharToUtfDString) /* 647 */
#define Tcl_UtfToUniCharDString \
	(tclStubsPtr->tcl_UtfToUniCharDString) /* 648 */
#define TclGetBytesFromObj \
	(tclStubsPtr->tclGetBytesFromObj) /* 649 */
#define Tcl_GetBytesFromObj \
	(tclStubsPtr->tcl_GetBytesFromObj) /* 650 */
#define Tcl_GetStringFromObj \
	(tclStubsPtr->tcl_GetStringFromObj) /* 651 */
#define Tcl_GetUnicodeFromObj \
	(tclStubsPtr->tcl_GetUnicodeFromObj) /* 652 */
#define Tcl_GetSizeIntFromObj \
	(tclStubsPtr->tcl_GetSizeIntFromObj) /* 653 */
#define Tcl_UtfCharComplete \
	(tclStubsPtr->tcl_UtfCharComplete) /* 654 */
#define Tcl_UtfNext \
	(tclStubsPtr->tcl_UtfNext) /* 655 */
#define Tcl_UtfPrev \
	(tclStubsPtr->tcl_UtfPrev) /* 656 */
#define Tcl_FSTildeExpand \
	(tclStubsPtr->tcl_FSTildeExpand) /* 657 */
#define Tcl_ExternalToUtfDStringEx \
	(tclStubsPtr->tcl_ExternalToUtfDStringEx) /* 658 */
#define Tcl_UtfToExternalDStringEx \
	(tclStubsPtr->tcl_UtfToExternalDStringEx) /* 659 */
#define Tcl_AsyncMarkFromSignal \
	(tclStubsPtr->tcl_AsyncMarkFromSignal) /* 660 */
#define Tcl_ListObjGetElements \
	(tclStubsPtr->tcl_ListObjGetElements) /* 661 */
#define Tcl_ListObjLength \
	(tclStubsPtr->tcl_ListObjLength) /* 662 */
#define Tcl_DictObjSize \
	(tclStubsPtr->tcl_DictObjSize) /* 663 */
#define Tcl_SplitList \
	(tclStubsPtr->tcl_SplitList) /* 664 */
#define Tcl_SplitPath \
	(tclStubsPtr->tcl_SplitPath) /* 665 */
#define Tcl_FSSplitPath \
	(tclStubsPtr->tcl_FSSplitPath) /* 666 */
#define Tcl_ParseArgsObjv \
	(tclStubsPtr->tcl_ParseArgsObjv) /* 667 */
#define Tcl_UniCharLen \
	(tclStubsPtr->tcl_UniCharLen) /* 668 */
#define Tcl_NumUtfChars \
	(tclStubsPtr->tcl_NumUtfChars) /* 669 */
#define Tcl_GetCharLength \
	(tclStubsPtr->tcl_GetCharLength) /* 670 */
#define Tcl_UtfAtIndex \
	(tclStubsPtr->tcl_UtfAtIndex) /* 671 */
#define Tcl_GetRange \
	(tclStubsPtr->tcl_GetRange) /* 672 */
#define Tcl_GetUniChar \
	(tclStubsPtr->tcl_GetUniChar) /* 673 */
#define Tcl_GetBool \
	(tclStubsPtr->tcl_GetBool) /* 674 */
#define Tcl_GetBoolFromObj \
	(tclStubsPtr->tcl_GetBoolFromObj) /* 675 */
#define Tcl_CreateObjCommand2 \
	(tclStubsPtr->tcl_CreateObjCommand2) /* 676 */
#define Tcl_CreateObjTrace2 \
	(tclStubsPtr->tcl_CreateObjTrace2) /* 677 */
#define Tcl_NRCreateCommand2 \
	(tclStubsPtr->tcl_NRCreateCommand2) /* 678 */
#define Tcl_NRCallObjProc2 \
	(tclStubsPtr->tcl_NRCallObjProc2) /* 679 */
#define Tcl_GetNumberFromObj \
	(tclStubsPtr->tcl_GetNumberFromObj) /* 680 */
#define Tcl_GetNumber \
	(tclStubsPtr->tcl_GetNumber) /* 681 */
#define Tcl_RemoveChannelMode \
	(tclStubsPtr->tcl_RemoveChannelMode) /* 682 */
#define Tcl_GetEncodingNulLength \
	(tclStubsPtr->tcl_GetEncodingNulLength) /* 683 */
#define Tcl_GetWideUIntFromObj \
	(tclStubsPtr->tcl_GetWideUIntFromObj) /* 684 */
#define Tcl_DStringToObj \
	(tclStubsPtr->tcl_DStringToObj) /* 685 */
#define Tcl_UtfNcmp \
	(tclStubsPtr->tcl_UtfNcmp) /* 686 */
#define Tcl_UtfNcasecmp \
	(tclStubsPtr->tcl_UtfNcasecmp) /* 687 */
#define Tcl_NewWideUIntObj \
	(tclStubsPtr->tcl_NewWideUIntObj) /* 688 */
#define Tcl_SetWideUIntObj \
	(tclStubsPtr->tcl_SetWideUIntObj) /* 689 */
#define TclUnusedStubEntry \
	(tclStubsPtr->tclUnusedStubEntry) /* 690 */

#endif /* defined(USE_TCL_STUBS) */

/* !END!: Do not edit above this line. */

#undef TclUnusedStubEntry

#ifdef _WIN32
#   undef Tcl_CreateFileHandler
#   undef Tcl_DeleteFileHandler
#   undef Tcl_GetOpenFile
#endif

#undef TCL_STORAGE_CLASS
#define TCL_STORAGE_CLASS DLLIMPORT

#define Tcl_PkgPresent(interp, name, version, exact) \
	Tcl_PkgPresentEx(interp, name, version, exact, NULL)
#define Tcl_PkgProvide(interp, name, version) \
	Tcl_PkgProvideEx(interp, name, version, NULL)
#define Tcl_PkgRequire(interp, name, version, exact) \
	Tcl_PkgRequireEx(interp, name, version, exact, NULL)
#define Tcl_GetIndexFromObj(interp, objPtr, tablePtr, msg, flags, indexPtr) \
	Tcl_GetIndexFromObjStruct(interp, objPtr, tablePtr, \
	sizeof(char *), msg, flags, indexPtr)
#define Tcl_NewBooleanObj(intValue) \
	Tcl_NewWideIntObj((intValue)!=0)
#define Tcl_DbNewBooleanObj(intValue, file, line) \
	Tcl_DbNewWideIntObj((intValue)!=0, file, line)
#define Tcl_SetBooleanObj(objPtr, intValue) \
	Tcl_SetWideIntObj(objPtr, (intValue)!=0)
#define Tcl_SetVar(interp, varName, newValue, flags) \
	Tcl_SetVar2(interp, varName, NULL, newValue, flags)
#define Tcl_UnsetVar(interp, varName, flags) \
	Tcl_UnsetVar2(interp, varName, NULL, flags)
#define Tcl_GetVar(interp, varName, flags) \
	Tcl_GetVar2(interp, varName, NULL, flags)
#define Tcl_TraceVar(interp, varName, flags, proc, clientData) \
	Tcl_TraceVar2(interp, varName, NULL, flags, proc, clientData)
#define Tcl_UntraceVar(interp, varName, flags, proc, clientData) \
	Tcl_UntraceVar2(interp, varName, NULL, flags, proc, clientData)
#define Tcl_VarTraceInfo(interp, varName, flags, proc, prevClientData) \
	Tcl_VarTraceInfo2(interp, varName, NULL, flags, proc, prevClientData)
#define Tcl_UpVar(interp, frameName, varName, localName, flags) \
	Tcl_UpVar2(interp, frameName, varName, NULL, localName, flags)
#define Tcl_AddErrorInfo(interp, message) \
	Tcl_AppendObjToErrorInfo(interp, Tcl_NewStringObj(message, -1))
#define Tcl_AddObjErrorInfo(interp, message, length) \
	Tcl_AppendObjToErrorInfo(interp, Tcl_NewStringObj(message, length))
#define Tcl_Eval(interp, objPtr) \
	Tcl_EvalEx(interp, objPtr, TCL_INDEX_NONE, 0)
#define Tcl_GlobalEval(interp, objPtr) \
	Tcl_EvalEx(interp, objPtr, TCL_INDEX_NONE, TCL_EVAL_GLOBAL)
#define Tcl_GetStringResult(interp) Tcl_GetString(Tcl_GetObjResult(interp))
#define Tcl_SetResult(interp, result, freeProc) \
	do { \
	    const char *__result = result; \
	    Tcl_FreeProc *__freeProc = freeProc; \
	    Tcl_SetObjResult(interp, Tcl_NewStringObj(__result, -1)); \
	    if (__result != NULL && __freeProc != NULL && __freeProc != TCL_VOLATILE) { \
		if (__freeProc == TCL_DYNAMIC) { \
		    Tcl_Free((void *)__result); \
		} else { \
		    (*__freeProc)((void *)__result); \
		} \
	    } \
	} while(0)

#if defined(USE_TCL_STUBS)
#   if defined(_WIN32) && defined(_WIN64) && TCL_MAJOR_VERSION < 9
#	undef Tcl_GetTime
/* Handle Win64 tk.dll being loaded in Cygwin64 (only needed for Tcl 8). */
#	define Tcl_GetTime(t) \
		do { \
		    struct { \
			Tcl_Time now; \
			long long reserved; \
		    } _t; \
		    _t.reserved = -1; \
		    tclStubsPtr->tcl_GetTime((&_t.now)); \
		    if (_t.reserved != -1) { \
			_t.now.usec = (long) _t.reserved; \
		    } \
		    *(t) = _t.now; \
		} while (0)
#   endif
#   if defined(__CYGWIN__) && defined(TCL_WIDE_INT_IS_LONG)
/* On Cygwin64, long is 64-bit while on Win64 long is 32-bit. Therefore
 * we have to make sure that all stub entries on Cygwin64 follow the
 * Win64 signature. Cygwin64 stubbed extensions cannot use those stub
 * entries any more, they should use the 64-bit alternatives where
 * possible. Tcl 9 must find a better solution, but that cannot be done
 * without introducing a binary incompatibility.
 */
#	undef Tcl_GetLongFromObj
#	undef Tcl_ExprLong
#	undef Tcl_ExprLongObj
#	define Tcl_GetLongFromObj ((int(*)(Tcl_Interp*,Tcl_Obj*,long*))Tcl_GetWideIntFromObj)
#	define Tcl_ExprLong TclExprLong
	static inline int TclExprLong(Tcl_Interp *interp, const char *string, long *ptr){
	    int intValue;
	    int result = tclStubsPtr->tcl_ExprLong(interp, string, (long *)&intValue);
	    if (result == TCL_OK) *ptr = (long)intValue;
	    return result;
	}
#	define Tcl_ExprLongObj TclExprLongObj
	static inline int TclExprLongObj(Tcl_Interp *interp, Tcl_Obj *obj, long *ptr){
	    int intValue;
	    int result = tclStubsPtr->tcl_ExprLongObj(interp, obj, (long *)&intValue);
	    if (result == TCL_OK) *ptr = (long)intValue;
	    return result;
	}
#   endif
#endif

#undef Tcl_GetString
#undef Tcl_GetUnicode
#undef Tcl_CreateHashEntry
#define Tcl_GetString(objPtr) \
	Tcl_GetStringFromObj(objPtr, (Tcl_Size *)NULL)
#define Tcl_GetUnicode(objPtr) \
	Tcl_GetUnicodeFromObj(objPtr, (Tcl_Size *)NULL)
#undef Tcl_GetIndexFromObjStruct
#undef Tcl_GetBooleanFromObj
#undef Tcl_GetBoolean
#if !defined(TCLBOOLWARNING)
#if !defined(__cplusplus) && !defined(BUILD_tcl) && !defined(BUILD_tk) && defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
#	define TCLBOOLWARNING(boolPtr) (void)(sizeof(struct {_Static_assert(sizeof(*(boolPtr)) <= sizeof(int), "sizeof(boolPtr) too large");int dummy;})),
#elif defined(__GNUC__) && !defined(__STRICT_ANSI__)
	/* If this gives: "error: size of array ‘_bool_Var’ is negative", it means that sizeof(*boolPtr)>sizeof(int), which is not allowed */
#   define TCLBOOLWARNING(boolPtr) ({__attribute__((unused)) char _bool_Var[sizeof(*(boolPtr)) <= sizeof(int) ? 1 : -1];}),
#else
#   define TCLBOOLWARNING(boolPtr)
#endif
#endif /* !TCLBOOLWARNING */
#if defined(USE_TCL_STUBS)
#define Tcl_GetIndexFromObjStruct(interp, objPtr, tablePtr, offset, msg, flags, indexPtr) \
	(tclStubsPtr->tcl_GetIndexFromObjStruct((interp), (objPtr), (tablePtr), (offset), (msg), \
		(flags)|(int)(sizeof(*(indexPtr))<<1), (indexPtr)))
#define Tcl_GetBooleanFromObj(interp, objPtr, boolPtr) \
	((sizeof(*(boolPtr)) == sizeof(int) && (TCL_MAJOR_VERSION == 8)) ? tclStubsPtr->tcl_GetBooleanFromObj(interp, objPtr, (int *)(boolPtr)) : \
	((sizeof(*(boolPtr)) <= sizeof(int)) ? Tcl_GetBoolFromObj(interp, objPtr, (TCL_NULL_OK-2)&(int)sizeof((*(boolPtr))), (char *)(boolPtr)) : \
	(TCLBOOLWARNING(boolPtr)Tcl_Panic("sizeof(%s) must be <= sizeof(int)", & #boolPtr [1]),TCL_ERROR)))
#define Tcl_GetBoolean(interp, src, boolPtr) \
	((sizeof(*(boolPtr)) == sizeof(int) && (TCL_MAJOR_VERSION == 8)) ? tclStubsPtr->tcl_GetBoolean(interp, src, (int *)(boolPtr)) : \
	((sizeof(*(boolPtr)) <= sizeof(int)) ? Tcl_GetBool(interp, src, (TCL_NULL_OK-2)&(int)sizeof((*(boolPtr))), (char *)(boolPtr)) : \
	(TCLBOOLWARNING(boolPtr)Tcl_Panic("sizeof(%s) must be <= sizeof(int)", & #boolPtr [1]),TCL_ERROR)))
#else
#define Tcl_GetIndexFromObjStruct(interp, objPtr, tablePtr, offset, msg, flags, indexPtr) \
	((Tcl_GetIndexFromObjStruct)((interp), (objPtr), (tablePtr), (offset), (msg), \
		(flags)|(int)(sizeof(*(indexPtr))<<1), (indexPtr)))
#define Tcl_GetBooleanFromObj(interp, objPtr, boolPtr) \
	((sizeof(*(boolPtr)) == sizeof(int) && (TCL_MAJOR_VERSION == 8)) ? Tcl_GetBooleanFromObj(interp, objPtr, (int *)(boolPtr)) : \
	((sizeof(*(boolPtr)) <= sizeof(int)) ? Tcl_GetBoolFromObj(interp, objPtr, (TCL_NULL_OK-2)&(int)sizeof((*(boolPtr))), (char *)(boolPtr)) : \
	(TCLBOOLWARNING(boolPtr)Tcl_Panic("sizeof(%s) must be <= sizeof(int)", & #boolPtr [1]),TCL_ERROR)))
#define Tcl_GetBoolean(interp, src, boolPtr) \
	((sizeof(*(boolPtr)) == sizeof(int) && (TCL_MAJOR_VERSION == 8)) ? Tcl_GetBoolean(interp, src, (int *)(boolPtr)) : \
	((sizeof(*(boolPtr)) <= sizeof(int)) ? Tcl_GetBool(interp, src, (TCL_NULL_OK-2)&(int)sizeof((*(boolPtr))), (char *)(boolPtr)) : \
	(TCLBOOLWARNING(boolPtr)Tcl_Panic("sizeof(%s) must be <= sizeof(int)", & #boolPtr [1]),TCL_ERROR)))
#endif

#ifdef TCL_MEM_DEBUG
#   undef Tcl_Alloc
#   define Tcl_Alloc(x) \
    (Tcl_DbCkalloc((x), __FILE__, __LINE__))
#   undef Tcl_Free
#   define Tcl_Free(x) \
    Tcl_DbCkfree((x), __FILE__, __LINE__)
#   undef Tcl_Realloc
#   define Tcl_Realloc(x,y) \
    (Tcl_DbCkrealloc((x), (y), __FILE__, __LINE__))
#   undef Tcl_AttemptAlloc
#   define Tcl_AttemptAlloc(x) \
    (Tcl_AttemptDbCkalloc((x), __FILE__, __LINE__))
#   undef Tcl_AttemptRealloc
#   define Tcl_AttemptRealloc(x,y) \
    (Tcl_AttemptDbCkrealloc((x), (y), __FILE__, __LINE__))
#endif /* !TCL_MEM_DEBUG */

#define Tcl_NewLongObj(value) Tcl_NewWideIntObj((long)(value))
#define Tcl_NewIntObj(value) Tcl_NewWideIntObj((int)(value))
#define Tcl_DbNewLongObj(value, file, line) Tcl_DbNewWideIntObj((long)(value), file, line)
#define Tcl_SetIntObj(objPtr, value)	Tcl_SetWideIntObj((objPtr), (int)(value))
#define Tcl_SetLongObj(objPtr, value)	Tcl_SetWideIntObj((objPtr), (long)(value))
#define Tcl_BackgroundError(interp)	Tcl_BackgroundException((interp), TCL_ERROR)
#define Tcl_StringMatch(str, pattern) Tcl_StringCaseMatch((str), (pattern), 0)

#if TCL_UTF_MAX < 4
#   undef Tcl_UniCharToUtfDString
#   define Tcl_UniCharToUtfDString Tcl_Char16ToUtfDString
#   undef Tcl_UtfToUniCharDString
#   define Tcl_UtfToUniCharDString Tcl_UtfToChar16DString
#   undef Tcl_UtfToUniChar
#   define Tcl_UtfToUniChar Tcl_UtfToChar16
#   undef Tcl_UniCharLen
#   define Tcl_UniCharLen Tcl_Char16Len
#   undef Tcl_UniCharToUtf
#   if defined(USE_TCL_STUBS)
#	define Tcl_UniCharToUtf(c, p) \
		(tclStubsPtr->tcl_UniCharToUtf((c)|TCL_COMBINE, (p)))
#   else
#	define Tcl_UniCharToUtf(c, p) \
		((Tcl_UniCharToUtf)((c)|TCL_COMBINE, (p)))
#   endif
#   undef Tcl_NumUtfChars
#   define Tcl_NumUtfChars TclNumUtfChars
#   undef Tcl_GetCharLength
#   define Tcl_GetCharLength TclGetCharLength
#   undef Tcl_UtfAtIndex
#   define Tcl_UtfAtIndex TclUtfAtIndex
#   undef Tcl_GetRange
#   define Tcl_GetRange TclGetRange
#   undef Tcl_GetUniChar
#   define Tcl_GetUniChar TclGetUniChar
#   undef Tcl_UtfNcmp
#   define Tcl_UtfNcmp TclUtfNcmp
#   undef Tcl_UtfNcasecmp
#   define Tcl_UtfNcasecmp TclUtfNcasecmp
#endif
#if TCL_MAJOR_VERSION > 8
# if defined(USE_TCL_STUBS)
#   define Tcl_WCharToUtfDString (sizeof(wchar_t) != sizeof(short) \
		? (char *(*)(const wchar_t *, Tcl_Size, Tcl_DString *))tclStubsPtr->tcl_UniCharToUtfDString \
		: (char *(*)(const wchar_t *, Tcl_Size, Tcl_DString *))Tcl_Char16ToUtfDString)
#   define Tcl_UtfToWCharDString (sizeof(wchar_t) != sizeof(short) \
		? (wchar_t *(*)(const char *, Tcl_Size, Tcl_DString *))tclStubsPtr->tcl_UtfToUniCharDString \
		: (wchar_t *(*)(const char *, Tcl_Size, Tcl_DString *))Tcl_UtfToChar16DString)
#   define Tcl_UtfToWChar (sizeof(wchar_t) != sizeof(short) \
		? (Tcl_Size (*)(const char *, wchar_t *))tclStubsPtr->tcl_UtfToUniChar \
		: (Tcl_Size (*)(const char *, wchar_t *))Tcl_UtfToChar16)
#   define Tcl_WCharLen (sizeof(wchar_t) != sizeof(short) \
		? (Tcl_Size (*)(wchar_t *))tclStubsPtr->tcl_UniCharLen \
		: (Tcl_Size (*)(wchar_t *))Tcl_Char16Len)
# else
#   define Tcl_WCharToUtfDString (sizeof(wchar_t) != sizeof(short) \
		? (char *(*)(const wchar_t *, Tcl_Size, Tcl_DString *))Tcl_UniCharToUtfDString \
		: (char *(*)(const wchar_t *, Tcl_Size, Tcl_DString *))Tcl_Char16ToUtfDString)
#   define Tcl_UtfToWCharDString (sizeof(wchar_t) != sizeof(short) \
		? (wchar_t *(*)(const char *, Tcl_Size, Tcl_DString *))Tcl_UtfToUniCharDString \
		: (wchar_t *(*)(const char *, Tcl_Size, Tcl_DString *))Tcl_UtfToChar16DString)
#   define Tcl_UtfToWChar (sizeof(wchar_t) != sizeof(short) \
		? (Tcl_Size (*)(const char *, wchar_t *))Tcl_UtfToUniChar \
		: (Tcl_Size (*)(const char *, wchar_t *))Tcl_UtfToChar16)
#   define Tcl_WCharLen (sizeof(wchar_t) != sizeof(short) \
		? (Tcl_Size (*)(wchar_t *))Tcl_UniCharLen \
		: (Tcl_Size (*)(wchar_t *))Tcl_Char16Len)
# endif
#endif

/*
 * Deprecated Tcl procedures:
 */

#define Tcl_EvalObj(interp, objPtr) \
    Tcl_EvalObjEx(interp, objPtr, 0)
#define Tcl_GlobalEvalObj(interp, objPtr) \
    Tcl_EvalObjEx(interp, objPtr, TCL_EVAL_GLOBAL)

#if TCL_MAJOR_VERSION > 8
#   undef Tcl_Close
#   define Tcl_Close(interp, chan) Tcl_CloseEx(interp, chan, 0)
#endif

#undef TclUtfCharComplete
#undef TclUtfNext
#undef TclUtfPrev
#ifndef TCL_NO_DEPRECATED
#   define Tcl_CreateSlave Tcl_CreateChild
#   define Tcl_GetSlave Tcl_GetChild
#   define Tcl_GetMaster Tcl_GetParent
#endif

/* Protect those 11 functions, make them useless through the stub table */
#undef TclGetStringFromObj
#undef TclGetBytesFromObj
#undef TclGetUnicodeFromObj
#undef TclListObjGetElements
#undef TclListObjLength
#undef TclDictObjSize
#undef TclSplitList
#undef TclSplitPath
#undef TclFSSplitPath
#undef TclParseArgsObjv
#undef TclGetAliasObj

#if TCL_MAJOR_VERSION < 9
    /* TIP #627 */
#   undef Tcl_CreateObjCommand2
#   define Tcl_CreateObjCommand2 Tcl_CreateObjCommand
#   undef Tcl_CreateObjTrace2
#   define Tcl_CreateObjTrace2 Tcl_CreateObjTrace
#   undef Tcl_NRCreateCommand2
#   define Tcl_NRCreateCommand2 Tcl_NRCreateCommand
#   undef Tcl_NRCallObjProc2
#   define Tcl_NRCallObjProc2 Tcl_NRCallObjProc
    /* TIP #660 */
#   undef Tcl_GetSizeIntFromObj
#   define Tcl_GetSizeIntFromObj Tcl_GetIntFromObj

#   undef Tcl_GetBytesFromObj
#   define Tcl_GetBytesFromObj(interp, objPtr, sizePtr) \
	    tclStubsPtr->tclGetBytesFromObj((interp), (objPtr), (sizePtr))
#   undef Tcl_GetStringFromObj
#   define Tcl_GetStringFromObj(objPtr, sizePtr) \
	    tclStubsPtr->tclGetStringFromObj((objPtr), (sizePtr))
#   undef Tcl_GetUnicodeFromObj
#   define Tcl_GetUnicodeFromObj(objPtr, sizePtr) \
	    tclStubsPtr->tclGetUnicodeFromObj((objPtr), (sizePtr))
#   undef Tcl_ListObjGetElements
#   define Tcl_ListObjGetElements(interp, listPtr, objcPtr, objvPtr) \
	    tclStubsPtr->tclListObjGetElements((interp), (listPtr), (objcPtr), (objvPtr))
#   undef Tcl_ListObjLength
#   define Tcl_ListObjLength(interp, listPtr, lengthPtr) \
	    tclStubsPtr->tclListObjLength((interp), (listPtr), (lengthPtr))
#   undef Tcl_DictObjSize
#   define Tcl_DictObjSize(interp, dictPtr, sizePtr) \
	    tclStubsPtr->tclDictObjSize((interp), (dictPtr), (sizePtr))
#   undef Tcl_SplitList
#   define Tcl_SplitList(interp, listStr, argcPtr, argvPtr) \
	    tclStubsPtr->tclSplitList((interp), (listStr), (argcPtr), (argvPtr))
#   undef Tcl_SplitPath
#   define Tcl_SplitPath(path, argcPtr, argvPtr) \
	    tclStubsPtr->tclSplitPath((path), (argcPtr), (argvPtr))
#   undef Tcl_FSSplitPath
#   define Tcl_FSSplitPath(pathPtr, lenPtr) \
	    tclStubsPtr->tclFSSplitPath((pathPtr), (lenPtr))
#   undef Tcl_ParseArgsObjv
#   define Tcl_ParseArgsObjv(interp, argTable, objcPtr, objv, remObjv) \
	    tclStubsPtr->tclParseArgsObjv((interp), (argTable), (objcPtr), (objv), (remObjv))
#   undef Tcl_GetAliasObj
#   define Tcl_GetAliasObj(interp, childCmd, targetInterpPtr, targetCmdPtr, objcPtr, objv) \
	    tclStubsPtr->tclGetAliasObj((interp), (childCmd), (targetInterpPtr), (targetCmdPtr), (objcPtr), (objv))
#   undef Tcl_OpenTcpServerEx
#   undef TclZipfs_Mount
#   undef TclZipfs_Unmount
#   undef TclZipfs_TclLibrary
#   undef TclZipfs_MountBuffer
#   undef Tcl_FreeInternalRep
#   undef Tcl_InitStringRep
#   undef Tcl_FetchInternalRep
#   undef Tcl_StoreInternalRep
#   undef Tcl_HasStringRep
#   undef Tcl_LinkArray
#   undef Tcl_GetIntForIndex
#   undef Tcl_FSTildeExpand
#   undef Tcl_ExternalToUtfDStringEx
#   undef Tcl_UtfToExternalDStringEx
#   undef Tcl_AsyncMarkFromSignal
#   undef Tcl_GetBool
#   undef Tcl_GetBoolFromObj
#   undef Tcl_GetNumberFromObj
#   undef Tcl_GetNumber
#   undef Tcl_RemoveChannelMode
#   undef Tcl_GetEncodingNulLength
#   undef Tcl_GetWideUIntFromObj
#   undef Tcl_DStringToObj
#   undef Tcl_NewWideUIntObj
#   undef Tcl_SetWideUIntObj
#elif defined(TCL_8_API)
#   undef Tcl_GetByteArrayFromObj
#   undef Tcl_GetBytesFromObj
#   undef Tcl_GetStringFromObj
#   undef Tcl_GetUnicodeFromObj
#   undef Tcl_ListObjGetElements
#   undef Tcl_ListObjLength
#   undef Tcl_DictObjSize
#   undef Tcl_SplitList
#   undef Tcl_SplitPath
#   undef Tcl_FSSplitPath
#   undef Tcl_ParseArgsObjv
#   undef Tcl_GetAliasObj
#   if !defined(USE_TCL_STUBS)
#	define Tcl_GetByteArrayFromObj(objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \
		TclGetBytesFromObj(NULL, (objPtr), (sizePtr)) : \
		(Tcl_GetBytesFromObj)(NULL, (objPtr), (Tcl_Size *)(void *)(sizePtr)))
#	define Tcl_GetBytesFromObj(interp, objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \
		TclGetBytesFromObj((interp), (objPtr), (sizePtr)) : \
		(Tcl_GetBytesFromObj)((interp), (objPtr), (Tcl_Size *)(void *)(sizePtr)))
#	define Tcl_GetStringFromObj(objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \
		(TclGetStringFromObj)((objPtr), (sizePtr)) : \
		(Tcl_GetStringFromObj)((objPtr), (Tcl_Size *)(void *)(sizePtr)))
#	define Tcl_GetUnicodeFromObj(objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \
		TclGetUnicodeFromObj((objPtr), (sizePtr)) : \
		(Tcl_GetUnicodeFromObj)((objPtr), (Tcl_Size *)(void *)(sizePtr)))
#	define Tcl_ListObjGetElements(interp, listPtr, objcPtr, objvPtr) (sizeof(*(objcPtr)) <= sizeof(int) ? \
		(TclListObjGetElements)((interp), (listPtr), (objcPtr), (objvPtr)) : \
		(Tcl_ListObjGetElements)((interp), (listPtr), (Tcl_Size *)(void *)(objcPtr), (objvPtr)))
#	define Tcl_ListObjLength(interp, listPtr, lengthPtr) (sizeof(*(lengthPtr)) <= sizeof(int) ? \
		(TclListObjLength)((interp), (listPtr), (lengthPtr)) : \
		(Tcl_ListObjLength)((interp), (listPtr), (Tcl_Size *)(void *)(lengthPtr)))
#	define Tcl_DictObjSize(interp, dictPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \
		TclDictObjSize((interp), (dictPtr), (sizePtr)) : \
		(Tcl_DictObjSize)((interp), (dictPtr), (Tcl_Size *)(void *)(sizePtr)))
#	define Tcl_SplitList(interp, listStr, argcPtr, argvPtr) (sizeof(*(argcPtr)) <= sizeof(int) ? \
		TclSplitList((interp), (listStr), (argcPtr), (argvPtr)) : \
		(Tcl_SplitList)((interp), (listStr), (Tcl_Size *)(void *)(argcPtr), (argvPtr)))
#	define Tcl_SplitPath(path, argcPtr, argvPtr) (sizeof(*(argcPtr)) <= sizeof(int) ? \
		TclSplitPath((path), (argcPtr), (argvPtr)) : \
		(Tcl_SplitPath)((path), (Tcl_Size *)(void *)(argcPtr), (argvPtr)))
#	define Tcl_FSSplitPath(pathPtr, lenPtr) (sizeof(*(lenPtr)) <= sizeof(int) ? \
		TclFSSplitPath((pathPtr), (lenPtr)) : \
		(Tcl_FSSplitPath)((pathPtr), (Tcl_Size *)(void *)(lenPtr)))
#	define Tcl_ParseArgsObjv(interp, argTable, objcPtr, objv, remObjv) (sizeof(*(objcPtr)) <= sizeof(int) ? \
		TclParseArgsObjv((interp), (argTable), (objcPtr), (objv), (remObjv)) : \
		(Tcl_ParseArgsObjv)((interp), (argTable), (Tcl_Size *)(void *)(objcPtr), (objv), (remObjv)))
#	define Tcl_GetAliasObj(interp, childCmd, targetInterpPtr, targetCmdPtr, objcPtr, objv) (sizeof(*(objcPtr)) <= sizeof(int) ? \
		TclGetAliasObj((interp), (childCmd), (targetInterpPtr), (targetCmdPtr), (objcPtr), (objv)) : \
		(Tcl_GetAliasObj)((interp), (childCmd), (targetInterpPtr), (targetCmdPtr), (Tcl_Size *)(void *)(objcPtr), (objv)))
#   elif !defined(BUILD_tcl)
#	define Tcl_GetByteArrayFromObj(objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \
		tclStubsPtr->tclGetBytesFromObj(NULL, (objPtr), (sizePtr)) : \
		tclStubsPtr->tcl_GetBytesFromObj(NULL, (objPtr), (Tcl_Size *)(void *)(sizePtr)))
#	define Tcl_GetBytesFromObj(interp, objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \
		tclStubsPtr->tclGetBytesFromObj((interp), (objPtr), (sizePtr)) : \
		tclStubsPtr->tcl_GetBytesFromObj((interp), (objPtr), (Tcl_Size *)(void *)(sizePtr)))
#	define Tcl_GetStringFromObj(objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \
		tclStubsPtr->tclGetStringFromObj((objPtr), (sizePtr)) : \
		tclStubsPtr->tcl_GetStringFromObj((objPtr), (Tcl_Size *)(void *)(sizePtr)))
#	define Tcl_GetUnicodeFromObj(objPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \
		tclStubsPtr->tclGetUnicodeFromObj((objPtr), (sizePtr)) : \
		tclStubsPtr->tcl_GetUnicodeFromObj((objPtr), (Tcl_Size *)(void *)(sizePtr)))
#	define Tcl_ListObjGetElements(interp, listPtr, objcPtr, objvPtr) (sizeof(*(objcPtr)) <= sizeof(int) ? \
		tclStubsPtr->tclListObjGetElements((interp), (listPtr), (objcPtr), (objvPtr)) : \
		tclStubsPtr->tcl_ListObjGetElements((interp), (listPtr), (Tcl_Size *)(void *)(objcPtr), (objvPtr)))
#	define Tcl_ListObjLength(interp, listPtr, lengthPtr) (sizeof(*(lengthPtr)) <= sizeof(int) ? \
		tclStubsPtr->tclListObjLength((interp), (listPtr), (lengthPtr)) : \
		tclStubsPtr->tcl_ListObjLength((interp), (listPtr), (Tcl_Size *)(void *)(lengthPtr)))
#	define Tcl_DictObjSize(interp, dictPtr, sizePtr) (sizeof(*(sizePtr)) <= sizeof(int) ? \
		tclStubsPtr->tclDictObjSize((interp), (dictPtr), (sizePtr)) : \
		tclStubsPtr->tcl_DictObjSize((interp), (dictPtr), (Tcl_Size *)(void *)(sizePtr)))
#	define Tcl_SplitList(interp, listStr, argcPtr, argvPtr) (sizeof(*(argcPtr)) <= sizeof(int) ? \
		tclStubsPtr->tclSplitList((interp), (listStr), (argcPtr), (argvPtr)) : \
		tclStubsPtr->tcl_SplitList((interp), (listStr), (Tcl_Size *)(void *)(argcPtr), (argvPtr)))
#	define Tcl_SplitPath(path, argcPtr, argvPtr) (sizeof(*(argcPtr)) <= sizeof(int) ? \
		tclStubsPtr->tclSplitPath((path), (argcPtr), (argvPtr)) : \
		tclStubsPtr->tcl_SplitPath((path), (Tcl_Size *)(void *)(argcPtr), (argvPtr)))
#	define Tcl_FSSplitPath(pathPtr, lenPtr) (sizeof(*(lenPtr)) <= sizeof(int) ? \
		tclStubsPtr->tclFSSplitPath((pathPtr), (lenPtr)) : \
		tclStubsPtr->tcl_FSSplitPath((pathPtr), (Tcl_Size *)(void *)(lenPtr)))
#	define Tcl_ParseArgsObjv(interp, argTable, objcPtr, objv, remObjv) (sizeof(*(objcPtr)) <= sizeof(int) ? \
		tclStubsPtr->tclParseArgsObjv((interp), (argTable), (objcPtr), (objv), (remObjv)) : \
		tclStubsPtr->tcl_ParseArgsObjv((interp), (argTable), (Tcl_Size *)(void *)(objcPtr), (objv), (remObjv)))
#	define Tcl_GetAliasObj(interp, childCmd, targetInterpPtr, targetCmdPtr, objcPtr, objv) (sizeof(*(objcPtr)) <= sizeof(int) ? \
		tclStubsPtr->tclGetAliasObj((interp), (childCmd), (targetInterpPtr), (targetCmdPtr), (objcPtr), (objv)) : \
		tclStubsPtr->tcl_GetAliasObj((interp), (childCmd), (targetInterpPtr), (targetCmdPtr), (Tcl_Size *)(void *)(objcPtr), (objv)))
#   endif /* defined(USE_TCL_STUBS) */
#else /* !defined(TCL_8_API) */
#   undef Tcl_GetByteArrayFromObj
#   define Tcl_GetByteArrayFromObj(objPtr, sizePtr) \
	   Tcl_GetBytesFromObj(NULL, (objPtr), (sizePtr))
#endif /* defined(TCL_8_API) */

#endif /* _TCLDECLS */
Changes to extsrc/cson_amalgamation.c.
11
12
13
14
15
16
17
18
19
20
21
22





23
24

25
26
27

28
29
30
31
32
33
34
11
12
13
14
15
16
17





18
19
20
21
22
23

24
25
26

27
28
29
30
31
32
33
34







-
-
-
-
-
+
+
+
+
+

-
+


-
+









#include <stddef.h>

/* Windows DLL stuff */
#ifdef JSON_PARSER_DLL
#   ifdef _MSC_VER
#	    ifdef JSON_PARSER_DLL_EXPORTS
#		    define JSON_PARSER_DLL_API __declspec(dllexport)
#	    else
#		    define JSON_PARSER_DLL_API __declspec(dllimport)
#       endif
#     ifdef JSON_PARSER_DLL_EXPORTS
#       define JSON_PARSER_DLL_API __declspec(dllexport)
#     else
#       define JSON_PARSER_DLL_API __declspec(dllimport)
#     endif
#   else
#	    define JSON_PARSER_DLL_API 
#     define JSON_PARSER_DLL_API 
#   endif
#else
#	define JSON_PARSER_DLL_API 
#   define JSON_PARSER_DLL_API 
#endif

/* Determine the integer type use to parse non-floating point numbers */
#ifdef _WIN32
typedef __int64 JSON_int_t;
#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%I64d"
#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%I64d"
Added extsrc/linenoise-win32.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
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
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
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

/* this code is not standalone
 * it is included into linenoise.c
 * for windows.
 * It is deliberately kept separate so that
 * applications that have no need for windows
 * support can omit this
 */
static DWORD orig_consolemode = 0;

static int flushOutput(struct current *current);
static void outputNewline(struct current *current);

static void refreshStart(struct current *current)
{
    (void)current;
}

static void refreshEnd(struct current *current)
{
    (void)current;
}

static void refreshStartChars(struct current *current)
{
    assert(current->output == NULL);
    /* We accumulate all output here */
    current->output = sb_alloc();
#ifdef USE_UTF8
    current->ubuflen = 0;
#endif
}

static void refreshNewline(struct current *current)
{
    DRL("<nl>");
    outputNewline(current);
}

static void refreshEndChars(struct current *current)
{
    assert(current->output);
    flushOutput(current);
    sb_free(current->output);
    current->output = NULL;
}

static int enableRawMode(struct current *current) {
    DWORD n;
    INPUT_RECORD irec;

    current->outh = GetStdHandle(STD_OUTPUT_HANDLE);
    current->inh = GetStdHandle(STD_INPUT_HANDLE);

    if (!PeekConsoleInput(current->inh, &irec, 1, &n)) {
        return -1;
    }
    if (getWindowSize(current) != 0) {
        return -1;
    }
    if (GetConsoleMode(current->inh, &orig_consolemode)) {
        SetConsoleMode(current->inh, ENABLE_PROCESSED_INPUT);
    }
#ifdef USE_UTF8
    /* XXX is this the right thing to do? */
    SetConsoleCP(65001);
#endif
    return 0;
}

static void disableRawMode(struct current *current)
{
    SetConsoleMode(current->inh, orig_consolemode);
}

void linenoiseClearScreen(void)
{
    /* XXX: This is ugly. Should just have the caller pass a handle */
    struct current current;

    current.outh = GetStdHandle(STD_OUTPUT_HANDLE);

    if (getWindowSize(&current) == 0) {
        COORD topleft = { 0, 0 };
        DWORD n;

        FillConsoleOutputCharacter(current.outh, ' ',
            current.cols * current.rows, topleft, &n);
        FillConsoleOutputAttribute(current.outh,
            FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN,
            current.cols * current.rows, topleft, &n);
        SetConsoleCursorPosition(current.outh, topleft);
    }
}

static void cursorToLeft(struct current *current)
{
    COORD pos;
    DWORD n;

    pos.X = 0;
    pos.Y = (SHORT)current->y;

    FillConsoleOutputAttribute(current->outh,
        FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, current->cols, pos, &n);
    current->x = 0;
}

#ifdef USE_UTF8
static void flush_ubuf(struct current *current)
{
    COORD pos;
    DWORD nwritten;
    pos.Y = (SHORT)current->y;
    pos.X = (SHORT)current->x;
    SetConsoleCursorPosition(current->outh, pos);
    WriteConsoleW(current->outh, current->ubuf, current->ubuflen, &nwritten, 0);
    current->x += current->ubufcols;
    current->ubuflen = 0;
    current->ubufcols = 0;
}

static void add_ubuf(struct current *current, int ch)
{
    /* This code originally by: Author: Mark E. Davis, 1994. */
    static const int halfShift  = 10; /* used for shifting by 10 bits */

    static const DWORD halfBase = 0x0010000UL;
    static const DWORD halfMask = 0x3FFUL;

    #define UNI_SUR_HIGH_START  0xD800
    #define UNI_SUR_HIGH_END    0xDBFF
    #define UNI_SUR_LOW_START   0xDC00
    #define UNI_SUR_LOW_END     0xDFFF

    #define UNI_MAX_BMP 0x0000FFFF

    if (ch > UNI_MAX_BMP) {
        /* convert from unicode to utf16 surrogate pairs
         * There is always space for one extra word in ubuf
         */
        ch -= halfBase;
        current->ubuf[current->ubuflen++] = (WORD)((ch >> halfShift) + UNI_SUR_HIGH_START);
        current->ubuf[current->ubuflen++] = (WORD)((ch & halfMask) + UNI_SUR_LOW_START);
    }
    else {
        current->ubuf[current->ubuflen++] = ch;
    }
    current->ubufcols += utf8_width(ch);
    if (current->ubuflen >= UBUF_MAX_CHARS) {
        flush_ubuf(current);
    }
}
#endif

static int flushOutput(struct current *current)
{
    const char *pt = sb_str(current->output);
    int len = sb_len(current->output);

#ifdef USE_UTF8
    /* convert utf8 in current->output into utf16 in current->ubuf
     */
    while (len) {
        int ch;
        int n = utf8_tounicode(pt, &ch);

        pt += n;
        len -= n;

        add_ubuf(current, ch);
    }
    flush_ubuf(current);
#else
    DWORD nwritten;
    COORD pos;

    pos.Y = (SHORT)current->y;
    pos.X = (SHORT)current->x;

    SetConsoleCursorPosition(current->outh, pos);
    WriteConsoleA(current->outh, pt, len, &nwritten, 0);

    current->x += len;
#endif

    sb_clear(current->output);

    return 0;
}

static int outputChars(struct current *current, const char *buf, int len)
{
    if (len < 0) {
        len = strlen(buf);
    }
    assert(current->output);

    sb_append_len(current->output, buf, len);

    return 0;
}

static void outputNewline(struct current *current)
{
    /* On the last row output a newline to force a scroll */
    if (current->y + 1 == current->rows) {
        outputChars(current, "\n", 1);
    }
    flushOutput(current);
    current->x = 0;
    current->y++;
}

static void setOutputHighlight(struct current *current, const int *props, int nprops)
{
    int colour = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN;
    int bold = 0;
    int reverse = 0;
    int i;

    for (i = 0; i < nprops; i++) {
        switch (props[i]) {
            case 0:
               colour = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN;
               bold = 0;
               reverse = 0;
               break;
            case 1:
               bold = FOREGROUND_INTENSITY;
               break;
            case 7:
               reverse = 1;
               break;
            case 30:
               colour = 0;
               break;
            case 31:
               colour = FOREGROUND_RED;
               break;
            case 32:
               colour = FOREGROUND_GREEN;
               break;
            case 33:
               colour = FOREGROUND_RED | FOREGROUND_GREEN;
               break;
            case 34:
               colour = FOREGROUND_BLUE;
               break;
            case 35:
               colour = FOREGROUND_RED | FOREGROUND_BLUE;
               break;
            case 36:
               colour = FOREGROUND_BLUE | FOREGROUND_GREEN;
               break;
            case 37:
               colour = FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN;
               break;
        }
    }

    flushOutput(current);

    if (reverse) {
        SetConsoleTextAttribute(current->outh, BACKGROUND_INTENSITY);
    }
    else {
        SetConsoleTextAttribute(current->outh, colour | bold);
    }
}

static void eraseEol(struct current *current)
{
    COORD pos;
    DWORD n;

    pos.X = (SHORT) current->x;
    pos.Y = (SHORT) current->y;

    FillConsoleOutputCharacter(current->outh, ' ', current->cols - current->x, pos, &n);
}

static void setCursorXY(struct current *current)
{
    COORD pos;

    pos.X = (SHORT) current->x;
    pos.Y = (SHORT) current->y;

    SetConsoleCursorPosition(current->outh, pos);
}


static void setCursorPos(struct current *current, int x)
{
    current->x = x;
    setCursorXY(current);
}

static void cursorUp(struct current *current, int n)
{
    current->y -= n;
    setCursorXY(current);
}

static void cursorDown(struct current *current, int n)
{
    current->y += n;
    setCursorXY(current);
}

static int fd_read(struct current *current)
{
    while (1) {
        INPUT_RECORD irec;
        DWORD n;
        if (WaitForSingleObject(current->inh, INFINITE) != WAIT_OBJECT_0) {
            break;
        }
        if (!ReadConsoleInputW(current->inh, &irec, 1, &n)) {
            break;
        }
        if (irec.EventType == KEY_EVENT) {
            KEY_EVENT_RECORD *k = &irec.Event.KeyEvent;
            if (k->bKeyDown || k->wVirtualKeyCode == VK_MENU) {
                if (k->dwControlKeyState & ENHANCED_KEY) {
                    switch (k->wVirtualKeyCode) {
                     case VK_LEFT:
                        return SPECIAL_LEFT;
                     case VK_RIGHT:
                        return SPECIAL_RIGHT;
                     case VK_UP:
                        return SPECIAL_UP;
                     case VK_DOWN:
                        return SPECIAL_DOWN;
                     case VK_INSERT:
                        return SPECIAL_INSERT;
                     case VK_DELETE:
                        return SPECIAL_DELETE;
                     case VK_HOME:
                        return SPECIAL_HOME;
                     case VK_END:
                        return SPECIAL_END;
                     case VK_PRIOR:
                        return SPECIAL_PAGE_UP;
                     case VK_NEXT:
                        return SPECIAL_PAGE_DOWN;
                     case VK_RETURN:
                        return k->uChar.UnicodeChar;
                    }
                }
                /* Note that control characters are already translated in AsciiChar */
                else if (k->wVirtualKeyCode == VK_CONTROL)
                    continue;
                else {
                    return k->uChar.UnicodeChar;
                }
            }
        }
    }
    return -1;
}

static int getWindowSize(struct current *current)
{
    CONSOLE_SCREEN_BUFFER_INFO info;
    if (!GetConsoleScreenBufferInfo(current->outh, &info)) {
        return -1;
    }
    current->cols = info.dwSize.X;
    current->rows = info.dwSize.Y;
    if (current->cols <= 0 || current->rows <= 0) {
        current->cols = 80;
        return -1;
    }
    current->y = info.dwCursorPosition.Y;
    current->x = info.dwCursorPosition.X;
    return 0;
}
Changes to extsrc/linenoise.c.
890
891
892
893
894
895
896

897
898
899
900
901
902
903
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904







+







    int colsright; /* refreshLine() cached cols for insert_char() optimisation */
    int colsleft;  /* refreshLine() cached cols for remove_char() optimisation */
    const char *prompt;
    stringbuf *capture; /* capture buffer, or NULL for none. Always null terminated */
    stringbuf *output;  /* used only during refreshLine() - output accumulator */
#if defined(USE_TERMIOS)
    int fd;     /* Terminal fd */
    int pending; /* pending char fd_read_char() */
#elif defined(USE_WINCONSOLE)
    HANDLE outh; /* Console output handle */
    HANDLE inh; /* Console input handle */
    int rows;   /* Screen rows */
    int x;      /* Current column during output */
    int y;      /* Current row */
#ifdef USE_UTF8
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
1148
1149
1150
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
1148
1149
1150
1151
1152
1153
1154







-
-
+
+
+
+
+








-
+






-
+







    /* local modes - choing off, canonical off, no extended functions,
     * no signal chars (^Z,^C) */
    raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
    /* control chars - set return condition: min number of bytes and timer.
     * We want read to return every single byte, without timeout. */
    raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */

    /* put terminal in raw mode after flushing */
    if (tcsetattr(current->fd,TCSADRAIN,&raw) < 0) {
    /* put terminal in raw mode. Because we aren't changing any output
     * settings we don't need to use TCSADRAIN and I have seen that hang on
     * OpenBSD when running under a pty
     */
    if (tcsetattr(current->fd,TCSANOW,&raw) < 0) {
        goto fatal;
    }
    rawmode = 1;
    return 0;
}

static void disableRawMode(struct current *current) {
    /* Don't even check the return value as it's too late. */
    if (rawmode && tcsetattr(current->fd,TCSADRAIN,&orig_termios) != -1)
    if (rawmode && tcsetattr(current->fd,TCSANOW,&orig_termios) != -1)
        rawmode = 0;
}

/* At exit we'll try to fix the terminal to the initial conditions. */
static void linenoiseAtExit(void) {
    if (rawmode) {
        tcsetattr(STDIN_FILENO, TCSADRAIN, &orig_termios);
        tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
    }
    linenoiseHistoryFree();
}

/* gcc/glibc insists that we care about the return code of write!
 * Clarification: This means that a void-cast like "(void) (EXPR)"
 * does not work.
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
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270




1271

1272
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


1320
1321
1322
1323
1324

1325
1326
1327
1328
1329
1330
1331
1232
1233
1234
1235
1236
1237
1238

1239
1240
1241
1242
1243
1244

1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255

1256
1257
1258
1259
1260
1261
1262

1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
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
1320
1321
1322
1323
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







-
+





-
+




+
+
+
+
+
+
-
+






-
+

















+
+
+
+
-
+















-
+












+
+
+
+
+
+
+
+
+









-
+










+
+





+








void linenoiseClearScreen(void)
{
    IGNORE_RC(write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7));
}

/**
 * Reads a char from 'fd', waiting at most 'timeout' milliseconds.
 * Reads a char from 'current->fd', waiting at most 'timeout' milliseconds.
 *
 * A timeout of -1 means to wait forever.
 *
 * Returns -1 if no char is received within the time or an error occurs.
 */
static int fd_read_char(int fd, int timeout)
static int fd_read_char(struct current *current, int timeout)
{
    struct pollfd p;
    unsigned char c;

    if (current->pending) {
        c = current->pending;
        current->pending = 0;
        return c;
    }

    p.fd = fd;
    p.fd = current->fd;
    p.events = POLLIN;

    if (poll(&p, 1, timeout) == 0) {
        /* timeout */
        return -1;
    }
    if (read(fd, &c, 1) != 1) {
    if (read(current->fd, &c, 1) != 1) {
        return -1;
    }
    return c;
}

/**
 * Reads a complete utf-8 character
 * and returns the unicode value, or -1 on error.
 */
static int fd_read(struct current *current)
{
#ifdef USE_UTF8
    char buf[MAX_UTF8_LEN];
    int n;
    int i;
    int c;

    if (current->pending) {
        buf[0] = current->pending;
        current->pending = 0;
    }
    if (read(current->fd, &buf[0], 1) != 1) {
    else if (read(current->fd, &buf[0], 1) != 1) {
        return -1;
    }
    n = utf8_charlen(buf[0]);
    if (n < 1) {
        return -1;
    }
    for (i = 1; i < n; i++) {
        if (read(current->fd, &buf[i], 1) != 1) {
            return -1;
        }
    }
    /* decode and return the character */
    utf8_tounicode(buf, &c);
    return c;
#else
    return fd_read_char(current->fd, -1);
    return fd_read_char(current, -1);
#endif
}


/**
 * Stores the current cursor column in '*cols'.
 * Returns 1 if OK, or 0 if failed to determine cursor pos.
 */
static int queryCursor(struct current *current, int* cols)
{
    struct esc_parser parser;
    int ch;
    /* Unfortunately we don't have any persistent state, so assume
     * a process will only ever interact with one terminal at a time.
     */
    static int query_cursor_failed;

    if (query_cursor_failed) {
        /* If it ever fails, don't try again */
        return 0;
    }

    /* Should not be buffering this output, it needs to go immediately */
    assert(current->output == NULL);

    /* control sequence - report cursor location */
    outputChars(current, "\x1b[6n", -1);

    /* Parse the response: ESC [ rows ; cols R */
    initParseEscapeSeq(&parser, 'R');
    while ((ch = fd_read_char(current->fd, 100)) > 0) {
    while ((ch = fd_read_char(current, 100)) > 0) {
        switch (parseEscapeSequence(&parser, ch)) {
            default:
                continue;
            case EP_END:
                if (parser.numprops == 2 && parser.props[1] < 1000) {
                    *cols = parser.props[1];
                    return 1;
                }
                break;
            case EP_ERROR:
                /* Push back the character that caused the error */
                current->pending = ch;
                break;
        }
        /* failed */
        break;
    }
    query_cursor_failed = 1;
    return 0;
}

/**
 * Updates current->cols with the current window size (width)
 */
static int getWindowSize(struct current *current)
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
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







-
+

-
+










-
+







 * chars to determine if this is a known special key.
 *
 * Returns SPECIAL_NONE if unrecognised, or -1 if EOF.
 *
 * If no additional char is received within a short time,
 * CHAR_ESCAPE is returned.
 */
static int check_special(int fd)
static int check_special(struct current *current)
{
    int c = fd_read_char(fd, 50);
    int c = fd_read_char(current, 50);
    int c2;

    if (c < 0) {
        return CHAR_ESCAPE;
    }
    else if (c >= 'a' && c <= 'z') {
        /* esc-a => meta-a */
        return meta(c);
    }

    c2 = fd_read_char(fd, 50);
    c2 = fd_read_char(current, 50);
    if (c2 < 0) {
        return c2;
    }
    if (c == '[' || c == 'O') {
        /* Potential arrow key */
        switch (c2) {
            case 'A':
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
1446

1447
1448
1449
1450
1451
1452
1453
1446
1447
1448
1449
1450
1451
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







-
+


















-
+







                return SPECIAL_END;
            case 'H':
                return SPECIAL_HOME;
        }
    }
    if (c == '[' && c2 >= '1' && c2 <= '8') {
        /* extended escape */
        c = fd_read_char(fd, 50);
        c = fd_read_char(current, 50);
        if (c == '~') {
            switch (c2) {
                case '2':
                    return SPECIAL_INSERT;
                case '3':
                    return SPECIAL_DELETE;
                case '5':
                    return SPECIAL_PAGE_UP;
                case '6':
                    return SPECIAL_PAGE_DOWN;
                case '7':
                    return SPECIAL_HOME;
                case '8':
                    return SPECIAL_END;
            }
        }
        while (c != -1 && c != '~') {
            /* .e.g \e[12~ or '\e[11;2~   discard the complete sequence */
            c = fd_read_char(fd, 50);
            c = fd_read_char(current, 50);
        }
    }

    return SPECIAL_NONE;
}
#endif

2232
2233
2234
2235
2236
2237
2238
2239

2240
2241
2242
2243
2244
2245
2246
2258
2259
2260
2261
2262
2263
2264

2265
2266
2267
2268
2269
2270
2271
2272







-
+







                rbuf[p_ind] = 0;
                rlen = strlen(rbuf);
            }
            continue;
        }
#ifdef USE_TERMIOS
        if (c == CHAR_ESCAPE) {
            c = check_special(current->fd);
            c = check_special(current);
        }
#endif
        if (c == ctrl('R')) {
            /* Search for the previous (earlier) match */
            if (searchpos > 0) {
                searchpos--;
            }
2344
2345
2346
2347
2348
2349
2350
2351

2352
2353
2354
2355
2356
2357
2358
2370
2371
2372
2373
2374
2375
2376

2377
2378
2379
2380
2381
2382
2383
2384







-
+







            /* reverse incremental search will provide an alternative keycode or 0 for none */
            c = reverseIncrementalSearch(current);
            /* go on to process the returned char normally */
        }

#ifdef USE_TERMIOS
        if (c == CHAR_ESCAPE) {   /* escape sequence */
            c = check_special(current->fd);
            c = check_special(current);
        }
#endif
        if (c == -1) {
            /* Return on errors */
            return sb_len(current->buf);
        }

Changes to extsrc/pikchr-worker.js.
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
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







-
+













-
+









-
+








  {
    pikchr: source code for the pikchr,
    darkMode: boolean true to adjust colors for a dark color scheme,
    cssClass: CSS class name to add to the SVG
  }

  Workers-to-Main types
  Workers-to-Main message types:

  - stdout, stderr: indicate stdout/stderr output from the wasm
  layer. The data property is the string of the output, noting
  that the emscripten binding emits these one line at a time. Thus,
  if a C-side puts() emits multiple lines in a single call, the JS
  side will see that as multiple calls. Example:

  {type:'stdout', data: 'Hi, world.'}

  - module: Status text. This is intended to alert the main thread
  about module loading status so that, e.g., the main thread can
  update a progress widget and DTRT when the module is finished
  loading and available for work. Status messages come in the form
  

  {type:'module', data:{
  type:'status',
  data: {text:string|null, step:1-based-integer}
  }

  with an incrementing step value for each subsequent message. When
  the module loading is complete, a message with a text value of
  null is posted.

  - pikchr: 
  - pikchr:

  {type: 'pikchr',
    data:{
      pikchr: input text,
      result: rendered result (SVG on success, HTML on error),
      isError: bool, true if .pikchr holds an error report,
      flags: integer: flags used to configure the pikchr rendering,
160
161
162
163
164
165
166
167

168
169
170
171
172
173
174
160
161
162
163
164
165
166

167
168
169
170
171
172
173
174







-
+







            pikchrModule.stackRestore(stack);
            wMsg('working','end');
          }
          return;
    };
    console.warn("Unknown pikchr-worker message type:",ev);
  };
  

  /**
     emscripten module for use with build mode -sMODULARIZE.
  */
  const pikchrModule = {
    print: function(){wMsg('stdout', Array.prototype.slice.call(arguments));},
    printErr: stderr,
    /**
204
205
206
207
208
209
210
211

212
213
214
215
216
217
218

219

220
221
204
205
206
207
208
209
210

211
212
213
214
215
216
217
218
219

220
221
222







-
+







+
-
+


      wMsg('module',{
        type:'status',
        data:{step: ++f.last.step, text: text||null}
      });
    }
  };

  importScripts('pikchr.js');
  importScripts('pikchr-v2813665466.js');
  /**
     initPikchrModule() is installed via pikchr.js due to
     building with:

     emcc ... -sMODULARIZE=1 -sEXPORT_NAME=initPikchrModule
  */
  initPikchrModule(pikchrModule).then(function(thisModule){
    //globalThis.M = pikchrModule; console.warn("pikchrModule=globalThis.M=",globalThis.M);
    wMsg('pikchr-ready');
    wMsg('pikchr-ready', pikchrModule.ccall('pikchr_version','string'));
  });
})();
Changes to extsrc/pikchr.c.
1
2
3







































4
5
6
7
8
9
10
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



+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







/* This file is automatically generated by Lemon from input grammar
** source file "pikchr.y".
*/
/*
** 2000-05-29
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** Driver template for the LEMON parser generator.
**
** The "lemon" program processes an LALR(1) input grammar file, then uses
** this template to construct a parser.  The "lemon" program inserts text
** at each "%%" line.  Also, any "P-a-r-s-e" identifier prefix (without the
** interstitial "-" characters) contained in this template is changed into
** the value of the %name directive from the grammar.  Otherwise, the content
** of this template is copied straight through into the generate parser
** source file.
**
** The following is the concatenation of all %include directives from the
** input grammar file:
*/
/************ Begin %include sections from the grammar ************************/
#line 1 "VERSION.h"
#define MANIFEST_UUID "8a43b020141f772a0ac45291a7fd73041d2efba5e3665c6bd2f334ad9b2e9845"
#define MANIFEST_VERSION "[8a43b02014]"
#define MANIFEST_DATE "2025-03-19 16:19:43"
#define MANIFEST_YEAR "2025"
#define MANIFEST_ISODATE "20250319161943"
#define MANIFEST_NUMERIC_DATE 20250319
#define MANIFEST_NUMERIC_TIME 161943
#define RELEASE_VERSION "1.0"
#define RELEASE_VERSION_NUMBER 10000
#define RELEASE_RESOURCE_VERSION 1,0,0,0
#define COMPILER "gcc-13.3.0"
#line 2 "pikchr.y"

/*
** Zero-Clause BSD license:
**
** Copyright (C) 2020-09-01 by D. Richard Hipp <drh@sqlite.org>
**
** Permission to use, copy, modify, and/or distribute this software for
** any purpose with or without fee is hereby granted.
123
124
125
126
127
128
129












130
131
132
133
134
135
136
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







+
+
+
+
+
+
+
+
+
+
+
+







#include <ctype.h>
#include <math.h>
#include <assert.h>
#define count(X) (sizeof(X)/sizeof(X[0]))
#ifndef M_PI
# define M_PI 3.1415926535897932385
#endif

/*
** Typesafe version of ctype.h macros.  Cygwin requires this, I'm told.
*/
#define IsUpper(X)  isupper((unsigned char)(X))
#define IsLower(X)  islower((unsigned char)(X))
#define ToLower(X)  tolower((unsigned char)(X))
#define IsDigit(X)  isdigit((unsigned char)(X))
#define IsXDigit(X) isxdigit((unsigned char)(X))
#define IsSpace(X)  isspace((unsigned char)(X))
#define IsAlnum(X)  isalnum((unsigned char)(X))


/* Limit the number of tokens in a single script to avoid run-away
** macro expansion attacks.  See forum post
**    https://pikchr.org/home/forumpost/ef8684c6955a411a
*/
#ifndef PIKCHR_TOKEN_LIMIT
# define PIKCHR_TOKEN_LIMIT 100000
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
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







-
+

















-
+







static int pik_bbox_contains_point(PBox*,PPoint*);
static void pik_bbox_init(PBox*);
static void pik_bbox_addbox(PBox*,PBox*);
static void pik_bbox_add_xy(PBox*,PNum,PNum);
static void pik_bbox_addellipse(PBox*,PNum x,PNum y,PNum rx,PNum ry);
static void pik_add_txt(Pik*,PToken*,int);
static int pik_text_length(const PToken *pToken, const int isMonospace);
static void pik_size_to_fit(Pik*,PToken*,int);
static void pik_size_to_fit(Pik*,PObj*,PToken*,int);
static int pik_text_position(int,PToken*);
static PNum pik_property_of(PObj*,PToken*);
static PNum pik_func(Pik*,PToken*,PNum,PNum);
static PPoint pik_position_between(PNum x, PPoint p1, PPoint p2);
static PPoint pik_position_at_angle(PNum dist, PNum r, PPoint pt);
static PPoint pik_position_at_hdg(PNum dist, PToken *pD, PPoint pt);
static void pik_same(Pik *p, PObj*, PToken*);
static PPoint pik_nth_vertex(Pik *p, PToken *pNth, PToken *pErr, PObj *pObj);
static PToken pik_next_semantic_token(PToken *pThis);
static void pik_compute_layout_settings(Pik*);
static void pik_behind(Pik*,PObj*);
static PObj *pik_assert(Pik*,PNum,PToken*,PNum);
static PObj *pik_position_assert(Pik*,PPoint*,PToken*,PPoint*);
static PNum pik_dist(PPoint*,PPoint*);
static void pik_add_macro(Pik*,PToken *pId,PToken *pCode);


#line 523 "pikchr.c"
#line 549 "pikchr.c"
/**************** End of %include directives **********************************/
/* These constants specify the various numeric values for terminal symbols.
***************** Begin token definitions *************************************/
#ifndef T_ID
#define T_ID                              1
#define T_EDGEPT                          2
#define T_OF                              3
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
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







+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







#define T_CODEBLOCK                      19
#define T_FILL                           20
#define T_COLOR                          21
#define T_THICKNESS                      22
#define T_PRINT                          23
#define T_STRING                         24
#define T_COMMA                          25
#define T_ISODATE                        26
#define T_CLASSNAME                      26
#define T_LB                             27
#define T_RB                             28
#define T_UP                             29
#define T_DOWN                           30
#define T_LEFT                           31
#define T_RIGHT                          32
#define T_CLOSE                          33
#define T_CHOP                           34
#define T_FROM                           35
#define T_TO                             36
#define T_THEN                           37
#define T_HEADING                        38
#define T_GO                             39
#define T_AT                             40
#define T_WITH                           41
#define T_SAME                           42
#define T_AS                             43
#define T_FIT                            44
#define T_BEHIND                         45
#define T_UNTIL                          46
#define T_EVEN                           47
#define T_DOT_E                          48
#define T_HEIGHT                         49
#define T_WIDTH                          50
#define T_RADIUS                         51
#define T_DIAMETER                       52
#define T_DOTTED                         53
#define T_DASHED                         54
#define T_CW                             55
#define T_CCW                            56
#define T_LARROW                         57
#define T_RARROW                         58
#define T_LRARROW                        59
#define T_INVIS                          60
#define T_THICK                          61
#define T_THIN                           62
#define T_SOLID                          63
#define T_CENTER                         64
#define T_LJUST                          65
#define T_RJUST                          66
#define T_ABOVE                          67
#define T_BELOW                          68
#define T_ITALIC                         69
#define T_BOLD                           70
#define T_MONO                           71
#define T_ALIGNED                        72
#define T_BIG                            73
#define T_SMALL                          74
#define T_AND                            75
#define T_LT                             76
#define T_GT                             77
#define T_ON                             78
#define T_WAY                            79
#define T_BETWEEN                        80
#define T_THE                            81
#define T_NTH                            82
#define T_VERTEX                         83
#define T_TOP                            84
#define T_BOTTOM                         85
#define T_START                          86
#define T_END                            87
#define T_IN                             88
#define T_THIS                           89
#define T_DOT_U                          90
#define T_LAST                           91
#define T_NUMBER                         92
#define T_FUNC1                          93
#define T_FUNC2                          94
#define T_DIST                           95
#define T_DOT_XY                         96
#define T_X                              97
#define T_Y                              98
#define T_DOT_L                          99
#define T_CLASSNAME                      27
#define T_LB                             28
#define T_RB                             29
#define T_UP                             30
#define T_DOWN                           31
#define T_LEFT                           32
#define T_RIGHT                          33
#define T_CLOSE                          34
#define T_CHOP                           35
#define T_FROM                           36
#define T_TO                             37
#define T_THEN                           38
#define T_HEADING                        39
#define T_GO                             40
#define T_AT                             41
#define T_WITH                           42
#define T_SAME                           43
#define T_AS                             44
#define T_FIT                            45
#define T_BEHIND                         46
#define T_UNTIL                          47
#define T_EVEN                           48
#define T_DOT_E                          49
#define T_HEIGHT                         50
#define T_WIDTH                          51
#define T_RADIUS                         52
#define T_DIAMETER                       53
#define T_DOTTED                         54
#define T_DASHED                         55
#define T_CW                             56
#define T_CCW                            57
#define T_LARROW                         58
#define T_RARROW                         59
#define T_LRARROW                        60
#define T_INVIS                          61
#define T_THICK                          62
#define T_THIN                           63
#define T_SOLID                          64
#define T_CENTER                         65
#define T_LJUST                          66
#define T_RJUST                          67
#define T_ABOVE                          68
#define T_BELOW                          69
#define T_ITALIC                         70
#define T_BOLD                           71
#define T_MONO                           72
#define T_ALIGNED                        73
#define T_BIG                            74
#define T_SMALL                          75
#define T_AND                            76
#define T_LT                             77
#define T_GT                             78
#define T_ON                             79
#define T_WAY                            80
#define T_BETWEEN                        81
#define T_THE                            82
#define T_NTH                            83
#define T_VERTEX                         84
#define T_TOP                            85
#define T_BOTTOM                         86
#define T_START                          87
#define T_END                            88
#define T_IN                             89
#define T_THIS                           90
#define T_DOT_U                          91
#define T_LAST                           92
#define T_NUMBER                         93
#define T_FUNC1                          94
#define T_FUNC2                          95
#define T_DIST                           96
#define T_DOT_XY                         97
#define T_X                              98
#define T_Y                              99
#define T_DOT_L                          100
#endif
/**************** End token definitions ***************************************/

/* The next sections is a series of control #defines.
** various aspects of the generated parser.
**    YYCODETYPE         is the data type used to store the integer codes
**                       that represent terminal and non-terminal symbols.
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
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







-
+





-
+
-
-
-
-
+
+
+
+
+
-







**    YY_MAX_DSTRCTR     Maximum symbol value that has a destructor
*/
#ifndef INTERFACE
# define INTERFACE 1
#endif
/************* Begin control #defines *****************************************/
#define YYCODETYPE unsigned char
#define YYNOCODE 136
#define YYNOCODE 138
#define YYACTIONTYPE unsigned short int
#define pik_parserTOKENTYPE PToken
typedef union {
  int yyinit;
  pik_parserTOKENTYPE yy0;
  PNum yy21;
  PList* yy23;
  PPoint yy63;
  PRel yy72;
  PObj* yy162;
  short int yy188;
  PRel yy28;
  PObj* yy54;
  PNum yy129;
  PPoint yy187;
  short int yy272;
  PList* yy235;
} YYMINORTYPE;
#ifndef YYSTACKDEPTH
#define YYSTACKDEPTH 100
#endif
#define pik_parserARG_SDECL
#define pik_parserARG_PDECL
#define pik_parserARG_PARAM
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
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







-
+








-
-
+
+







#define pik_parserCTX_PARAM ,p
#define pik_parserCTX_FETCH Pik *p=yypParser->p;
#define pik_parserCTX_STORE yypParser->p=p;
#define YYFALLBACK 1
#define YYNSTATE             164
#define YYNRULE              156
#define YYNRULE_WITH_ACTION  116
#define YYNTOKEN             100
#define YYNTOKEN             101
#define YY_MAX_SHIFT         163
#define YY_MIN_SHIFTREDUCE   287
#define YY_MAX_SHIFTREDUCE   442
#define YY_ERROR_ACTION      443
#define YY_ACCEPT_ACTION     444
#define YY_NO_ACTION         445
#define YY_MIN_REDUCE        446
#define YY_MAX_REDUCE        601
#define YY_MIN_DSTRCTR       100
#define YY_MAX_DSTRCTR       103
#define YY_MIN_DSTRCTR       101
#define YY_MAX_DSTRCTR       104
/************* End control #defines *******************************************/
#define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])))

/* Define the yytestcase() macro to be a no-op if is not already defined
** otherwise.
**
** Applications can choose to define yytestcase() in the %include section
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
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
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




















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
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
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
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
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
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
1142


1143
1144
1145









1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161







-
+


-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+


-
-
-
-
-
+
+
+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-



-
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
-
+
+

-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+







**  yy_shift_ofst[]    For each state, the offset into yy_action for
**                     shifting terminals.
**  yy_reduce_ofst[]   For each state, the offset into yy_action for
**                     shifting non-terminals after a reduce.
**  yy_default[]       Default action for each state.
**
*********** Begin parsing tables **********************************************/
#define YY_ACTTAB_COUNT (1313)
#define YY_ACTTAB_COUNT (1305)
static const YYACTIONTYPE yy_action[] = {
 /*     0 */   575,  495,  161,  119,   25,  452,   29,   74,  129,  148,
 /*    10 */   575,  492,  161,  119,  453,  113,  120,  161,  119,  530,
 /*    20 */   427,  428,  339,  559,   81,   30,  560,  561,  575,   64,
 /*    30 */    63,   62,   61,  322,  323,    9,    8,   33,  149,   32,
 /*    40 */     7,   71,  127,   38,  335,   66,   48,   37,   28,  339,
 /*    50 */   339,  339,  339,  425,  426,  340,  341,  342,  343,  344,
 /*    60 */   345,  346,  347,  348,  474,  528,  161,  119,  577,   77,
 /*    70 */   577,   73,  306,  148,  474,  533,  161,  119,  112,  113,
 /*    80 */   120,  161,  119,  128,  427,  428,  339,   31,   81,  531,
 /*    90 */   161,  119,  474,   35,  330,  378,  158,  322,  323,    9,
 /*   100 */     8,   33,  149,   32,    7,   71,  127,  328,  335,   66,
 /*   110 */   579,  378,  158,  339,  339,  339,  339,  425,  426,  340,
 /*   120 */   341,  342,  343,  344,  345,  346,  347,  348,  394,  435,
 /*   130 */    46,   59,   60,   64,   63,   62,   61,  357,   36,  376,
 /*   140 */    54,   51,    2,   47,  403,   13,  297,  411,  412,  413,
 /*   150 */   414,   80,  162,  308,   79,  133,  310,  126,  441,  440,
 /*   160 */   118,  123,   83,  404,  405,  406,  408,   80,   84,  308,
 /*   170 */    79,  299,  411,  412,  413,  414,  118,   69,  350,  350,
 /*   180 */   350,  350,  350,  350,  350,  350,  350,  350,  350,   62,
 /*    10 */   575,   64,   63,   62,   61,  453,  113,  120,  161,  119,
 /*    20 */   427,  428,  339,  357,   81,  121,  447,  454,   29,  575,
 /*    30 */   530,   13,   50,  450,  322,  323,    9,    8,   33,  149,
 /*    40 */    32,    7,   71,  127,  163,  335,   66,   28,  444,   27,
 /*    50 */   339,  339,  339,  339,  425,  426,  340,  341,  342,  343,
 /*    60 */   344,  345,  346,  347,  348,  474,   64,   63,   62,   61,
 /*    70 */    54,   51,   73,  306,  148,  474,  492,  161,  119,  297,
 /*    80 */   112,  113,  120,  161,  119,  427,  428,  339,   30,   81,
 /*    90 */   109,  447,  454,   29,  474,  528,  161,  119,  450,  322,
 /*   100 */   323,    9,    8,   33,  149,   32,    7,   71,  127,  163,
 /*   110 */   335,   66,  535,   36,   27,  339,  339,  339,  339,  425,
 /*   120 */   426,  340,  341,  342,  343,  344,  345,  346,  347,  348,
 /*   130 */   394,  435,  310,   59,   60,   64,   63,   62,   61,  313,
 /*   140 */    74,  376,  148,   69,    2,  533,  161,  119,  124,  113,
 /*   150 */   120,  161,  119,   80,  535,   31,  308,   79,   83,  107,
 /*   160 */   535,  441,  440,  535,  394,  435,  299,   59,   60,  120,
 /*   170 */   161,  119,  149,  463,  376,  376,  330,   84,    2,  122,
 /*   180 */    78,   78,   38,  156,  156,  156,   48,   37,  559,  328,
 /*   190 */   128,  152,  560,  561,  434,  441,  440,  350,  350,  350,
 /*   200 */   350,  350,  350,  350,  350,  350,  350,  350,  577,   77,
 /*   190 */    61,  434,   64,   63,   62,   61,  313,  398,  399,  427,
 /*   200 */   428,  339,  380,  157,   64,   63,   62,   61,  122,  106,
 /*   210 */   535,  436,  437,  438,  439,  298,  375,  391,  117,  393,
 /*   220 */   155,  154,  153,  394,  435,   49,   59,   60,  339,  339,
 /*   230 */   339,  339,  425,  426,  376,    3,    4,    2,   64,   63,
 /*   240 */    62,   61,  156,  156,  156,  394,  379,  159,   59,   60,
 /*   250 */    76,   67,  535,  441,  440,    5,  102,    6,  535,   42,
 /*   260 */   131,  535,   69,  107,  301,  302,  303,  394,  305,   15,
 /*   270 */    59,   60,  120,  161,  119,  446,  463,  424,  376,  423,
 /*   280 */     1,   42,  397,   78,   78,   36,  434,   11,  394,  435,
 /*   290 */   356,   59,   60,   12,  152,  139,  432,   14,   16,  376,
 /*   300 */    18,   65,    2,  138,  106,  430,  436,  437,  438,  439,
 /*   310 */    44,  375,   19,  117,  393,  155,  154,  153,  441,  440,
 /*   320 */   142,  140,   64,   63,   62,   61,  106,   20,   68,  376,
 /*   210 */   577,   35,  106,   46,  436,  437,  438,  439,  579,  375,
 /*   220 */   298,  117,  393,  155,  154,  153,   47,    4,  434,   69,
 /*   230 */   394,  435,    3,   59,   60,  411,  412,  413,  414,  398,
 /*   240 */   399,  376,   62,   61,    2,  108,  106,    5,  436,  437,
 /*   250 */   438,  439,  375,  375,  117,  117,  393,  155,  154,  153,
 /*   260 */    76,  441,  440,   67,    6,  142,  140,   64,   63,   62,
 /*   270 */    61,  380,  157,  424,  427,  428,  339,  379,  159,   45,
 /*   280 */   423,   72,  131,  148,  531,  161,  119,    1,   55,  125,
 /*   290 */   113,  120,  161,  119,  434,  147,  146,   64,   63,   62,
 /*   300 */    61,  397,   43,   11,  339,  339,  339,  339,  425,  426,
 /*   310 */   355,   65,  106,  149,  436,  437,  438,  439,   74,  375,
 /*   320 */   148,  117,  393,  155,  154,  153,  497,  113,  120,  161,
 /*   330 */   119,   22,   21,   12,  142,  140,   64,   63,   62,   61,
 /*   330 */   359,  107,   23,  375,   45,  117,  393,  155,  154,  153,
 /*   340 */   120,  161,  119,   55,  463,  114,   26,   57,  106,  147,
 /*   350 */   146,  434,  569,   58,  392,  375,   43,  117,  393,  155,
 /*   360 */   154,  153,  152,  384,   64,   63,   62,   61,  382,  106,
 /*   370 */   383,  436,  437,  438,  439,  377,  375,   70,  117,  393,
 /*   380 */   155,  154,  153,  160,   39,   22,   21,  445,  142,  140,
 /*   390 */    64,   63,   62,   61,   24,   17,  145,  141,  431,  108,
 /*   400 */   445,  445,  445,  391,  445,  445,  375,  445,  117,  445,
 /*   410 */   445,   55,   74,  445,  148,  445,  445,  147,  146,  124,
 /*   420 */   113,  120,  161,  119,   43,  445,  445,  142,  140,   64,
 /*   430 */    63,   62,   61,  445,  394,  445,  445,   59,   60,   64,
 /*   440 */    63,   62,   61,  149,  445,  376,  445,  445,   42,  445,
 /*   450 */    55,  445,  391,   22,   21,  445,  147,  146,  445,  445,
 /*   460 */    52,  445,   24,   43,  145,  141,  431,  394,  445,  445,
 /*   470 */    59,   60,   64,   63,   62,   61,  445,  445,  376,  132,
 /*   480 */   130,   42,  445,  445,  445,  355,  156,  156,  156,  445,
 /*   490 */   445,  445,   22,   21,  445,  394,  473,  445,   59,   60,
 /*   500 */   445,   24,  445,  145,  141,  431,  376,  445,  107,   42,
 /*   510 */    64,   63,   62,   61,  445,  106,  445,  120,  161,  119,
 /*   520 */   445,  478,  375,  354,  117,  393,  155,  154,  153,  445,
 /*   530 */   394,  143,  473,   59,   60,   64,   63,   62,   61,  152,
 /*   540 */   445,  376,  445,  445,   42,  445,  445,  445,  106,   64,
 /*   550 */    63,   62,   61,  445,  445,  375,   50,  117,  393,  155,
 /*   560 */   154,  153,  445,  394,  144,  445,   59,   60,  445,  445,
 /*   570 */    53,   72,  445,  148,  376,  445,  106,   42,  125,  113,
 /*   580 */   120,  161,  119,  375,  445,  117,  393,  155,  154,  153,
 /*   590 */   394,  445,  445,   59,   60,  445,  445,  445,  445,  445,
 /*   600 */   445,  102,  149,  445,   42,  445,   74,  445,  148,  445,
 /*   610 */   445,  106,  445,  497,  113,  120,  161,  119,  375,  445,
 /*   620 */   117,  393,  155,  154,  153,  394,  445,  445,   59,   60,
 /*   630 */   445,  445,   88,  445,  445,  445,  376,  149,  445,   40,
 /*   640 */   445,  120,  161,  119,  106,  445,  445,  435,  110,  110,
 /*   340 */    24,  356,  145,  141,  431,   64,   63,   62,   61,  391,
 /*   350 */   149,  448,  454,   29,  378,  158,   85,   55,  450,  394,
 /*   360 */   432,  138,   59,   60,  147,  146,  120,  161,  119,  163,
 /*   370 */   102,   43,  139,   42,   27,  430,   14,   15,  301,  302,
 /*   380 */   303,  446,  305,   16,   44,   74,   18,  148,  152,   19,
 /*   390 */    20,   36,   68,  496,  113,  120,  161,  119,  114,  359,
 /*   400 */    22,   21,   23,  142,  140,   64,   63,   62,   61,   24,
 /*   410 */   107,  145,  141,  431,   26,   57,  377,  149,   58,  118,
 /*   420 */   120,  161,  119,  392,  463,  384,   55,   64,   63,   62,
 /*   430 */    61,  382,  569,  147,  146,  160,  383,  435,   39,   70,
 /*   440 */    43,  106,  152,  445,  445,   88,  445,  445,  375,  445,
 /*   450 */   117,  393,  155,  154,  153,  120,  161,  119,  445,   17,
 /*   460 */   445,   10,  479,  479,  445,  445,  435,  441,  440,   22,
 /*   470 */    21,  445,  403,   64,   63,   62,   61,  152,   24,  445,
 /*   480 */   145,  141,  431,  133,   75,  126,  354,  445,  445,  123,
 /*   490 */   445,  404,  405,  406,  408,   80,  441,  440,  308,   79,
 /*   500 */   434,  411,  412,  413,  414,  394,  445,  445,   59,   60,
 /*   510 */    64,   63,   62,   61,  445,  445,  376,  445,  445,   42,
 /*   520 */   436,  437,  438,  439,  156,  156,  156,  394,  445,  434,
 /*   530 */    59,   60,   64,   63,   62,   61,  445,  445,  376,  445,
 /*   540 */   445,   42,  445,  394,  473,  391,   59,   60,  445,  436,
 /*   550 */   437,  438,  439,   49,  376,  445,   74,   42,  148,  445,
 /*   560 */    88,  445,  445,  445,  490,  113,  120,  161,  119,  445,
 /*   570 */   120,  161,  119,  132,  130,  394,  143,  475,   59,   60,
 /*   580 */   445,  473,   64,   63,   62,   61,  376,  106,  149,   42,
 /*   590 */   445,  445,  152,  445,  375,  391,  117,  393,  155,  154,
 /*   600 */   153,  394,  144,   52,   59,   60,  445,  445,  445,  106,
 /*   610 */   445,  445,  376,  445,  445,   42,  375,  445,  117,  393,
 /*   620 */   155,  154,  153,  445,  445,  106,   64,   63,   62,   61,
 /*   630 */   445,  445,  375,  445,  117,  393,  155,  154,  153,  394,
 /*   640 */   445,  445,   59,   60,   88,  445,  445,   53,  445,  445,
 /*   650 */   376,  445,  445,   42,  120,  161,  119,  106,  445,  445,
 /*   650 */   445,  375,  445,  117,  393,  155,  154,  153,  394,  445,
 /*   660 */   445,   59,   60,  152,   85,  445,  445,  445,  445,  376,
 /*   670 */   445,  106,   41,  120,  161,  119,  441,  440,  375,  445,
 /*   680 */   117,  393,  155,  154,  153,  448,  454,   29,  445,  445,
 /*   690 */    74,  450,  148,   75,   88,  152,  445,  496,  113,  120,
 /*   700 */   161,  119,  163,  120,  161,  119,  106,   27,  445,  434,
 /*   710 */   111,  111,  445,  375,  445,  117,  393,  155,  154,  153,
 /*   720 */   445,  149,  445,  445,  445,  152,   74,  445,  148,  436,
 /*   730 */   437,  438,  439,  490,  113,  120,  161,  119,  445,  106,
 /*   740 */   121,  447,  454,   29,  445,  445,  375,  450,  117,  393,
 /*   750 */   155,  154,  153,  445,  445,  445,  445,  149,  163,   74,
 /*   760 */   445,  148,  444,   27,  445,  445,  484,  113,  120,  161,
 /*   770 */   119,  445,  445,  445,   74,  445,  148,  445,  445,  445,
 /*   780 */   445,  483,  113,  120,  161,  119,   74,  445,  148,   86,
 /*   790 */   149,  445,  445,  480,  113,  120,  161,  119,  120,  161,
 /*   800 */   119,  445,   74,  445,  148,  149,  445,  445,  445,  134,
 /*   810 */   113,  120,  161,  119,   74,  445,  148,  149,  445,  445,
 /*   820 */   152,  517,  113,  120,  161,  119,   88,   64,   63,   62,
 /*   830 */    61,  445,  445,  149,  445,  120,  161,  119,  445,   74,
 /*   840 */   396,  148,  475,  445,  445,  149,  137,  113,  120,  161,
 /*   850 */   119,   74,  445,  148,  445,  445,  445,  152,  525,  113,
 /*   660 */   445,  110,  110,  445,  375,  445,  117,  393,  155,  154,
 /*   670 */   153,  394,  445,  445,   59,   60,  152,  107,  445,  445,
 /*   680 */   445,  445,  102,  106,  445,   42,  445,  120,  161,  119,
 /*   690 */   375,  451,  117,  393,  155,  154,  153,  394,  445,  445,
 /*   700 */    59,   60,   64,   63,   62,   61,  445,  445,  376,  152,
 /*   710 */   445,   40,  445,  394,  445,  396,   59,   60,  445,  445,
 /*   720 */   445,  106,  445,  445,  376,   88,  445,   41,  375,  445,
 /*   730 */   117,  393,  155,  154,  153,  120,  161,  119,   74,  445,
 /*   740 */   148,  445,  111,  111,  107,  445,  484,  113,  120,  161,
 /*   750 */   119,  445,  445,  106,  120,  161,  119,  152,  478,  445,
 /*   760 */   375,   86,  117,  393,  155,  154,  153,  445,  445,  445,
 /*   770 */   149,  120,  161,  119,  445,  445,  152,  445,  445,  106,
 /*   780 */   445,   64,   63,   62,   61,  445,  375,  445,  117,  393,
 /*   790 */   155,  154,  153,  152,  395,  106,   64,   63,   62,   61,
 /*   800 */    98,  445,  375,  445,  117,  393,  155,  154,  153,  445,
 /*   810 */   120,  161,  119,  445,   74,  445,  148,   56,  445,   74,
 /*   820 */   445,  148,  483,  113,  120,  161,  119,  480,  113,  120,
 /*   830 */   161,  119,  152,   74,  445,  148,  445,   89,  445,  445,
 /*   840 */   445,  134,  113,  120,  161,  119,  149,  120,  161,  119,
 /*   850 */   445,  149,   74,  445,  148,  445,  445,  445,  378,  158,
 /*   860 */   120,  161,  119,  445,   74,  445,  148,  445,  445,  445,
 /*   870 */   149,  527,  113,  120,  161,  119,  445,  445,  445,   74,
 /*   860 */   517,  113,  120,  161,  119,  149,   74,  445,  148,  152,
 /*   880 */   445,  148,  149,  445,  445,  445,  524,  113,  120,  161,
 /*   890 */   119,   74,  445,  148,   98,  149,  445,  445,  526,  113,
 /*   900 */   120,  161,  119,  120,  161,  119,  445,   74,  445,  148,
 /*   910 */   149,  445,  445,  445,  523,  113,  120,  161,  119,   74,
 /*   870 */   445,   74,  445,  148,  137,  113,  120,  161,  119,  525,
 /*   880 */   113,  120,  161,  119,  149,   74,  445,  148,   64,   63,
 /*   890 */    62,   61,  445,  527,  113,  120,  161,  119,  149,  445,
 /*   920 */   445,  148,  149,  445,  445,  152,  522,  113,  120,  161,
 /*   930 */   119,   89,   64,   63,   62,   61,  445,  445,  149,  445,
 /*   940 */   120,  161,  119,  445,   74,  395,  148,  445,  445,  445,
 /*   950 */   149,  521,  113,  120,  161,  119,   74,  445,  148,  445,
 /*   960 */   445,  445,  152,  520,  113,  120,  161,  119,  445,   74,
 /*   900 */   445,  391,  445,  149,  445,  445,  445,  445,  445,  445,
 /*   910 */    74,  445,  148,  445,  445,  162,  445,  149,  524,  113,
 /*   920 */   120,  161,  119,  118,  445,   74,  445,  148,  445,  445,
 /*   930 */   445,  445,  445,  526,  113,  120,  161,  119,  445,   74,
 /*   970 */   445,  148,  445,  445,  445,  149,  519,  113,  120,  161,
 /*   980 */   119,  445,  445,  445,   74,  445,  148,  149,  445,  445,
 /*   990 */   445,  150,  113,  120,  161,  119,   74,  445,  148,   90,
 /*  1000 */   149,  445,  445,  151,  113,  120,  161,  119,  120,  161,
 /*  1010 */   119,  445,   74,  445,  148,  149,  445,  435,  445,  136,
 /*  1020 */   113,  120,  161,  119,   74,  445,  148,  149,  445,  445,
 /*  1030 */   152,  135,  113,  120,  161,  119,   64,   63,   62,   61,
 /*  1040 */   445,  445,  445,  149,  445,  445,  441,  440,  445,   88,
 /*  1050 */   445,  445,  445,  445,  445,  149,  445,   56,  120,  161,
 /*  1060 */   119,   88,  445,  445,   10,  479,  479,  445,  445,  445,
 /*  1070 */   120,  161,  119,  445,  445,  445,  445,   82,  445,  434,
 /*  1080 */   152,  445,  445,  445,  466,  445,   34,  109,  447,  454,
 /*  1090 */    29,  445,  152,  445,  450,  445,  445,  445,  107,  436,
 /*   940 */   445,  148,  149,  445,  445,  445,  445,  523,  113,  120,
 /*   950 */   161,  119,   74,  445,  148,  445,  445,  149,  445,  445,
 /*   960 */   522,  113,  120,  161,  119,  445,   74,  445,  148,  445,
 /*   970 */   445,  149,  445,  445,  521,  113,  120,  161,  119,   74,
 /*   980 */   445,  148,  445,  445,  149,  445,  445,  520,  113,  120,
 /*   990 */   161,  119,  445,   74,  445,  148,  445,  445,  149,  445,
 /*  1000 */   445,  519,  113,  120,  161,  119,  445,  445,  445,  445,
 /*  1010 */   445,  149,  445,  445,  445,  445,  445,  445,   74,  445,
 /*  1020 */   148,  445,  445,  445,  445,  149,  150,  113,  120,  161,
 /*  1030 */   119,   74,  445,  148,  445,  445,  445,  445,  445,  151,
 /*  1040 */   113,  120,  161,  119,  445,   74,  445,  148,  445,  445,
 /*  1050 */   149,  445,  445,  136,  113,  120,  161,  119,   74,  445,
 /*  1060 */   148,  445,  445,  149,  445,  445,  135,  113,  120,  161,
 /*  1100 */   437,  438,  439,   87,  445,  163,  445,  120,  161,  119,
 /*  1110 */    27,  451,  120,  161,  119,   99,  445,   64,   63,   62,
 /*  1120 */    61,  445,  100,  445,  120,  161,  119,  101,  445,  152,
 /*  1130 */   391,  120,  161,  119,  152,  445,  120,  161,  119,   91,
 /*  1140 */   445,  445,  445,  445,  445,  445,  152,  445,  120,  161,
 /*  1150 */   119,  103,  445,  152,   92,  445,  445,  445,  152,  445,
 /*  1160 */   120,  161,  119,  120,  161,  119,   93,  445,  445,  104,
 /*  1170 */   152,  445,  445,  445,  445,  120,  161,  119,  120,  161,
 /*  1180 */   119,  445,  152,  445,   94,  152,  445,  445,  445,  445,
 /*  1190 */   445,  445,  105,  120,  161,  119,  445,  152,  445,   95,
 /*  1200 */   152,  120,  161,  119,  445,  445,  445,   96,  120,  161,
 /*  1210 */   119,  445,  445,  445,  445,  152,  120,  161,  119,  445,
 /*  1220 */   445,  445,  445,  152,  445,  445,  445,  445,  445,  445,
 /*  1230 */   152,   97,  445,  445,  549,  445,  445,  548,  152,  445,
 /*  1240 */   120,  161,  119,  120,  161,  119,  120,  161,  119,  445,
 /*  1250 */   445,  445,  445,  445,  445,  445,  445,  445,  445,  445,
 /*  1260 */   445,  445,  152,  547,  445,  152,  546,  445,  152,  115,
 /*  1070 */   119,  445,   88,  445,  445,  445,  445,  149,  445,  445,
 /*  1080 */   445,   90,  120,  161,  119,  445,  445,  445,  445,   82,
 /*  1090 */   149,  120,  161,  119,  445,   87,  466,  445,   34,   99,
 /*  1100 */   445,  445,  445,  445,  152,  120,  161,  119,  100,  120,
 /*  1110 */   161,  119,  445,  152,  445,  445,  445,  445,  120,  161,
 /*  1120 */   119,  445,  445,  445,  101,  445,  445,  152,  445,  445,
 /*  1130 */   445,  152,   91,  445,  120,  161,  119,  103,  445,  445,
 /*  1140 */   152,  445,  120,  161,  119,  445,  445,  120,  161,  119,
 /*  1150 */   445,   92,  445,  445,  445,  445,  152,  445,  445,  445,
 /*  1160 */    93,  120,  161,  119,  152,  445,  104,  445,  445,  152,
 /*  1170 */   120,  161,  119,  445,   94,  445,  120,  161,  119,  445,
 /*  1180 */   445,  445,  445,  152,  120,  161,  119,  445,  445,  105,
 /*  1190 */   445,  445,  152,  445,  445,  445,  445,   95,  152,  120,
 /*  1200 */   161,  119,   96,  445,  445,   97,  152,  120,  161,  119,
 /*  1210 */   445,  445,  120,  161,  119,  120,  161,  119,  445,  445,
 /*  1220 */   445,  152,  445,  445,  445,  445,  445,  445,  445,  152,
 /*  1230 */   549,  445,  445,  548,  152,  445,  445,  152,  547,  445,
 /*  1240 */   120,  161,  119,  120,  161,  119,  546,  445,  120,  161,
 /*  1250 */   119,  445,  445,  445,  445,  445,  120,  161,  119,  445,
 /*  1260 */   445,  445,  152,  445,  445,  152,  445,  445,  445,  115,
 /*  1270 */   445,  445,  120,  161,  119,  120,  161,  119,  120,  161,
 /*  1280 */   119,  116,  445,  445,  445,  445,  445,  445,  445,  445,
 /*  1290 */   120,  161,  119,  445,  152,  445,  445,  152,  445,  445,
 /*  1300 */   152,  445,  445,  445,  445,  445,  445,  445,  445,  445,
 /*  1310 */   445,  445,  152,
 /*  1270 */   152,  445,  116,  445,  445,  445,  445,  445,  152,  120,
 /*  1280 */   161,  119,  120,  161,  119,  445,  445,  445,  445,  445,
 /*  1290 */   445,  445,  445,  445,  445,  445,  445,  445,  445,  445,
 /*  1300 */   445,  152,  445,  445,  152,
};
static const YYCODETYPE yy_lookahead[] = {
 /*     0 */     0,  113,  114,  115,  134,  102,  103,  104,  106,  106,
 /*    10 */    10,  113,  114,  115,  111,  112,  113,  114,  115,  106,
 /*    20 */    20,   21,   22,  105,   24,  126,  108,  109,   28,    4,
 /*    30 */     5,    6,    7,   33,   34,   35,   36,   37,  135,   39,
 /*    40 */    40,   41,   42,  105,   44,   45,  108,  109,  107,   49,
 /*     0 */     0,  115,  116,  117,  136,  103,  104,  105,  107,  107,
 /*    10 */    10,    4,    5,    6,    7,  113,  114,  115,  116,  117,
 /*    20 */    20,   21,   22,   17,   24,  101,  102,  103,  104,   29,
 /*    30 */   107,   25,   25,  109,   34,   35,   36,   37,   38,  137,
 /*    40 */    40,   41,   42,   43,  120,   45,   46,  109,  124,  125,
 /*    50 */    50,   51,   52,   53,   54,   55,   56,   57,   58,   59,
 /*    60 */    60,   61,   62,   63,    0,  113,  114,  115,  130,  131,
 /*    70 */   132,  104,   25,  106,   10,  113,  114,  115,  111,  112,
 /*    80 */   113,  114,  115,  106,   20,   21,   22,  128,   24,  113,
 /*    90 */   114,  115,   28,  129,    2,   26,   27,   33,   34,   35,
 /*   100 */    36,   37,  135,   39,   40,   41,   42,    2,   44,   45,
 /*   110 */   133,   26,   27,   49,   50,   51,   52,   53,   54,   55,
 /*   120 */    56,   57,   58,   59,   60,   61,   62,   63,    1,    2,
 /*   130 */    38,    4,    5,    4,    5,    6,    7,   17,   10,   12,
 /*   140 */     4,    5,   15,   38,    1,   25,   17,   29,   30,   31,
 /*   150 */    32,   24,   83,   26,   27,   12,   28,   14,   31,   32,
 /*   160 */    91,   18,  116,   20,   21,   22,   23,   24,  116,   26,
 /*   170 */    27,   19,   29,   30,   31,   32,   91,    3,   64,   65,
 /*   180 */    66,   67,   68,   69,   70,   71,   72,   73,   74,    6,
 /*   190 */     7,   64,    4,    5,    6,    7,    8,   97,   98,   20,
 /*   200 */    21,   22,   26,   27,    4,    5,    6,    7,    1,   82,
 /*   210 */    48,   84,   85,   86,   87,   17,   89,   17,   91,   92,
 /*   220 */    93,   94,   95,    1,    2,   25,    4,    5,   49,   50,
 /*   230 */    51,   52,   53,   54,   12,   16,   15,   15,    4,    5,
 /*   240 */     6,    7,   20,   21,   22,    1,   26,   27,    4,    5,
 /*   250 */    48,   43,   90,   31,   32,   40,   12,   40,   96,   15,
 /*   260 */    47,   99,   88,  104,   20,   21,   22,    1,   24,   35,
 /*   270 */     4,    5,  113,  114,  115,    0,  117,   41,   12,   41,
 /*   280 */    13,   15,   17,  124,  125,   10,   64,   25,    1,    2,
 /*   290 */    17,    4,    5,   75,  135,   81,   80,    3,    3,   12,
 /*   300 */     3,   99,   15,   79,   82,   80,   84,   85,   86,   87,
 /*   310 */    38,   89,    3,   91,   92,   93,   94,   95,   31,   32,
 /*   320 */     2,    3,    4,    5,    6,    7,   82,    3,    3,   12,
 /*    60 */    60,   61,   62,   63,   64,    0,    4,    5,    6,    7,
 /*    70 */     4,    5,  105,   25,  107,   10,  115,  116,  117,   17,
 /*    80 */   113,  114,  115,  116,  117,   20,   21,   22,  128,   24,
 /*    90 */   101,  102,  103,  104,   29,  115,  116,  117,  109,   34,
 /*   100 */    35,   36,   37,   38,  137,   40,   41,   42,   43,  120,
 /*   110 */    45,   46,   49,   10,  125,   50,   51,   52,   53,   54,
 /*   120 */    55,   56,   57,   58,   59,   60,   61,   62,   63,   64,
 /*   130 */     1,    2,   29,    4,    5,    4,    5,    6,    7,    8,
 /*   140 */   105,   12,  107,    3,   15,  115,  116,  117,  113,  114,
 /*   150 */   115,  116,  117,   24,   91,  130,   27,   28,  118,  105,
 /*   160 */    97,   32,   33,  100,    1,    2,   19,    4,    5,  115,
 /*   170 */   116,  117,  137,  119,   12,   12,    2,  118,   15,    1,
 /*   180 */   126,  127,  106,   20,   21,   22,  110,  111,  106,    2,
 /*   190 */   107,  137,  110,  111,   65,   32,   33,   65,   66,   67,
 /*   200 */    68,   69,   70,   71,   72,   73,   74,   75,  132,  133,
 /*   210 */   134,  131,   83,   39,   85,   86,   87,   88,  135,   90,
 /*   220 */    17,   92,   93,   94,   95,   96,   39,   15,   65,   89,
 /*   230 */     1,    2,   16,    4,    5,   30,   31,   32,   33,   98,
 /*   240 */    99,   12,    6,    7,   15,   83,   83,   41,   85,   86,
 /*   250 */    87,   88,   90,   90,   92,   92,   93,   94,   95,   96,
 /*   260 */    49,   32,   33,   44,   41,    2,    3,    4,    5,    6,
 /*   270 */     7,   27,   28,   42,   20,   21,   22,   27,   28,   16,
 /*   280 */    42,  105,   48,  107,  115,  116,  117,   13,   25,  113,
 /*   290 */   114,  115,  116,  117,   65,   32,   33,    4,    5,    6,
 /*   300 */     7,   17,   39,   25,   50,   51,   52,   53,   54,   55,
 /*   310 */    17,  100,   83,  137,   85,   86,   87,   88,  105,   90,
 /*   320 */   107,   92,   93,   94,   95,   96,  113,  114,  115,  116,
 /*   330 */   117,   68,   69,   76,    2,    3,    4,    5,    6,    7,
 /*   330 */    77,  104,   25,   89,   16,   91,   92,   93,   94,   95,
 /*   340 */   113,  114,  115,   25,  117,   96,   15,   15,   82,   31,
 /*   350 */    32,   64,  125,   15,   17,   89,   38,   91,   92,   93,
 /*   360 */    94,   95,  135,   28,    4,    5,    6,    7,   28,   82,
 /*   370 */    28,   84,   85,   86,   87,   12,   89,    3,   91,   92,
 /*   380 */    93,   94,   95,   90,   11,   67,   68,  136,    2,    3,
 /*   390 */     4,    5,    6,    7,   76,   35,   78,   79,   80,   82,
 /*   340 */    77,   17,   79,   80,   81,    4,    5,    6,    7,   17,
 /*   350 */   137,  102,  103,  104,   27,   28,  105,   25,  109,    1,
 /*   360 */    81,   80,    4,    5,   32,   33,  115,  116,  117,  120,
 /*   370 */    12,   39,   82,   15,  125,   81,    3,   36,   20,   21,
 /*   380 */    22,    0,   24,    3,   39,  105,    3,  107,  137,    3,
 /*   390 */     3,   10,    3,  113,  114,  115,  116,  117,   97,   78,
 /*   400 */    68,   69,   25,    2,    3,    4,    5,    6,    7,   77,
 /*   400 */   136,  136,  136,   17,  136,  136,   89,  136,   91,  136,
 /*   410 */   136,   25,  104,  136,  106,  136,  136,   31,   32,  111,
 /*   420 */   112,  113,  114,  115,   38,  136,  136,    2,    3,    4,
 /*   430 */     5,    6,    7,  136,    1,  136,  136,    4,    5,    4,
 /*   440 */     5,    6,    7,  135,  136,   12,  136,  136,   15,  136,
 /*   450 */    25,  136,   17,   67,   68,  136,   31,   32,  136,  136,
 /*   460 */    25,  136,   76,   38,   78,   79,   80,    1,  136,  136,
 /*   470 */     4,    5,    4,    5,    6,    7,  136,  136,   12,   46,
 /*   480 */    47,   15,  136,  136,  136,   17,   20,   21,   22,  136,
 /*   490 */   136,  136,   67,   68,  136,    1,    2,  136,    4,    5,
 /*   500 */   136,   76,  136,   78,   79,   80,   12,  136,  104,   15,
 /*   510 */     4,    5,    6,    7,  136,   82,  136,  113,  114,  115,
 /*   520 */   136,  117,   89,   17,   91,   92,   93,   94,   95,  136,
 /*   530 */     1,    2,   38,    4,    5,    4,    5,    6,    7,  135,
 /*   540 */   136,   12,  136,  136,   15,  136,  136,  136,   82,    4,
 /*   550 */     5,    6,    7,  136,  136,   89,   25,   91,   92,   93,
 /*   560 */    94,   95,  136,    1,    2,  136,    4,    5,  136,  136,
 /*   570 */    25,  104,  136,  106,   12,  136,   82,   15,  111,  112,
 /*   580 */   113,  114,  115,   89,  136,   91,   92,   93,   94,   95,
 /*   590 */     1,  136,  136,    4,    5,  136,  136,  136,  136,  136,
 /*   600 */   136,   12,  135,  136,   15,  136,  104,  136,  106,  136,
 /*   610 */   136,   82,  136,  111,  112,  113,  114,  115,   89,  136,
 /*   620 */    91,   92,   93,   94,   95,    1,  136,  136,    4,    5,
 /*   630 */   136,  136,  104,  136,  136,  136,   12,  135,  136,   15,
 /*   640 */   136,  113,  114,  115,   82,  136,  136,    2,  120,  121,
 /*   650 */   136,   89,  136,   91,   92,   93,   94,   95,    1,  136,
 /*   660 */   136,    4,    5,  135,  104,  136,  136,  136,  136,   12,
 /*   670 */   136,   82,   15,  113,  114,  115,   31,   32,   89,  136,
 /*   680 */    91,   92,   93,   94,   95,  101,  102,  103,  136,  136,
 /*   690 */   104,  107,  106,   48,  104,  135,  136,  111,  112,  113,
 /*   700 */   114,  115,  118,  113,  114,  115,   82,  123,  136,   64,
 /*   710 */   120,  121,  136,   89,  136,   91,   92,   93,   94,   95,
 /*   720 */   136,  135,  136,  136,  136,  135,  104,  136,  106,   84,
 /*   730 */    85,   86,   87,  111,  112,  113,  114,  115,  136,   82,
 /*   740 */   100,  101,  102,  103,  136,  136,   89,  107,   91,   92,
 /*   750 */    93,   94,   95,  136,  136,  136,  136,  135,  118,  104,
 /*   760 */   136,  106,  122,  123,  136,  136,  111,  112,  113,  114,
 /*   770 */   115,  136,  136,  136,  104,  136,  106,  136,  136,  136,
 /*   780 */   136,  111,  112,  113,  114,  115,  104,  136,  106,  104,
 /*   790 */   135,  136,  136,  111,  112,  113,  114,  115,  113,  114,
 /*   800 */   115,  136,  104,  136,  106,  135,  136,  136,  136,  111,
 /*   810 */   112,  113,  114,  115,  104,  136,  106,  135,  136,  136,
 /*   820 */   135,  111,  112,  113,  114,  115,  104,    4,    5,    6,
 /*   830 */     7,  136,  136,  135,  136,  113,  114,  115,  136,  104,
 /*   840 */    17,  106,  120,  136,  136,  135,  111,  112,  113,  114,
 /*   850 */   115,  104,  136,  106,  136,  136,  136,  135,  111,  112,
 /*   860 */   113,  114,  115,  136,  104,  136,  106,  136,  136,  136,
 /*   870 */   135,  111,  112,  113,  114,  115,  136,  136,  136,  104,
 /*   880 */   136,  106,  135,  136,  136,  136,  111,  112,  113,  114,
 /*   890 */   115,  104,  136,  106,  104,  135,  136,  136,  111,  112,
 /*   900 */   113,  114,  115,  113,  114,  115,  136,  104,  136,  106,
 /*   910 */   135,  136,  136,  136,  111,  112,  113,  114,  115,  104,
 /*   920 */   136,  106,  135,  136,  136,  135,  111,  112,  113,  114,
 /*   930 */   115,  104,    4,    5,    6,    7,  136,  136,  135,  136,
 /*   940 */   113,  114,  115,  136,  104,   17,  106,  136,  136,  136,
 /*   950 */   135,  111,  112,  113,  114,  115,  104,  136,  106,  136,
 /*   960 */   136,  136,  135,  111,  112,  113,  114,  115,  136,  104,
 /*   970 */   136,  106,  136,  136,  136,  135,  111,  112,  113,  114,
 /*   980 */   115,  136,  136,  136,  104,  136,  106,  135,  136,  136,
 /*   990 */   136,  111,  112,  113,  114,  115,  104,  136,  106,  104,
 /*  1000 */   135,  136,  136,  111,  112,  113,  114,  115,  113,  114,
 /*  1010 */   115,  136,  104,  136,  106,  135,  136,    2,  136,  111,
 /*  1020 */   112,  113,  114,  115,  104,  136,  106,  135,  136,  136,
 /*  1030 */   135,  111,  112,  113,  114,  115,    4,    5,    6,    7,
 /*  1040 */   136,  136,  136,  135,  136,  136,   31,   32,  136,  104,
 /*  1050 */   136,  136,  136,  136,  136,  135,  136,   25,  113,  114,
 /*  1060 */   115,  104,  136,  136,  119,  120,  121,  136,  136,  136,
 /*  1070 */   113,  114,  115,  136,  136,  136,  136,  120,  136,   64,
 /*  1080 */   135,  136,  136,  136,  127,  136,  129,  100,  101,  102,
 /*  1090 */   103,  136,  135,  136,  107,  136,  136,  136,  104,   84,
 /*  1100 */    85,   86,   87,  104,  136,  118,  136,  113,  114,  115,
 /*  1110 */   123,  117,  113,  114,  115,  104,  136,    4,    5,    6,
 /*  1120 */     7,  136,  104,  136,  113,  114,  115,  104,  136,  135,
 /*  1130 */    17,  113,  114,  115,  135,  136,  113,  114,  115,  104,
 /*  1140 */   136,  136,  136,  136,  136,  136,  135,  136,  113,  114,
 /*  1150 */   115,  104,  136,  135,  104,  136,  136,  136,  135,  136,
 /*  1160 */   113,  114,  115,  113,  114,  115,  104,  136,  136,  104,
 /*  1170 */   135,  136,  136,  136,  136,  113,  114,  115,  113,  114,
 /*  1180 */   115,  136,  135,  136,  104,  135,  136,  136,  136,  136,
 /*  1190 */   136,  136,  104,  113,  114,  115,  136,  135,  136,  104,
 /*  1200 */   135,  113,  114,  115,  136,  136,  136,  104,  113,  114,
 /*  1210 */   115,  136,  136,  136,  136,  135,  113,  114,  115,  136,
 /*  1220 */   136,  136,  136,  135,  136,  136,  136,  136,  136,  136,
 /*  1230 */   135,  104,  136,  136,  104,  136,  136,  104,  135,  136,
 /*  1240 */   113,  114,  115,  113,  114,  115,  113,  114,  115,  136,
 /*  1250 */   136,  136,  136,  136,  136,  136,  136,  136,  136,  136,
 /*  1260 */   136,  136,  135,  104,  136,  135,  104,  136,  135,  104,
 /*  1270 */   136,  136,  113,  114,  115,  113,  114,  115,  113,  114,
 /*  1280 */   115,  104,  136,  136,  136,  136,  136,  136,  136,  136,
 /*  1290 */   113,  114,  115,  136,  135,  136,  136,  135,  136,  136,
 /*  1300 */   135,  136,  136,  136,  136,  136,  136,  136,  136,  136,
 /*  1310 */   136,  136,  135,  100,  100,  100,  100,  100,  100,  100,
 /*  1320 */   100,  100,  100,  100,  100,  100,  100,  100,  100,  100,
 /*  1330 */   100,  100,  100,  100,  100,  100,  100,  100,  100,  100,
 /*  1340 */   100,  100,  100,  100,  100,  100,  100,  100,  100,  100,
 /*  1350 */   100,  100,  100,  100,  100,  100,  100,  100,  100,  100,
 /*  1360 */   100,  100,  100,  100,  100,  100,  100,  100,  100,  100,
 /*  1370 */   100,  100,  100,  100,  100,  100,  100,  100,  100,  100,
 /*  1380 */   100,  100,  100,  100,  100,  100,  100,  100,  100,  100,
 /*  1390 */   100,  100,  100,  100,  100,  100,  100,  100,  100,  100,
 /*  1400 */   100,  100,  100,  100,  100,  100,  100,  100,  100,  100,
 /*   410 */   105,   79,   80,   81,   15,   15,   12,  137,   15,   92,
 /*   420 */   115,  116,  117,   17,  119,   29,   25,    4,    5,    6,
 /*   430 */     7,   29,  127,   32,   33,   91,   29,    2,   11,    3,
 /*   440 */    39,   83,  137,  138,  138,  105,  138,  138,   90,  138,
 /*   450 */    92,   93,   94,   95,   96,  115,  116,  117,  138,   36,
 /*   460 */   138,  121,  122,  123,  138,  138,    2,   32,   33,   68,
 /*   470 */    69,  138,    1,    4,    5,    6,    7,  137,   77,  138,
 /*   480 */    79,   80,   81,   12,   49,   14,   17,  138,  138,   18,
 /*   490 */   138,   20,   21,   22,   23,   24,   32,   33,   27,   28,
 /*   500 */    65,   30,   31,   32,   33,    1,  138,  138,    4,    5,
 /*   510 */     4,    5,    6,    7,  138,  138,   12,  138,  138,   15,
 /*   520 */    85,   86,   87,   88,   20,   21,   22,    1,  138,   65,
 /*   530 */     4,    5,    4,    5,    6,    7,  138,  138,   12,  138,
 /*   540 */   138,   15,  138,    1,    2,   17,    4,    5,  138,   85,
 /*   550 */    86,   87,   88,   25,   12,  138,  105,   15,  107,  138,
 /*   560 */   105,  138,  138,  138,  113,  114,  115,  116,  117,  138,
 /*   570 */   115,  116,  117,   47,   48,    1,    2,  122,    4,    5,
 /*   580 */   138,   39,    4,    5,    6,    7,   12,   83,  137,   15,
 /*   590 */   138,  138,  137,  138,   90,   17,   92,   93,   94,   95,
 /*   600 */    96,    1,    2,   25,    4,    5,  138,  138,  138,   83,
 /*   610 */   138,  138,   12,  138,  138,   15,   90,  138,   92,   93,
 /*   620 */    94,   95,   96,  138,  138,   83,    4,    5,    6,    7,
 /*   630 */   138,  138,   90,  138,   92,   93,   94,   95,   96,    1,
 /*   640 */   138,  138,    4,    5,  105,  138,  138,   25,  138,  138,
 /*   650 */    12,  138,  138,   15,  115,  116,  117,   83,  138,  138,
 /*   660 */   138,  122,  123,  138,   90,  138,   92,   93,   94,   95,
 /*   670 */    96,    1,  138,  138,    4,    5,  137,  105,  138,  138,
 /*   680 */   138,  138,   12,   83,  138,   15,  138,  115,  116,  117,
 /*   690 */    90,  119,   92,   93,   94,   95,   96,    1,  138,  138,
 /*   700 */     4,    5,    4,    5,    6,    7,  138,  138,   12,  137,
 /*   710 */   138,   15,  138,    1,  138,   17,    4,    5,  138,  138,
 /*   720 */   138,   83,  138,  138,   12,  105,  138,   15,   90,  138,
 /*   730 */    92,   93,   94,   95,   96,  115,  116,  117,  105,  138,
 /*   740 */   107,  138,  122,  123,  105,  138,  113,  114,  115,  116,
 /*   750 */   117,  138,  138,   83,  115,  116,  117,  137,  119,  138,
 /*   760 */    90,  105,   92,   93,   94,   95,   96,  138,  138,  138,
 /*   770 */   137,  115,  116,  117,  138,  138,  137,  138,  138,   83,
 /*   780 */   138,    4,    5,    6,    7,  138,   90,  138,   92,   93,
 /*   790 */    94,   95,   96,  137,   17,   83,    4,    5,    6,    7,
 /*   800 */   105,  138,   90,  138,   92,   93,   94,   95,   96,  138,
 /*   810 */   115,  116,  117,  138,  105,  138,  107,   25,  138,  105,
 /*   820 */   138,  107,  113,  114,  115,  116,  117,  113,  114,  115,
 /*   830 */   116,  117,  137,  105,  138,  107,  138,  105,  138,  138,
 /*   840 */   138,  113,  114,  115,  116,  117,  137,  115,  116,  117,
 /*   850 */   138,  137,  105,  138,  107,  138,  138,  138,   27,   28,
 /*   860 */   113,  114,  115,  116,  117,  137,  105,  138,  107,  137,
 /*   870 */   138,  105,  138,  107,  113,  114,  115,  116,  117,  113,
 /*   880 */   114,  115,  116,  117,  137,  105,  138,  107,    4,    5,
 /*   890 */     6,    7,  138,  113,  114,  115,  116,  117,  137,  138,
 /*   900 */   138,   17,  138,  137,  138,  138,  138,  138,  138,  138,
 /*   910 */   105,  138,  107,  138,  138,   84,  138,  137,  113,  114,
 /*   920 */   115,  116,  117,   92,  138,  105,  138,  107,  138,  138,
 /*   930 */   138,  138,  138,  113,  114,  115,  116,  117,  138,  105,
 /*   940 */   138,  107,  137,  138,  138,  138,  138,  113,  114,  115,
 /*   950 */   116,  117,  105,  138,  107,  138,  138,  137,  138,  138,
 /*   960 */   113,  114,  115,  116,  117,  138,  105,  138,  107,  138,
 /*   970 */   138,  137,  138,  138,  113,  114,  115,  116,  117,  105,
 /*   980 */   138,  107,  138,  138,  137,  138,  138,  113,  114,  115,
 /*   990 */   116,  117,  138,  105,  138,  107,  138,  138,  137,  138,
 /*  1000 */   138,  113,  114,  115,  116,  117,  138,  138,  138,  138,
 /*  1010 */   138,  137,  138,  138,  138,  138,  138,  138,  105,  138,
 /*  1020 */   107,  138,  138,  138,  138,  137,  113,  114,  115,  116,
 /*  1030 */   117,  105,  138,  107,  138,  138,  138,  138,  138,  113,
 /*  1040 */   114,  115,  116,  117,  138,  105,  138,  107,  138,  138,
 /*  1050 */   137,  138,  138,  113,  114,  115,  116,  117,  105,  138,
 /*  1060 */   107,  138,  138,  137,  138,  138,  113,  114,  115,  116,
 /*  1070 */   117,  138,  105,  138,  138,  138,  138,  137,  138,  138,
 /*  1080 */   138,  105,  115,  116,  117,  138,  138,  138,  138,  122,
 /*  1090 */   137,  115,  116,  117,  138,  105,  129,  138,  131,  105,
 /*  1100 */   138,  138,  138,  138,  137,  115,  116,  117,  105,  115,
 /*  1110 */   116,  117,  138,  137,  138,  138,  138,  138,  115,  116,
 /*  1120 */   117,  138,  138,  138,  105,  138,  138,  137,  138,  138,
 /*  1130 */   138,  137,  105,  138,  115,  116,  117,  105,  138,  138,
 /*  1140 */   137,  138,  115,  116,  117,  138,  138,  115,  116,  117,
 /*  1150 */   138,  105,  138,  138,  138,  138,  137,  138,  138,  138,
 /*  1160 */   105,  115,  116,  117,  137,  138,  105,  138,  138,  137,
 /*  1170 */   115,  116,  117,  138,  105,  138,  115,  116,  117,  138,
 /*  1180 */   138,  138,  138,  137,  115,  116,  117,  138,  138,  105,
 /*  1190 */   138,  138,  137,  138,  138,  138,  138,  105,  137,  115,
 /*  1200 */   116,  117,  105,  138,  138,  105,  137,  115,  116,  117,
 /*  1210 */   138,  138,  115,  116,  117,  115,  116,  117,  138,  138,
 /*  1220 */   138,  137,  138,  138,  138,  138,  138,  138,  138,  137,
 /*  1230 */   105,  138,  138,  105,  137,  138,  138,  137,  105,  138,
 /*  1240 */   115,  116,  117,  115,  116,  117,  105,  138,  115,  116,
 /*  1250 */   117,  138,  138,  138,  138,  138,  115,  116,  117,  138,
 /*  1260 */   138,  138,  137,  138,  138,  137,  138,  138,  138,  105,
 /*  1270 */   137,  138,  105,  138,  138,  138,  138,  138,  137,  115,
 /*  1280 */   116,  117,  115,  116,  117,  138,  138,  138,  138,  138,
 /*  1290 */   138,  138,  138,  138,  138,  138,  138,  138,  138,  138,
 /*  1300 */   138,  137,  138,  138,  137,  101,  101,  101,  101,  101,
 /*  1310 */   101,  101,  101,  101,  101,  101,  101,  101,  101,  101,
 /*  1320 */   101,  101,  101,  101,  101,  101,  101,  101,  101,  101,
 /*  1330 */   101,  101,  101,  101,  101,  101,  101,  101,  101,  101,
 /*  1340 */   101,  101,  101,  101,  101,  101,  101,  101,  101,  101,
 /*  1350 */   101,  101,  101,  101,  101,  101,  101,  101,  101,  101,
 /*  1360 */   101,  101,  101,  101,  101,  101,  101,  101,  101,  101,
 /*  1370 */   101,  101,  101,  101,  101,  101,  101,  101,  101,  101,
 /*  1380 */   101,  101,  101,  101,  101,  101,  101,  101,  101,  101,
 /*  1390 */   101,  101,  101,  101,  101,  101,  101,  101,  101,  101,
 /*  1400 */   101,  101,  101,  101,  101,  101,
 /*  1410 */   100,  100,  100,
};
#define YY_SHIFT_COUNT    (163)
#define YY_SHIFT_MIN      (0)
#define YY_SHIFT_MAX      (1113)
#define YY_SHIFT_MAX      (884)
static const unsigned short int yy_shift_ofst[] = {
 /*     0 */   143,  127,  222,  287,  287,  287,  287,  287,  287,  287,
 /*    10 */   287,  287,  287,  287,  287,  287,  287,  287,  287,  287,
 /*    20 */   287,  287,  287,  287,  287,  287,  287,  244,  433,  266,
 /*    30 */   244,  143,  494,  494,    0,   64,  143,  589,  266,  589,
 /*    40 */   466,  466,  466,  529,  562,  266,  266,  266,  266,  266,
 /*    50 */   266,  624,  266,  266,  657,  266,  266,  266,  266,  266,
 /*    60 */   266,  266,  266,  266,  266,  179,  317,  317,  317,  317,
 /*    70 */   317,  645,  318,  386,  425, 1015, 1015,  118,   47, 1313,
 /*    80 */  1313, 1313, 1313,  114,  114,  200,  435,  129,  188,  234,
 /*    90 */   360,  468,  531,  506,  545,  823, 1032,  928, 1113,   25,
 /*   100 */    25,   25,  162,   25,   25,   25,   69,   25,   85,  128,
 /*   110 */    92,  105,  120,  136,  100,  183,  183,  176,  220,  174,
 /*   120 */   202,  275,  152,  207,  198,  219,  221,  208,  215,  217,
 /*   130 */   236,  238,  213,  267,  265,  262,  218,  273,  216,  224,
 /*   140 */   214,  225,  294,  295,  297,  272,  309,  324,  325,  249,
 /*   150 */   253,  307,  249,  331,  332,  338,  337,  335,  340,  342,
 /*   160 */   363,  293,  374,  373,
 /*     0 */   471,  129,  163,  229,  229,  229,  229,  229,  229,  229,
 /*    10 */   229,  229,  229,  229,  229,  229,  229,  229,  229,  229,
 /*    20 */   229,  229,  229,  229,  229,  229,  229,  358,  526,  638,
 /*    30 */   358,  471,  542,  542,    0,   65,  471,  670,  638,  670,
 /*    40 */   504,  504,  504,  574,  600,  638,  638,  638,  638,  638,
 /*    50 */   638,  696,  638,  638,  712,  638,  638,  638,  638,  638,
 /*    60 */   638,  638,  638,  638,  638,  254,  162,  162,  162,  162,
 /*    70 */   162,  435,  263,  332,  401,  464,  464,  205,   48, 1305,
 /*    80 */  1305, 1305, 1305,  132,  132,  528,  578,   62,  131,  341,
 /*    90 */   423,  293,    7,  469,  622,  698,  792,  777,  884,  506,
 /*   100 */   506,  506,   63,  506,  506,  506,  831,  506,  327,  103,
 /*   110 */   174,  187,    6,   66,  141,  236,  236,  244,  250,  140,
 /*   120 */   211,  381,  147,  178,  203,  216,  212,  219,  206,  223,
 /*   130 */   231,  238,  234,  274,  284,  278,  257,  324,  279,  281,
 /*   140 */   290,  294,  373,  380,  383,  345,  386,  387,  389,  301,
 /*   150 */   321,  377,  301,  399,  400,  403,  406,  396,  402,  407,
 /*   160 */   404,  344,  436,  427,
};
#define YY_REDUCE_COUNT (82)
#define YY_REDUCE_MIN   (-130)
#define YY_REDUCE_MAX   (1177)
#define YY_REDUCE_MIN   (-132)
#define YY_REDUCE_MAX   (1167)
static const short yy_reduce_ofst[] = {
 /*     0 */   640,  -97,  -33,  308,  467,  502,  586,  622,  655,  670,
 /*    10 */   682,  698,  710,  735,  747,  760,  775,  787,  803,  815,
 /*    20 */   840,  852,  865,  880,  892,  908,  920,  159,  945,  957,
 /*    30 */   227,  987,  528,  590,  -62,  -62,  584,  404,  722,  994,
 /*    40 */   560,  685,  790,  827,  895,  999, 1011, 1018, 1023, 1035,
 /*    50 */  1047, 1050, 1062, 1065, 1080, 1088, 1095, 1103, 1127, 1130,
 /*    60 */  1133, 1159, 1162, 1165, 1177,  -82, -112, -102,  -48,  -38,
 /*    70 */   -24,  -23, -130, -130, -130,  -98,  -87,  -59, -101,  -41,
 /*    80 */    46,   52,  -36,
 /*     0 */   -76,  -98,  -33,   35,  176,  213,  280,  451,  633,  709,
 /*    10 */   714,  728,  747,  761,  766,  780,  805,  820,  834,  847,
 /*    20 */   861,  874,  888,  913,  926,  940,  953,   54,  340,  967,
 /*    30 */   305,  -11,  539,  620,   76,   76,  249,  639,  455,  572,
 /*    40 */   251,  656,  695,  732,  976,  990,  994, 1003, 1019, 1027,
 /*    50 */  1032, 1046, 1055, 1061, 1069, 1084, 1092, 1097, 1100, 1125,
 /*    60 */  1128, 1133, 1141, 1164, 1167,   82, -114,  -39,  -20,   30,
 /*    70 */   169,   83, -132, -132, -132,  -99,  -77,  -62,  -40,   25,
 /*    80 */    40,   59,   80,
};
static const YYACTIONTYPE yy_default[] = {
 /*     0 */   449,  443,  443,  443,  443,  443,  443,  443,  443,  443,
 /*    10 */   443,  443,  443,  443,  443,  443,  443,  443,  443,  443,
 /*    20 */   443,  443,  443,  443,  443,  443,  443,  443,  473,  576,
 /*    30 */   443,  449,  580,  485,  581,  581,  449,  443,  443,  443,
 /*    40 */   443,  443,  443,  443,  443,  443,  443,  443,  477,  443,
1162
1163
1164
1165
1166
1167
1168

1169
1170
1171
1172
1173
1174
1175
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226







+







    0,  /*  CODEBLOCK => nothing */
    0,  /*       FILL => nothing */
    0,  /*      COLOR => nothing */
    0,  /*  THICKNESS => nothing */
    0,  /*      PRINT => nothing */
    0,  /*     STRING => nothing */
    0,  /*      COMMA => nothing */
    0,  /*    ISODATE => nothing */
    0,  /*  CLASSNAME => nothing */
    0,  /*         LB => nothing */
    0,  /*         RB => nothing */
    0,  /*         UP => nothing */
    0,  /*       DOWN => nothing */
    0,  /*       LEFT => nothing */
    0,  /*      RIGHT => nothing */
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
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
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
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434





















































































1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461



























1462
1463
1464
1465
1466
1467
1468
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
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
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
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
1517
1518
1519
1520
1521







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  /*   19 */ "CODEBLOCK",
  /*   20 */ "FILL",
  /*   21 */ "COLOR",
  /*   22 */ "THICKNESS",
  /*   23 */ "PRINT",
  /*   24 */ "STRING",
  /*   25 */ "COMMA",
  /*   26 */ "CLASSNAME",
  /*   27 */ "LB",
  /*   28 */ "RB",
  /*   29 */ "UP",
  /*   30 */ "DOWN",
  /*   31 */ "LEFT",
  /*   32 */ "RIGHT",
  /*   33 */ "CLOSE",
  /*   34 */ "CHOP",
  /*   35 */ "FROM",
  /*   36 */ "TO",
  /*   37 */ "THEN",
  /*   38 */ "HEADING",
  /*   39 */ "GO",
  /*   40 */ "AT",
  /*   41 */ "WITH",
  /*   42 */ "SAME",
  /*   43 */ "AS",
  /*   44 */ "FIT",
  /*   45 */ "BEHIND",
  /*   46 */ "UNTIL",
  /*   47 */ "EVEN",
  /*   48 */ "DOT_E",
  /*   49 */ "HEIGHT",
  /*   50 */ "WIDTH",
  /*   51 */ "RADIUS",
  /*   52 */ "DIAMETER",
  /*   53 */ "DOTTED",
  /*   54 */ "DASHED",
  /*   55 */ "CW",
  /*   56 */ "CCW",
  /*   57 */ "LARROW",
  /*   58 */ "RARROW",
  /*   59 */ "LRARROW",
  /*   60 */ "INVIS",
  /*   61 */ "THICK",
  /*   62 */ "THIN",
  /*   63 */ "SOLID",
  /*   64 */ "CENTER",
  /*   65 */ "LJUST",
  /*   66 */ "RJUST",
  /*   67 */ "ABOVE",
  /*   68 */ "BELOW",
  /*   69 */ "ITALIC",
  /*   70 */ "BOLD",
  /*   71 */ "MONO",
  /*   72 */ "ALIGNED",
  /*   73 */ "BIG",
  /*   74 */ "SMALL",
  /*   75 */ "AND",
  /*   76 */ "LT",
  /*   77 */ "GT",
  /*   78 */ "ON",
  /*   79 */ "WAY",
  /*   80 */ "BETWEEN",
  /*   81 */ "THE",
  /*   82 */ "NTH",
  /*   83 */ "VERTEX",
  /*   84 */ "TOP",
  /*   85 */ "BOTTOM",
  /*   86 */ "START",
  /*   87 */ "END",
  /*   88 */ "IN",
  /*   89 */ "THIS",
  /*   90 */ "DOT_U",
  /*   91 */ "LAST",
  /*   92 */ "NUMBER",
  /*   93 */ "FUNC1",
  /*   94 */ "FUNC2",
  /*   95 */ "DIST",
  /*   96 */ "DOT_XY",
  /*   97 */ "X",
  /*   98 */ "Y",
  /*   99 */ "DOT_L",
  /*  100 */ "statement_list",
  /*  101 */ "statement",
  /*  102 */ "unnamed_statement",
  /*  103 */ "basetype",
  /*  104 */ "expr",
  /*  105 */ "numproperty",
  /*  106 */ "edge",
  /*  107 */ "direction",
  /*  108 */ "dashproperty",
  /*   26 */ "ISODATE",
  /*   27 */ "CLASSNAME",
  /*   28 */ "LB",
  /*   29 */ "RB",
  /*   30 */ "UP",
  /*   31 */ "DOWN",
  /*   32 */ "LEFT",
  /*   33 */ "RIGHT",
  /*   34 */ "CLOSE",
  /*   35 */ "CHOP",
  /*   36 */ "FROM",
  /*   37 */ "TO",
  /*   38 */ "THEN",
  /*   39 */ "HEADING",
  /*   40 */ "GO",
  /*   41 */ "AT",
  /*   42 */ "WITH",
  /*   43 */ "SAME",
  /*   44 */ "AS",
  /*   45 */ "FIT",
  /*   46 */ "BEHIND",
  /*   47 */ "UNTIL",
  /*   48 */ "EVEN",
  /*   49 */ "DOT_E",
  /*   50 */ "HEIGHT",
  /*   51 */ "WIDTH",
  /*   52 */ "RADIUS",
  /*   53 */ "DIAMETER",
  /*   54 */ "DOTTED",
  /*   55 */ "DASHED",
  /*   56 */ "CW",
  /*   57 */ "CCW",
  /*   58 */ "LARROW",
  /*   59 */ "RARROW",
  /*   60 */ "LRARROW",
  /*   61 */ "INVIS",
  /*   62 */ "THICK",
  /*   63 */ "THIN",
  /*   64 */ "SOLID",
  /*   65 */ "CENTER",
  /*   66 */ "LJUST",
  /*   67 */ "RJUST",
  /*   68 */ "ABOVE",
  /*   69 */ "BELOW",
  /*   70 */ "ITALIC",
  /*   71 */ "BOLD",
  /*   72 */ "MONO",
  /*   73 */ "ALIGNED",
  /*   74 */ "BIG",
  /*   75 */ "SMALL",
  /*   76 */ "AND",
  /*   77 */ "LT",
  /*   78 */ "GT",
  /*   79 */ "ON",
  /*   80 */ "WAY",
  /*   81 */ "BETWEEN",
  /*   82 */ "THE",
  /*   83 */ "NTH",
  /*   84 */ "VERTEX",
  /*   85 */ "TOP",
  /*   86 */ "BOTTOM",
  /*   87 */ "START",
  /*   88 */ "END",
  /*   89 */ "IN",
  /*   90 */ "THIS",
  /*   91 */ "DOT_U",
  /*   92 */ "LAST",
  /*   93 */ "NUMBER",
  /*   94 */ "FUNC1",
  /*   95 */ "FUNC2",
  /*   96 */ "DIST",
  /*   97 */ "DOT_XY",
  /*   98 */ "X",
  /*   99 */ "Y",
  /*  100 */ "DOT_L",
  /*  101 */ "statement_list",
  /*  102 */ "statement",
  /*  103 */ "unnamed_statement",
  /*  104 */ "basetype",
  /*  105 */ "expr",
  /*  106 */ "numproperty",
  /*  107 */ "edge",
  /*  108 */ "isodate",
  /*  109 */ "direction",
  /*  110 */ "dashproperty",
  /*  109 */ "colorproperty",
  /*  110 */ "locproperty",
  /*  111 */ "position",
  /*  112 */ "place",
  /*  113 */ "object",
  /*  114 */ "objectname",
  /*  115 */ "nth",
  /*  116 */ "textposition",
  /*  117 */ "rvalue",
  /*  118 */ "lvalue",
  /*  119 */ "even",
  /*  120 */ "relexpr",
  /*  121 */ "optrelexpr",
  /*  122 */ "document",
  /*  123 */ "print",
  /*  124 */ "prlist",
  /*  125 */ "pritem",
  /*  126 */ "prsep",
  /*  127 */ "attribute_list",
  /*  128 */ "savelist",
  /*  129 */ "alist",
  /*  130 */ "attribute",
  /*  131 */ "go",
  /*  132 */ "boolproperty",
  /*  133 */ "withclause",
  /*  134 */ "between",
  /*  135 */ "place2",
  /*  111 */ "colorproperty",
  /*  112 */ "locproperty",
  /*  113 */ "position",
  /*  114 */ "place",
  /*  115 */ "object",
  /*  116 */ "objectname",
  /*  117 */ "nth",
  /*  118 */ "textposition",
  /*  119 */ "rvalue",
  /*  120 */ "lvalue",
  /*  121 */ "even",
  /*  122 */ "relexpr",
  /*  123 */ "optrelexpr",
  /*  124 */ "document",
  /*  125 */ "print",
  /*  126 */ "prlist",
  /*  127 */ "pritem",
  /*  128 */ "prsep",
  /*  129 */ "attribute_list",
  /*  130 */ "savelist",
  /*  131 */ "alist",
  /*  132 */ "attribute",
  /*  133 */ "go",
  /*  134 */ "boolproperty",
  /*  135 */ "withclause",
  /*  136 */ "between",
  /*  137 */ "place2",
};
#endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */

#ifndef NDEBUG
/* For tracing reduce actions, the names of all rules are required.
*/
static const char *const yyRuleName[] = {
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
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







-
+

-
-
-
+
+
+


-
-
-
+
+
+

-
-
-
+
+
+







    ** being destroyed before it is finished parsing.
    **
    ** Note: during a reduce, the only symbols destroyed are those
    ** which appear on the RHS of the rule, but which are *not* used
    ** inside the C code.
    */
/********* Begin destructor definitions ***************************************/
    case 100: /* statement_list */
    case 101: /* statement_list */
{
#line 511 "pikchr.y"
pik_elist_free(p,(yypminor->yy235));
#line 1777 "pikchr.c"
#line 524 "pikchr.y"
pik_elist_free(p,(yypminor->yy23));
#line 1805 "pikchr.c"
}
      break;
    case 101: /* statement */
    case 102: /* unnamed_statement */
    case 103: /* basetype */
    case 102: /* statement */
    case 103: /* unnamed_statement */
    case 104: /* basetype */
{
#line 513 "pikchr.y"
pik_elem_free(p,(yypminor->yy162));
#line 1786 "pikchr.c"
#line 526 "pikchr.y"
pik_elem_free(p,(yypminor->yy54));
#line 1814 "pikchr.c"
}
      break;
/********* End destructor definitions *****************************************/
    default:  break;   /* If no destructor action specified: do nothing */
  }
}

1989
1990
1991
1992
1993
1994
1995
1996

1997
1998
1999

2000
2001
2002
2003
2004
2005
2006
2042
2043
2044
2045
2046
2047
2048

2049
2050
2051

2052
2053
2054
2055
2056
2057
2058
2059







-
+


-
+







     fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
   }
#endif
   while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser);
   /* Here code is inserted which will execute if the parser
   ** stack every overflows */
/******** Begin %stack_overflow code ******************************************/
#line 545 "pikchr.y"
#line 559 "pikchr.y"

  pik_error(p, 0, "parser stack overflow");
#line 2024 "pikchr.c"
#line 2052 "pikchr.c"
/******** End %stack_overflow code ********************************************/
   pik_parserARG_STORE /* Suppress warning about unused %extra_argument var */
   pik_parserCTX_STORE
}

/*
** Print tracing information for a SHIFT action
2058
2059
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
2088
2089
2090
2091
2092
2093
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
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
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
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
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







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  yytos->minor.yy0 = yyMinor;
  yyTraceShift(yypParser, yyNewState, "Shift");
}

/* For rule J, yyRuleInfoLhs[J] contains the symbol on the left-hand side
** of that rule */
static const YYCODETYPE yyRuleInfoLhs[] = {
   122,  /* (0) document ::= statement_list */
   100,  /* (1) statement_list ::= statement */
   100,  /* (2) statement_list ::= statement_list EOL statement */
   101,  /* (3) statement ::= */
   101,  /* (4) statement ::= direction */
   101,  /* (5) statement ::= lvalue ASSIGN rvalue */
   101,  /* (6) statement ::= PLACENAME COLON unnamed_statement */
   101,  /* (7) statement ::= PLACENAME COLON position */
   101,  /* (8) statement ::= unnamed_statement */
   101,  /* (9) statement ::= print prlist */
   101,  /* (10) statement ::= ASSERT LP expr EQ expr RP */
   101,  /* (11) statement ::= ASSERT LP position EQ position RP */
   101,  /* (12) statement ::= DEFINE ID CODEBLOCK */
   117,  /* (13) rvalue ::= PLACENAME */
   125,  /* (14) pritem ::= FILL */
   125,  /* (15) pritem ::= COLOR */
   125,  /* (16) pritem ::= THICKNESS */
   125,  /* (17) pritem ::= rvalue */
   125,  /* (18) pritem ::= STRING */
   126,  /* (19) prsep ::= COMMA */
   102,  /* (20) unnamed_statement ::= basetype attribute_list */
   103,  /* (21) basetype ::= CLASSNAME */
   103,  /* (22) basetype ::= STRING textposition */
   103,  /* (23) basetype ::= LB savelist statement_list RB */
   128,  /* (24) savelist ::= */
   120,  /* (25) relexpr ::= expr */
   120,  /* (26) relexpr ::= expr PERCENT */
   121,  /* (27) optrelexpr ::= */
   127,  /* (28) attribute_list ::= relexpr alist */
   130,  /* (29) attribute ::= numproperty relexpr */
   130,  /* (30) attribute ::= dashproperty expr */
   130,  /* (31) attribute ::= dashproperty */
   130,  /* (32) attribute ::= colorproperty rvalue */
   130,  /* (33) attribute ::= go direction optrelexpr */
   130,  /* (34) attribute ::= go direction even position */
   130,  /* (35) attribute ::= CLOSE */
   130,  /* (36) attribute ::= CHOP */
   130,  /* (37) attribute ::= FROM position */
   130,  /* (38) attribute ::= TO position */
   130,  /* (39) attribute ::= THEN */
   130,  /* (40) attribute ::= THEN optrelexpr HEADING expr */
   130,  /* (41) attribute ::= THEN optrelexpr EDGEPT */
   130,  /* (42) attribute ::= GO optrelexpr HEADING expr */
   130,  /* (43) attribute ::= GO optrelexpr EDGEPT */
   130,  /* (44) attribute ::= AT position */
   130,  /* (45) attribute ::= SAME */
   130,  /* (46) attribute ::= SAME AS object */
   130,  /* (47) attribute ::= STRING textposition */
   130,  /* (48) attribute ::= FIT */
   130,  /* (49) attribute ::= BEHIND object */
   133,  /* (50) withclause ::= DOT_E edge AT position */
   133,  /* (51) withclause ::= edge AT position */
   105,  /* (52) numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */
   132,  /* (53) boolproperty ::= CW */
   132,  /* (54) boolproperty ::= CCW */
   132,  /* (55) boolproperty ::= LARROW */
   132,  /* (56) boolproperty ::= RARROW */
   132,  /* (57) boolproperty ::= LRARROW */
   132,  /* (58) boolproperty ::= INVIS */
   132,  /* (59) boolproperty ::= THICK */
   132,  /* (60) boolproperty ::= THIN */
   132,  /* (61) boolproperty ::= SOLID */
   116,  /* (62) textposition ::= */
   116,  /* (63) textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|MONO|ALIGNED|BIG|SMALL */
   111,  /* (64) position ::= expr COMMA expr */
   111,  /* (65) position ::= place PLUS expr COMMA expr */
   111,  /* (66) position ::= place MINUS expr COMMA expr */
   111,  /* (67) position ::= place PLUS LP expr COMMA expr RP */
   111,  /* (68) position ::= place MINUS LP expr COMMA expr RP */
   111,  /* (69) position ::= LP position COMMA position RP */
   111,  /* (70) position ::= LP position RP */
   111,  /* (71) position ::= expr between position AND position */
   111,  /* (72) position ::= expr LT position COMMA position GT */
   111,  /* (73) position ::= expr ABOVE position */
   111,  /* (74) position ::= expr BELOW position */
   111,  /* (75) position ::= expr LEFT OF position */
   111,  /* (76) position ::= expr RIGHT OF position */
   111,  /* (77) position ::= expr ON HEADING EDGEPT OF position */
   111,  /* (78) position ::= expr HEADING EDGEPT OF position */
   111,  /* (79) position ::= expr EDGEPT OF position */
   111,  /* (80) position ::= expr ON HEADING expr FROM position */
   111,  /* (81) position ::= expr HEADING expr FROM position */
   112,  /* (82) place ::= edge OF object */
   135,  /* (83) place2 ::= object */
   135,  /* (84) place2 ::= object DOT_E edge */
   135,  /* (85) place2 ::= NTH VERTEX OF object */
   113,  /* (86) object ::= nth */
   113,  /* (87) object ::= nth OF|IN object */
   114,  /* (88) objectname ::= THIS */
   114,  /* (89) objectname ::= PLACENAME */
   114,  /* (90) objectname ::= objectname DOT_U PLACENAME */
   115,  /* (91) nth ::= NTH CLASSNAME */
   115,  /* (92) nth ::= NTH LAST CLASSNAME */
   115,  /* (93) nth ::= LAST CLASSNAME */
   115,  /* (94) nth ::= LAST */
   115,  /* (95) nth ::= NTH LB RB */
   115,  /* (96) nth ::= NTH LAST LB RB */
   115,  /* (97) nth ::= LAST LB RB */
   104,  /* (98) expr ::= expr PLUS expr */
   104,  /* (99) expr ::= expr MINUS expr */
   104,  /* (100) expr ::= expr STAR expr */
   104,  /* (101) expr ::= expr SLASH expr */
   104,  /* (102) expr ::= MINUS expr */
   104,  /* (103) expr ::= PLUS expr */
   104,  /* (104) expr ::= LP expr RP */
   104,  /* (105) expr ::= LP FILL|COLOR|THICKNESS RP */
   104,  /* (106) expr ::= NUMBER */
   104,  /* (107) expr ::= ID */
   104,  /* (108) expr ::= FUNC1 LP expr RP */
   104,  /* (109) expr ::= FUNC2 LP expr COMMA expr RP */
   104,  /* (110) expr ::= DIST LP position COMMA position RP */
   104,  /* (111) expr ::= place2 DOT_XY X */
   104,  /* (112) expr ::= place2 DOT_XY Y */
   104,  /* (113) expr ::= object DOT_L numproperty */
   104,  /* (114) expr ::= object DOT_L dashproperty */
   104,  /* (115) expr ::= object DOT_L colorproperty */
   118,  /* (116) lvalue ::= ID */
   118,  /* (117) lvalue ::= FILL */
   118,  /* (118) lvalue ::= COLOR */
   118,  /* (119) lvalue ::= THICKNESS */
   117,  /* (120) rvalue ::= expr */
   123,  /* (121) print ::= PRINT */
   124,  /* (122) prlist ::= pritem */
   124,  /* (123) prlist ::= prlist prsep pritem */
   107,  /* (124) direction ::= UP */
   107,  /* (125) direction ::= DOWN */
   107,  /* (126) direction ::= LEFT */
   107,  /* (127) direction ::= RIGHT */
   121,  /* (128) optrelexpr ::= relexpr */
   127,  /* (129) attribute_list ::= alist */
   129,  /* (130) alist ::= */
   129,  /* (131) alist ::= alist attribute */
   130,  /* (132) attribute ::= boolproperty */
   130,  /* (133) attribute ::= WITH withclause */
   131,  /* (134) go ::= GO */
   131,  /* (135) go ::= */
   119,  /* (136) even ::= UNTIL EVEN WITH */
   119,  /* (137) even ::= EVEN WITH */
   108,  /* (138) dashproperty ::= DOTTED */
   108,  /* (139) dashproperty ::= DASHED */
   109,  /* (140) colorproperty ::= FILL */
   109,  /* (141) colorproperty ::= COLOR */
   111,  /* (142) position ::= place */
   134,  /* (143) between ::= WAY BETWEEN */
   134,  /* (144) between ::= BETWEEN */
   134,  /* (145) between ::= OF THE WAY BETWEEN */
   112,  /* (146) place ::= place2 */
   106,  /* (147) edge ::= CENTER */
   106,  /* (148) edge ::= EDGEPT */
   106,  /* (149) edge ::= TOP */
   106,  /* (150) edge ::= BOTTOM */
   106,  /* (151) edge ::= START */
   106,  /* (152) edge ::= END */
   106,  /* (153) edge ::= RIGHT */
   106,  /* (154) edge ::= LEFT */
   113,  /* (155) object ::= objectname */
   124,  /* (0) document ::= statement_list */
   101,  /* (1) statement_list ::= statement */
   101,  /* (2) statement_list ::= statement_list EOL statement */
   102,  /* (3) statement ::= */
   102,  /* (4) statement ::= direction */
   102,  /* (5) statement ::= lvalue ASSIGN rvalue */
   102,  /* (6) statement ::= PLACENAME COLON unnamed_statement */
   102,  /* (7) statement ::= PLACENAME COLON position */
   102,  /* (8) statement ::= unnamed_statement */
   102,  /* (9) statement ::= print prlist */
   102,  /* (10) statement ::= ASSERT LP expr EQ expr RP */
   102,  /* (11) statement ::= ASSERT LP position EQ position RP */
   102,  /* (12) statement ::= DEFINE ID CODEBLOCK */
   119,  /* (13) rvalue ::= PLACENAME */
   127,  /* (14) pritem ::= FILL */
   127,  /* (15) pritem ::= COLOR */
   127,  /* (16) pritem ::= THICKNESS */
   127,  /* (17) pritem ::= rvalue */
   127,  /* (18) pritem ::= STRING */
   128,  /* (19) prsep ::= COMMA */
   103,  /* (20) unnamed_statement ::= basetype attribute_list */
   104,  /* (21) basetype ::= CLASSNAME */
   104,  /* (22) basetype ::= STRING textposition */
   104,  /* (23) basetype ::= LB savelist statement_list RB */
   130,  /* (24) savelist ::= */
   122,  /* (25) relexpr ::= expr */
   122,  /* (26) relexpr ::= expr PERCENT */
   123,  /* (27) optrelexpr ::= */
   129,  /* (28) attribute_list ::= relexpr alist */
   132,  /* (29) attribute ::= numproperty relexpr */
   132,  /* (30) attribute ::= dashproperty expr */
   132,  /* (31) attribute ::= dashproperty */
   132,  /* (32) attribute ::= colorproperty rvalue */
   132,  /* (33) attribute ::= go direction optrelexpr */
   132,  /* (34) attribute ::= go direction even position */
   132,  /* (35) attribute ::= CLOSE */
   132,  /* (36) attribute ::= CHOP */
   132,  /* (37) attribute ::= FROM position */
   132,  /* (38) attribute ::= TO position */
   132,  /* (39) attribute ::= THEN */
   132,  /* (40) attribute ::= THEN optrelexpr HEADING expr */
   132,  /* (41) attribute ::= THEN optrelexpr EDGEPT */
   132,  /* (42) attribute ::= GO optrelexpr HEADING expr */
   132,  /* (43) attribute ::= GO optrelexpr EDGEPT */
   132,  /* (44) attribute ::= AT position */
   132,  /* (45) attribute ::= SAME */
   132,  /* (46) attribute ::= SAME AS object */
   132,  /* (47) attribute ::= STRING textposition */
   132,  /* (48) attribute ::= FIT */
   132,  /* (49) attribute ::= BEHIND object */
   135,  /* (50) withclause ::= DOT_E edge AT position */
   135,  /* (51) withclause ::= edge AT position */
   106,  /* (52) numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */
   134,  /* (53) boolproperty ::= CW */
   134,  /* (54) boolproperty ::= CCW */
   134,  /* (55) boolproperty ::= LARROW */
   134,  /* (56) boolproperty ::= RARROW */
   134,  /* (57) boolproperty ::= LRARROW */
   134,  /* (58) boolproperty ::= INVIS */
   134,  /* (59) boolproperty ::= THICK */
   134,  /* (60) boolproperty ::= THIN */
   134,  /* (61) boolproperty ::= SOLID */
   118,  /* (62) textposition ::= */
   118,  /* (63) textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|MONO|ALIGNED|BIG|SMALL */
   113,  /* (64) position ::= expr COMMA expr */
   113,  /* (65) position ::= place PLUS expr COMMA expr */
   113,  /* (66) position ::= place MINUS expr COMMA expr */
   113,  /* (67) position ::= place PLUS LP expr COMMA expr RP */
   113,  /* (68) position ::= place MINUS LP expr COMMA expr RP */
   113,  /* (69) position ::= LP position COMMA position RP */
   113,  /* (70) position ::= LP position RP */
   113,  /* (71) position ::= expr between position AND position */
   113,  /* (72) position ::= expr LT position COMMA position GT */
   113,  /* (73) position ::= expr ABOVE position */
   113,  /* (74) position ::= expr BELOW position */
   113,  /* (75) position ::= expr LEFT OF position */
   113,  /* (76) position ::= expr RIGHT OF position */
   113,  /* (77) position ::= expr ON HEADING EDGEPT OF position */
   113,  /* (78) position ::= expr HEADING EDGEPT OF position */
   113,  /* (79) position ::= expr EDGEPT OF position */
   113,  /* (80) position ::= expr ON HEADING expr FROM position */
   113,  /* (81) position ::= expr HEADING expr FROM position */
   114,  /* (82) place ::= edge OF object */
   137,  /* (83) place2 ::= object */
   137,  /* (84) place2 ::= object DOT_E edge */
   137,  /* (85) place2 ::= NTH VERTEX OF object */
   115,  /* (86) object ::= nth */
   115,  /* (87) object ::= nth OF|IN object */
   116,  /* (88) objectname ::= THIS */
   116,  /* (89) objectname ::= PLACENAME */
   116,  /* (90) objectname ::= objectname DOT_U PLACENAME */
   117,  /* (91) nth ::= NTH CLASSNAME */
   117,  /* (92) nth ::= NTH LAST CLASSNAME */
   117,  /* (93) nth ::= LAST CLASSNAME */
   117,  /* (94) nth ::= LAST */
   117,  /* (95) nth ::= NTH LB RB */
   117,  /* (96) nth ::= NTH LAST LB RB */
   117,  /* (97) nth ::= LAST LB RB */
   105,  /* (98) expr ::= expr PLUS expr */
   105,  /* (99) expr ::= expr MINUS expr */
   105,  /* (100) expr ::= expr STAR expr */
   105,  /* (101) expr ::= expr SLASH expr */
   105,  /* (102) expr ::= MINUS expr */
   105,  /* (103) expr ::= PLUS expr */
   105,  /* (104) expr ::= LP expr RP */
   105,  /* (105) expr ::= LP FILL|COLOR|THICKNESS RP */
   105,  /* (106) expr ::= NUMBER */
   105,  /* (107) expr ::= ID */
   105,  /* (108) expr ::= FUNC1 LP expr RP */
   105,  /* (109) expr ::= FUNC2 LP expr COMMA expr RP */
   105,  /* (110) expr ::= DIST LP position COMMA position RP */
   105,  /* (111) expr ::= place2 DOT_XY X */
   105,  /* (112) expr ::= place2 DOT_XY Y */
   105,  /* (113) expr ::= object DOT_L numproperty */
   105,  /* (114) expr ::= object DOT_L dashproperty */
   105,  /* (115) expr ::= object DOT_L colorproperty */
   120,  /* (116) lvalue ::= ID */
   120,  /* (117) lvalue ::= FILL */
   120,  /* (118) lvalue ::= COLOR */
   120,  /* (119) lvalue ::= THICKNESS */
   119,  /* (120) rvalue ::= expr */
   125,  /* (121) print ::= PRINT */
   126,  /* (122) prlist ::= pritem */
   126,  /* (123) prlist ::= prlist prsep pritem */
   109,  /* (124) direction ::= UP */
   109,  /* (125) direction ::= DOWN */
   109,  /* (126) direction ::= LEFT */
   109,  /* (127) direction ::= RIGHT */
   123,  /* (128) optrelexpr ::= relexpr */
   129,  /* (129) attribute_list ::= alist */
   131,  /* (130) alist ::= */
   131,  /* (131) alist ::= alist attribute */
   132,  /* (132) attribute ::= boolproperty */
   132,  /* (133) attribute ::= WITH withclause */
   133,  /* (134) go ::= GO */
   133,  /* (135) go ::= */
   121,  /* (136) even ::= UNTIL EVEN WITH */
   121,  /* (137) even ::= EVEN WITH */
   110,  /* (138) dashproperty ::= DOTTED */
   110,  /* (139) dashproperty ::= DASHED */
   111,  /* (140) colorproperty ::= FILL */
   111,  /* (141) colorproperty ::= COLOR */
   113,  /* (142) position ::= place */
   136,  /* (143) between ::= WAY BETWEEN */
   136,  /* (144) between ::= BETWEEN */
   136,  /* (145) between ::= OF THE WAY BETWEEN */
   114,  /* (146) place ::= place2 */
   107,  /* (147) edge ::= CENTER */
   107,  /* (148) edge ::= EDGEPT */
   107,  /* (149) edge ::= TOP */
   107,  /* (150) edge ::= BOTTOM */
   107,  /* (151) edge ::= START */
   107,  /* (152) edge ::= END */
   107,  /* (153) edge ::= RIGHT */
   107,  /* (154) edge ::= LEFT */
   115,  /* (155) object ::= objectname */
};

/* For rule J, yyRuleInfoNRhs[J] contains the negative of the number
** of symbols on the right-hand side of that rule. */
static const signed char yyRuleInfoNRhs[] = {
   -1,  /* (0) document ::= statement_list */
   -1,  /* (1) statement_list ::= statement */
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



2444
2445
2446
2447
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



2480
2481
2482
2483
2484



2485
2486
2487
2488
2489



2490
2491
2492
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
2519
2520

2521
2522

2523
2524
2525
2526
2527
2528




2529
2530
2531
2532
2533
2534




2535
2536
2537
2538
2539
2540




2541
2542
2543
2544
2545



2546
2547
2548
2549
2550



2551
2552
2553
2554
2555
2556




2557
2558
2559
2560
2561
2562




2563
2564
2565
2566
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
2595
2596
2597



2598
2599
2600
2601
2602



2603
2604
2605

2606
2607

2608
2609
2610

2611
2612

2613
2614
2615
2616
2617



2618
2619
2620
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



2676
2677
2678

2679
2680

2681
2682
2683
2684

2685
2686

2687
2688
2689

2690
2691

2692
2693
2694

2695
2696

2697
2698
2699

2700
2701

2702
2703
2704

2705
2706

2707
2708
2709

2710
2711

2712
2713
2714

2715
2716

2717
2718
2719

2720
2721

2722
2723
2724

2725
2726
2727

2728
2729
2730
2731
2732



2733
2734
2735
2736
2737
2738




2739
2740
2741
2742
2743
2744




2745
2746
2747
2748
2749
2750




2751
2752
2753
2754
2755
2756




2757
2758
2759
2760
2761
2762




2763
2764
2765
2766
2767
2768




2769
2770
2771
2772
2773



2774
2775
2776
2777
2778



2779
2780
2781
2782
2783
2784




2785
2786
2787
2788
2789
2790




2791
2792
2793
2794
2795
2796




2797
2798
2799
2800
2801
2802




2803
2804
2805
2806
2807
2808




2809
2810
2811
2812
2813
2814




2815
2816
2817
2818
2819
2820




2821
2822
2823
2824
2825
2826




2827
2828
2829
2830
2831
2832




2833
2834
2835
2836
2837
2838




2839
2840
2841
2842
2843
2844




2845
2846
2847
2848
2849
2850




2851
2852
2853
2854
2855
2856




2857
2858
2859
2860
2861
2862




2863
2864
2865
2866
2867
2868




2869
2870
2871
2872
2873
2874




2875
2876
2877
2878
2879
2880




2881
2882
2883
2884
2885



2886
2887
2888
2889
2890
2891




2892
2893
2894
2895
2896
2897




2898
2899
2900

2901
2902

2903
2904
2905
2906

2907
2908

2909
2910
2911
2912

2913
2914

2915
2916
2917

2918
2919

2920
2921
2922
2923

2924
2925

2926
2927
2928
2929

2930
2931

2932
2933
2934
2935

2936
2937

2938
2939
2940
2941
2942
2943




2944
2945
2946
2947
2948
2949




2950
2951
2952
2953
2954
2955




2956
2957
2958

2959
2960
2961


2962
2963
2964


2965
2966
2967
2968
2969



2970
2971
2972
2973
2974



2975
2976
2977
2978
2979



2980
2981
2982
2983
2984



2985
2986
2987
2988
2989
2990




2991
2992
2993
2994
2995
2996




2997
2998
2999
3000
3001
3002




3003
3004
3005
3006
3007
3008




3009
3010
3011
3012
3013



3014
3015
3016
3017
3018
3019




3020
3021
3022
3023
3024
3025




3026
3027
3028
3029
3030
3031
3032
3033




3034
3035
3036
3037
3038
3039
3040
2470
2471
2472
2473
2474
2475
2476



2477
2478
2479
2480
2481




2482
2483
2484
2485
2486
2487




2488
2489
2490
2491
2492
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
2519
2520
2521
2522
2523




2524
2525
2526
2527
2528
2529



2530
2531
2532
2533
2534



2535
2536
2537
2538
2539



2540
2541
2542
2543
2544



2545
2546
2547
2548
2549




2550
2551
2552
2553
2554
2555
2556
2557

2558
2559

2560
2561
2562



2563
2564
2565
2566
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
2595



2596
2597
2598
2599
2600



2601
2602
2603
2604
2605




2606
2607
2608
2609
2610
2611




2612
2613
2614
2615
2616
2617



2618
2619
2620
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
2676
2677

2678
2679

2680
2681
2682
2683



2684
2685
2686
2687
2688
2689



2690
2691
2692
2693
2694



2695
2696
2697
2698
2699

2700
2701

2702
2703
2704



2705
2706
2707
2708
2709



2710
2711
2712
2713
2714



2715
2716
2717
2718
2719



2720
2721
2722
2723
2724
2725



2726
2727
2728
2729
2730

2731
2732

2733
2734
2735
2736

2737
2738

2739
2740
2741

2742
2743

2744
2745
2746

2747
2748

2749
2750
2751

2752
2753

2754
2755
2756

2757
2758

2759
2760
2761

2762
2763

2764
2765
2766

2767
2768

2769
2770
2771

2772
2773

2774
2775
2776

2777
2778
2779

2780
2781
2782



2783
2784
2785
2786
2787




2788
2789
2790
2791
2792
2793




2794
2795
2796
2797
2798
2799




2800
2801
2802
2803
2804
2805




2806
2807
2808
2809
2810
2811




2812
2813
2814
2815
2816
2817




2818
2819
2820
2821
2822
2823



2824
2825
2826
2827
2828



2829
2830
2831
2832
2833




2834
2835
2836
2837
2838
2839




2840
2841
2842
2843
2844
2845




2846
2847
2848
2849
2850
2851




2852
2853
2854
2855
2856
2857




2858
2859
2860
2861
2862
2863




2864
2865
2866
2867
2868
2869




2870
2871
2872
2873
2874
2875




2876
2877
2878
2879
2880
2881




2882
2883
2884
2885
2886
2887




2888
2889
2890
2891
2892
2893




2894
2895
2896
2897
2898
2899




2900
2901
2902
2903
2904
2905




2906
2907
2908
2909
2910
2911




2912
2913
2914
2915
2916
2917




2918
2919
2920
2921
2922
2923




2924
2925
2926
2927
2928
2929




2930
2931
2932
2933
2934
2935



2936
2937
2938
2939
2940




2941
2942
2943
2944
2945
2946




2947
2948
2949
2950
2951
2952

2953
2954

2955
2956
2957
2958

2959
2960

2961
2962
2963
2964

2965
2966

2967
2968
2969

2970
2971

2972
2973
2974
2975

2976
2977

2978
2979
2980
2981

2982
2983

2984
2985
2986
2987

2988
2989

2990
2991
2992




2993
2994
2995
2996
2997
2998




2999
3000
3001
3002
3003
3004




3005
3006
3007
3008
3009
3010

3011
3012


3013
3014
3015


3016
3017
3018
3019



3020
3021
3022
3023
3024



3025
3026
3027
3028
3029



3030
3031
3032
3033
3034



3035
3036
3037
3038
3039




3040
3041
3042
3043
3044
3045




3046
3047
3048
3049
3050
3051




3052
3053
3054
3055
3056
3057




3058
3059
3060
3061
3062
3063



3064
3065
3066
3067
3068




3069
3070
3071
3072
3073
3074




3075
3076
3077
3078
3079
3080
3081
3082




3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093







-
-
-
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
-
+
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
+
+
+


-
-
-
+
+
+


-
-
-
+
+
+


-
-
-
+
+
+


-
-
-
-
+
+
+
+




-
+

-
+


-
-
-
+
+
+


-
+

-
+


-
+

-
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
+
+
+


-
-
-
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
+
+
+


-
-
-
+
+
+


-
-
-
+
+
+


-
-
-
+
+
+


-
+

-
+


-
-
-
+
+
+


-
-
-
+
+
+


-
-
-
+
+
+


-
+

-
+


-
+

-
+


-
-
-
+
+
+


-
-
-
+
+
+


-
+

-
+



-
-
-
+
+
+



-
-
-
+
+
+


-
-
-
+
+
+


-
+

-
+


-
-
-
+
+
+


-
-
-
+
+
+


-
-
-
+
+
+


-
-
-
+
+
+



-
-
-
+
+
+


-
+

-
+



-
+

-
+


-
+

-
+


-
+

-
+


-
+

-
+


-
+

-
+


-
+

-
+


-
+

-
+


-
+

-
+


-
+


-
+


-
-
-
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
+
+
+


-
-
-
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
+

-
+



-
+

-
+



-
+

-
+


-
+

-
+



-
+

-
+



-
+

-
+



-
+

-
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
+

-
-
+
+

-
-
+
+


-
-
-
+
+
+


-
-
-
+
+
+


-
-
-
+
+
+


-
-
-
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+


-
-
-
+
+
+


-
-
-
-
+
+
+
+


-
-
-
-
+
+
+
+




-
-
-
-
+
+
+
+







  **     { ... }           // User supplied code
  **  #line <lineno> <thisfile>
  **     break;
  */
/********** Begin reduce actions **********************************************/
        YYMINORTYPE yylhsminor;
      case 0: /* document ::= statement_list */
#line 549 "pikchr.y"
{pik_render(p,yymsp[0].minor.yy235);}
#line 2451 "pikchr.c"
#line 563 "pikchr.y"
{pik_render(p,yymsp[0].minor.yy23);}
#line 2479 "pikchr.c"
        break;
      case 1: /* statement_list ::= statement */
#line 552 "pikchr.y"
{ yylhsminor.yy235 = pik_elist_append(p,0,yymsp[0].minor.yy162); }
#line 2456 "pikchr.c"
  yymsp[0].minor.yy235 = yylhsminor.yy235;
#line 566 "pikchr.y"
{ yylhsminor.yy23 = pik_elist_append(p,0,yymsp[0].minor.yy54); }
#line 2484 "pikchr.c"
  yymsp[0].minor.yy23 = yylhsminor.yy23;
        break;
      case 2: /* statement_list ::= statement_list EOL statement */
#line 554 "pikchr.y"
{ yylhsminor.yy235 = pik_elist_append(p,yymsp[-2].minor.yy235,yymsp[0].minor.yy162); }
#line 2462 "pikchr.c"
  yymsp[-2].minor.yy235 = yylhsminor.yy235;
#line 568 "pikchr.y"
{ yylhsminor.yy23 = pik_elist_append(p,yymsp[-2].minor.yy23,yymsp[0].minor.yy54); }
#line 2490 "pikchr.c"
  yymsp[-2].minor.yy23 = yylhsminor.yy23;
        break;
      case 3: /* statement ::= */
#line 557 "pikchr.y"
{ yymsp[1].minor.yy162 = 0; }
#line 2468 "pikchr.c"
#line 571 "pikchr.y"
{ yymsp[1].minor.yy54 = 0; }
#line 2496 "pikchr.c"
        break;
      case 4: /* statement ::= direction */
#line 558 "pikchr.y"
{ pik_set_direction(p,yymsp[0].minor.yy0.eCode);  yylhsminor.yy162=0; }
#line 2473 "pikchr.c"
  yymsp[0].minor.yy162 = yylhsminor.yy162;
#line 572 "pikchr.y"
{ pik_set_direction(p,yymsp[0].minor.yy0.eCode);  yylhsminor.yy54=0; }
#line 2501 "pikchr.c"
  yymsp[0].minor.yy54 = yylhsminor.yy54;
        break;
      case 5: /* statement ::= lvalue ASSIGN rvalue */
#line 559 "pikchr.y"
{pik_set_var(p,&yymsp[-2].minor.yy0,yymsp[0].minor.yy21,&yymsp[-1].minor.yy0); yylhsminor.yy162=0;}
#line 2479 "pikchr.c"
  yymsp[-2].minor.yy162 = yylhsminor.yy162;
#line 573 "pikchr.y"
{pik_set_var(p,&yymsp[-2].minor.yy0,yymsp[0].minor.yy129,&yymsp[-1].minor.yy0); yylhsminor.yy54=0;}
#line 2507 "pikchr.c"
  yymsp[-2].minor.yy54 = yylhsminor.yy54;
        break;
      case 6: /* statement ::= PLACENAME COLON unnamed_statement */
#line 561 "pikchr.y"
{ yylhsminor.yy162 = yymsp[0].minor.yy162;  pik_elem_setname(p,yymsp[0].minor.yy162,&yymsp[-2].minor.yy0); }
#line 2485 "pikchr.c"
  yymsp[-2].minor.yy162 = yylhsminor.yy162;
#line 575 "pikchr.y"
{ yylhsminor.yy54 = yymsp[0].minor.yy54;  pik_elem_setname(p,yymsp[0].minor.yy54,&yymsp[-2].minor.yy0); }
#line 2513 "pikchr.c"
  yymsp[-2].minor.yy54 = yylhsminor.yy54;
        break;
      case 7: /* statement ::= PLACENAME COLON position */
#line 563 "pikchr.y"
{ yylhsminor.yy162 = pik_elem_new(p,0,0,0);
                 if(yylhsminor.yy162){ yylhsminor.yy162->ptAt = yymsp[0].minor.yy63; pik_elem_setname(p,yylhsminor.yy162,&yymsp[-2].minor.yy0); }}
#line 2492 "pikchr.c"
  yymsp[-2].minor.yy162 = yylhsminor.yy162;
#line 577 "pikchr.y"
{ yylhsminor.yy54 = pik_elem_new(p,0,0,0);
                 if(yylhsminor.yy54){ yylhsminor.yy54->ptAt = yymsp[0].minor.yy187; pik_elem_setname(p,yylhsminor.yy54,&yymsp[-2].minor.yy0); }}
#line 2520 "pikchr.c"
  yymsp[-2].minor.yy54 = yylhsminor.yy54;
        break;
      case 8: /* statement ::= unnamed_statement */
#line 565 "pikchr.y"
{yylhsminor.yy162 = yymsp[0].minor.yy162;}
#line 2498 "pikchr.c"
  yymsp[0].minor.yy162 = yylhsminor.yy162;
#line 579 "pikchr.y"
{yylhsminor.yy54 = yymsp[0].minor.yy54;}
#line 2526 "pikchr.c"
  yymsp[0].minor.yy54 = yylhsminor.yy54;
        break;
      case 9: /* statement ::= print prlist */
#line 566 "pikchr.y"
{pik_append(p,"<br>\n",5); yymsp[-1].minor.yy162=0;}
#line 2504 "pikchr.c"
#line 580 "pikchr.y"
{pik_append(p,"<br>\n",5); yymsp[-1].minor.yy54=0;}
#line 2532 "pikchr.c"
        break;
      case 10: /* statement ::= ASSERT LP expr EQ expr RP */
#line 571 "pikchr.y"
{yymsp[-5].minor.yy162=pik_assert(p,yymsp[-3].minor.yy21,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy21);}
#line 2509 "pikchr.c"
#line 585 "pikchr.y"
{yymsp[-5].minor.yy54=pik_assert(p,yymsp[-3].minor.yy129,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy129);}
#line 2537 "pikchr.c"
        break;
      case 11: /* statement ::= ASSERT LP position EQ position RP */
#line 573 "pikchr.y"
{yymsp[-5].minor.yy162=pik_position_assert(p,&yymsp[-3].minor.yy63,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy63);}
#line 2514 "pikchr.c"
#line 587 "pikchr.y"
{yymsp[-5].minor.yy54=pik_position_assert(p,&yymsp[-3].minor.yy187,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy187);}
#line 2542 "pikchr.c"
        break;
      case 12: /* statement ::= DEFINE ID CODEBLOCK */
#line 574 "pikchr.y"
{yymsp[-2].minor.yy162=0; pik_add_macro(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);}
#line 2519 "pikchr.c"
#line 588 "pikchr.y"
{yymsp[-2].minor.yy54=0; pik_add_macro(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);}
#line 2547 "pikchr.c"
        break;
      case 13: /* rvalue ::= PLACENAME */
#line 585 "pikchr.y"
{yylhsminor.yy21 = pik_lookup_color(p,&yymsp[0].minor.yy0);}
#line 2524 "pikchr.c"
  yymsp[0].minor.yy21 = yylhsminor.yy21;
#line 599 "pikchr.y"
{yylhsminor.yy129 = pik_lookup_color(p,&yymsp[0].minor.yy0);}
#line 2552 "pikchr.c"
  yymsp[0].minor.yy129 = yylhsminor.yy129;
        break;
      case 14: /* pritem ::= FILL */
      case 15: /* pritem ::= COLOR */ yytestcase(yyruleno==15);
      case 16: /* pritem ::= THICKNESS */ yytestcase(yyruleno==16);
#line 590 "pikchr.y"
#line 604 "pikchr.y"
{pik_append_num(p,"",pik_value(p,yymsp[0].minor.yy0.z,yymsp[0].minor.yy0.n,0));}
#line 2532 "pikchr.c"
#line 2560 "pikchr.c"
        break;
      case 17: /* pritem ::= rvalue */
#line 593 "pikchr.y"
{pik_append_num(p,"",yymsp[0].minor.yy21);}
#line 2537 "pikchr.c"
#line 607 "pikchr.y"
{pik_append_num(p,"",yymsp[0].minor.yy129);}
#line 2565 "pikchr.c"
        break;
      case 18: /* pritem ::= STRING */
#line 594 "pikchr.y"
#line 608 "pikchr.y"
{pik_append_text(p,yymsp[0].minor.yy0.z+1,yymsp[0].minor.yy0.n-2,0);}
#line 2542 "pikchr.c"
#line 2570 "pikchr.c"
        break;
      case 19: /* prsep ::= COMMA */
#line 595 "pikchr.y"
#line 609 "pikchr.y"
{pik_append(p, " ", 1);}
#line 2547 "pikchr.c"
#line 2575 "pikchr.c"
        break;
      case 20: /* unnamed_statement ::= basetype attribute_list */
#line 598 "pikchr.y"
{yylhsminor.yy162 = yymsp[-1].minor.yy162; pik_after_adding_attributes(p,yylhsminor.yy162);}
#line 2552 "pikchr.c"
  yymsp[-1].minor.yy162 = yylhsminor.yy162;
#line 614 "pikchr.y"
{yylhsminor.yy54 = yymsp[-1].minor.yy54; pik_after_adding_attributes(p,yylhsminor.yy54);}
#line 2580 "pikchr.c"
  yymsp[-1].minor.yy54 = yylhsminor.yy54;
        break;
      case 21: /* basetype ::= CLASSNAME */
#line 600 "pikchr.y"
{yylhsminor.yy162 = pik_elem_new(p,&yymsp[0].minor.yy0,0,0); }
#line 2558 "pikchr.c"
  yymsp[0].minor.yy162 = yylhsminor.yy162;
#line 616 "pikchr.y"
{yylhsminor.yy54 = pik_elem_new(p,&yymsp[0].minor.yy0,0,0); }
#line 2586 "pikchr.c"
  yymsp[0].minor.yy54 = yylhsminor.yy54;
        break;
      case 22: /* basetype ::= STRING textposition */
#line 602 "pikchr.y"
{yymsp[-1].minor.yy0.eCode = yymsp[0].minor.yy188; yylhsminor.yy162 = pik_elem_new(p,0,&yymsp[-1].minor.yy0,0); }
#line 2564 "pikchr.c"
  yymsp[-1].minor.yy162 = yylhsminor.yy162;
#line 618 "pikchr.y"
{yymsp[-1].minor.yy0.eCode = yymsp[0].minor.yy272; yylhsminor.yy54 = pik_elem_new(p,0,&yymsp[-1].minor.yy0,0); }
#line 2592 "pikchr.c"
  yymsp[-1].minor.yy54 = yylhsminor.yy54;
        break;
      case 23: /* basetype ::= LB savelist statement_list RB */
#line 604 "pikchr.y"
{ p->list = yymsp[-2].minor.yy235; yymsp[-3].minor.yy162 = pik_elem_new(p,0,0,yymsp[-1].minor.yy235); if(yymsp[-3].minor.yy162) yymsp[-3].minor.yy162->errTok = yymsp[0].minor.yy0; }
#line 2570 "pikchr.c"
#line 620 "pikchr.y"
{ p->list = yymsp[-2].minor.yy23; yymsp[-3].minor.yy54 = pik_elem_new(p,0,0,yymsp[-1].minor.yy23); if(yymsp[-3].minor.yy54) yymsp[-3].minor.yy54->errTok = yymsp[0].minor.yy0; }
#line 2598 "pikchr.c"
        break;
      case 24: /* savelist ::= */
#line 609 "pikchr.y"
{yymsp[1].minor.yy235 = p->list; p->list = 0;}
#line 2575 "pikchr.c"
#line 625 "pikchr.y"
{yymsp[1].minor.yy23 = p->list; p->list = 0;}
#line 2603 "pikchr.c"
        break;
      case 25: /* relexpr ::= expr */
#line 616 "pikchr.y"
{yylhsminor.yy72.rAbs = yymsp[0].minor.yy21; yylhsminor.yy72.rRel = 0;}
#line 2580 "pikchr.c"
  yymsp[0].minor.yy72 = yylhsminor.yy72;
#line 632 "pikchr.y"
{yylhsminor.yy28.rAbs = yymsp[0].minor.yy129; yylhsminor.yy28.rRel = 0;}
#line 2608 "pikchr.c"
  yymsp[0].minor.yy28 = yylhsminor.yy28;
        break;
      case 26: /* relexpr ::= expr PERCENT */
#line 617 "pikchr.y"
{yylhsminor.yy72.rAbs = 0; yylhsminor.yy72.rRel = yymsp[-1].minor.yy21/100;}
#line 2586 "pikchr.c"
  yymsp[-1].minor.yy72 = yylhsminor.yy72;
#line 633 "pikchr.y"
{yylhsminor.yy28.rAbs = 0; yylhsminor.yy28.rRel = yymsp[-1].minor.yy129/100;}
#line 2614 "pikchr.c"
  yymsp[-1].minor.yy28 = yylhsminor.yy28;
        break;
      case 27: /* optrelexpr ::= */
#line 619 "pikchr.y"
{yymsp[1].minor.yy72.rAbs = 0; yymsp[1].minor.yy72.rRel = 1.0;}
#line 2592 "pikchr.c"
#line 635 "pikchr.y"
{yymsp[1].minor.yy28.rAbs = 0; yymsp[1].minor.yy28.rRel = 1.0;}
#line 2620 "pikchr.c"
        break;
      case 28: /* attribute_list ::= relexpr alist */
#line 621 "pikchr.y"
{pik_add_direction(p,0,&yymsp[-1].minor.yy72);}
#line 2597 "pikchr.c"
#line 637 "pikchr.y"
{pik_add_direction(p,0,&yymsp[-1].minor.yy28);}
#line 2625 "pikchr.c"
        break;
      case 29: /* attribute ::= numproperty relexpr */
#line 625 "pikchr.y"
{ pik_set_numprop(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy72); }
#line 2602 "pikchr.c"
#line 641 "pikchr.y"
{ pik_set_numprop(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy28); }
#line 2630 "pikchr.c"
        break;
      case 30: /* attribute ::= dashproperty expr */
#line 626 "pikchr.y"
{ pik_set_dashed(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy21); }
#line 2607 "pikchr.c"
#line 642 "pikchr.y"
{ pik_set_dashed(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy129); }
#line 2635 "pikchr.c"
        break;
      case 31: /* attribute ::= dashproperty */
#line 627 "pikchr.y"
#line 643 "pikchr.y"
{ pik_set_dashed(p,&yymsp[0].minor.yy0,0);  }
#line 2612 "pikchr.c"
#line 2640 "pikchr.c"
        break;
      case 32: /* attribute ::= colorproperty rvalue */
#line 628 "pikchr.y"
{ pik_set_clrprop(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy21); }
#line 2617 "pikchr.c"
#line 644 "pikchr.y"
{ pik_set_clrprop(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy129); }
#line 2645 "pikchr.c"
        break;
      case 33: /* attribute ::= go direction optrelexpr */
#line 629 "pikchr.y"
{ pik_add_direction(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy72);}
#line 2622 "pikchr.c"
#line 645 "pikchr.y"
{ pik_add_direction(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy28);}
#line 2650 "pikchr.c"
        break;
      case 34: /* attribute ::= go direction even position */
#line 630 "pikchr.y"
{pik_evenwith(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy63);}
#line 2627 "pikchr.c"
#line 646 "pikchr.y"
{pik_evenwith(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy187);}
#line 2655 "pikchr.c"
        break;
      case 35: /* attribute ::= CLOSE */
#line 631 "pikchr.y"
#line 647 "pikchr.y"
{ pik_close_path(p,&yymsp[0].minor.yy0); }
#line 2632 "pikchr.c"
#line 2660 "pikchr.c"
        break;
      case 36: /* attribute ::= CHOP */
#line 632 "pikchr.y"
#line 648 "pikchr.y"
{ p->cur->bChop = 1; }
#line 2637 "pikchr.c"
#line 2665 "pikchr.c"
        break;
      case 37: /* attribute ::= FROM position */
#line 633 "pikchr.y"
{ pik_set_from(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy63); }
#line 2642 "pikchr.c"
#line 649 "pikchr.y"
{ pik_set_from(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy187); }
#line 2670 "pikchr.c"
        break;
      case 38: /* attribute ::= TO position */
#line 634 "pikchr.y"
{ pik_add_to(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy63); }
#line 2647 "pikchr.c"
#line 650 "pikchr.y"
{ pik_add_to(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy187); }
#line 2675 "pikchr.c"
        break;
      case 39: /* attribute ::= THEN */
#line 635 "pikchr.y"
#line 651 "pikchr.y"
{ pik_then(p, &yymsp[0].minor.yy0, p->cur); }
#line 2652 "pikchr.c"
#line 2680 "pikchr.c"
        break;
      case 40: /* attribute ::= THEN optrelexpr HEADING expr */
      case 42: /* attribute ::= GO optrelexpr HEADING expr */ yytestcase(yyruleno==42);
#line 637 "pikchr.y"
{pik_move_hdg(p,&yymsp[-2].minor.yy72,&yymsp[-1].minor.yy0,yymsp[0].minor.yy21,0,&yymsp[-3].minor.yy0);}
#line 2658 "pikchr.c"
#line 653 "pikchr.y"
{pik_move_hdg(p,&yymsp[-2].minor.yy28,&yymsp[-1].minor.yy0,yymsp[0].minor.yy129,0,&yymsp[-3].minor.yy0);}
#line 2686 "pikchr.c"
        break;
      case 41: /* attribute ::= THEN optrelexpr EDGEPT */
      case 43: /* attribute ::= GO optrelexpr EDGEPT */ yytestcase(yyruleno==43);
#line 638 "pikchr.y"
{pik_move_hdg(p,&yymsp[-1].minor.yy72,0,0,&yymsp[0].minor.yy0,&yymsp[-2].minor.yy0);}
#line 2664 "pikchr.c"
#line 654 "pikchr.y"
{pik_move_hdg(p,&yymsp[-1].minor.yy28,0,0,&yymsp[0].minor.yy0,&yymsp[-2].minor.yy0);}
#line 2692 "pikchr.c"
        break;
      case 44: /* attribute ::= AT position */
#line 643 "pikchr.y"
{ pik_set_at(p,0,&yymsp[0].minor.yy63,&yymsp[-1].minor.yy0); }
#line 2669 "pikchr.c"
#line 659 "pikchr.y"
{ pik_set_at(p,0,&yymsp[0].minor.yy187,&yymsp[-1].minor.yy0); }
#line 2697 "pikchr.c"
        break;
      case 45: /* attribute ::= SAME */
#line 645 "pikchr.y"
#line 661 "pikchr.y"
{pik_same(p,0,&yymsp[0].minor.yy0);}
#line 2674 "pikchr.c"
#line 2702 "pikchr.c"
        break;
      case 46: /* attribute ::= SAME AS object */
#line 646 "pikchr.y"
{pik_same(p,yymsp[0].minor.yy162,&yymsp[-2].minor.yy0);}
#line 2679 "pikchr.c"
#line 662 "pikchr.y"
{pik_same(p,yymsp[0].minor.yy54,&yymsp[-2].minor.yy0);}
#line 2707 "pikchr.c"
        break;
      case 47: /* attribute ::= STRING textposition */
#line 647 "pikchr.y"
{pik_add_txt(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy188);}
#line 2684 "pikchr.c"
#line 663 "pikchr.y"
{pik_add_txt(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy272);}
#line 2712 "pikchr.c"
        break;
      case 48: /* attribute ::= FIT */
#line 648 "pikchr.y"
{pik_size_to_fit(p,&yymsp[0].minor.yy0,3); }
#line 2689 "pikchr.c"
#line 664 "pikchr.y"
{pik_size_to_fit(p,0,&yymsp[0].minor.yy0,3); }
#line 2717 "pikchr.c"
        break;
      case 49: /* attribute ::= BEHIND object */
#line 649 "pikchr.y"
{pik_behind(p,yymsp[0].minor.yy162);}
#line 2694 "pikchr.c"
#line 665 "pikchr.y"
{pik_behind(p,yymsp[0].minor.yy54);}
#line 2722 "pikchr.c"
        break;
      case 50: /* withclause ::= DOT_E edge AT position */
      case 51: /* withclause ::= edge AT position */ yytestcase(yyruleno==51);
#line 657 "pikchr.y"
{ pik_set_at(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy63,&yymsp[-1].minor.yy0); }
#line 2700 "pikchr.c"
#line 673 "pikchr.y"
{ pik_set_at(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy187,&yymsp[-1].minor.yy0); }
#line 2728 "pikchr.c"
        break;
      case 52: /* numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */
#line 661 "pikchr.y"
#line 677 "pikchr.y"
{yylhsminor.yy0 = yymsp[0].minor.yy0;}
#line 2705 "pikchr.c"
#line 2733 "pikchr.c"
  yymsp[0].minor.yy0 = yylhsminor.yy0;
        break;
      case 53: /* boolproperty ::= CW */
#line 672 "pikchr.y"
#line 688 "pikchr.y"
{p->cur->cw = 1;}
#line 2711 "pikchr.c"
#line 2739 "pikchr.c"
        break;
      case 54: /* boolproperty ::= CCW */
#line 673 "pikchr.y"
#line 689 "pikchr.y"
{p->cur->cw = 0;}
#line 2716 "pikchr.c"
#line 2744 "pikchr.c"
        break;
      case 55: /* boolproperty ::= LARROW */
#line 674 "pikchr.y"
#line 690 "pikchr.y"
{p->cur->larrow=1; p->cur->rarrow=0; }
#line 2721 "pikchr.c"
#line 2749 "pikchr.c"
        break;
      case 56: /* boolproperty ::= RARROW */
#line 675 "pikchr.y"
#line 691 "pikchr.y"
{p->cur->larrow=0; p->cur->rarrow=1; }
#line 2726 "pikchr.c"
#line 2754 "pikchr.c"
        break;
      case 57: /* boolproperty ::= LRARROW */
#line 676 "pikchr.y"
#line 692 "pikchr.y"
{p->cur->larrow=1; p->cur->rarrow=1; }
#line 2731 "pikchr.c"
#line 2759 "pikchr.c"
        break;
      case 58: /* boolproperty ::= INVIS */
#line 677 "pikchr.y"
#line 693 "pikchr.y"
{p->cur->sw = -0.00001;}
#line 2736 "pikchr.c"
#line 2764 "pikchr.c"
        break;
      case 59: /* boolproperty ::= THICK */
#line 678 "pikchr.y"
#line 694 "pikchr.y"
{p->cur->sw *= 1.5;}
#line 2741 "pikchr.c"
#line 2769 "pikchr.c"
        break;
      case 60: /* boolproperty ::= THIN */
#line 679 "pikchr.y"
#line 695 "pikchr.y"
{p->cur->sw *= 0.67;}
#line 2746 "pikchr.c"
#line 2774 "pikchr.c"
        break;
      case 61: /* boolproperty ::= SOLID */
#line 680 "pikchr.y"
#line 696 "pikchr.y"
{p->cur->sw = pik_value(p,"thickness",9,0);
                               p->cur->dotted = p->cur->dashed = 0.0;}
#line 2752 "pikchr.c"
#line 2780 "pikchr.c"
        break;
      case 62: /* textposition ::= */
#line 683 "pikchr.y"
{yymsp[1].minor.yy188 = 0;}
#line 2757 "pikchr.c"
#line 699 "pikchr.y"
{yymsp[1].minor.yy272 = 0;}
#line 2785 "pikchr.c"
        break;
      case 63: /* textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|MONO|ALIGNED|BIG|SMALL */
#line 686 "pikchr.y"
{yylhsminor.yy188 = (short int)pik_text_position(yymsp[-1].minor.yy188,&yymsp[0].minor.yy0);}
#line 2762 "pikchr.c"
  yymsp[-1].minor.yy188 = yylhsminor.yy188;
#line 702 "pikchr.y"
{yylhsminor.yy272 = (short int)pik_text_position(yymsp[-1].minor.yy272,&yymsp[0].minor.yy0);}
#line 2790 "pikchr.c"
  yymsp[-1].minor.yy272 = yylhsminor.yy272;
        break;
      case 64: /* position ::= expr COMMA expr */
#line 689 "pikchr.y"
{yylhsminor.yy63.x=yymsp[-2].minor.yy21; yylhsminor.yy63.y=yymsp[0].minor.yy21;}
#line 2768 "pikchr.c"
  yymsp[-2].minor.yy63 = yylhsminor.yy63;
#line 705 "pikchr.y"
{yylhsminor.yy187.x=yymsp[-2].minor.yy129; yylhsminor.yy187.y=yymsp[0].minor.yy129;}
#line 2796 "pikchr.c"
  yymsp[-2].minor.yy187 = yylhsminor.yy187;
        break;
      case 65: /* position ::= place PLUS expr COMMA expr */
#line 691 "pikchr.y"
{yylhsminor.yy63.x=yymsp[-4].minor.yy63.x+yymsp[-2].minor.yy21; yylhsminor.yy63.y=yymsp[-4].minor.yy63.y+yymsp[0].minor.yy21;}
#line 2774 "pikchr.c"
  yymsp[-4].minor.yy63 = yylhsminor.yy63;
#line 707 "pikchr.y"
{yylhsminor.yy187.x=yymsp[-4].minor.yy187.x+yymsp[-2].minor.yy129; yylhsminor.yy187.y=yymsp[-4].minor.yy187.y+yymsp[0].minor.yy129;}
#line 2802 "pikchr.c"
  yymsp[-4].minor.yy187 = yylhsminor.yy187;
        break;
      case 66: /* position ::= place MINUS expr COMMA expr */
#line 692 "pikchr.y"
{yylhsminor.yy63.x=yymsp[-4].minor.yy63.x-yymsp[-2].minor.yy21; yylhsminor.yy63.y=yymsp[-4].minor.yy63.y-yymsp[0].minor.yy21;}
#line 2780 "pikchr.c"
  yymsp[-4].minor.yy63 = yylhsminor.yy63;
#line 708 "pikchr.y"
{yylhsminor.yy187.x=yymsp[-4].minor.yy187.x-yymsp[-2].minor.yy129; yylhsminor.yy187.y=yymsp[-4].minor.yy187.y-yymsp[0].minor.yy129;}
#line 2808 "pikchr.c"
  yymsp[-4].minor.yy187 = yylhsminor.yy187;
        break;
      case 67: /* position ::= place PLUS LP expr COMMA expr RP */
#line 694 "pikchr.y"
{yylhsminor.yy63.x=yymsp[-6].minor.yy63.x+yymsp[-3].minor.yy21; yylhsminor.yy63.y=yymsp[-6].minor.yy63.y+yymsp[-1].minor.yy21;}
#line 2786 "pikchr.c"
  yymsp[-6].minor.yy63 = yylhsminor.yy63;
#line 710 "pikchr.y"
{yylhsminor.yy187.x=yymsp[-6].minor.yy187.x+yymsp[-3].minor.yy129; yylhsminor.yy187.y=yymsp[-6].minor.yy187.y+yymsp[-1].minor.yy129;}
#line 2814 "pikchr.c"
  yymsp[-6].minor.yy187 = yylhsminor.yy187;
        break;
      case 68: /* position ::= place MINUS LP expr COMMA expr RP */
#line 696 "pikchr.y"
{yylhsminor.yy63.x=yymsp[-6].minor.yy63.x-yymsp[-3].minor.yy21; yylhsminor.yy63.y=yymsp[-6].minor.yy63.y-yymsp[-1].minor.yy21;}
#line 2792 "pikchr.c"
  yymsp[-6].minor.yy63 = yylhsminor.yy63;
#line 712 "pikchr.y"
{yylhsminor.yy187.x=yymsp[-6].minor.yy187.x-yymsp[-3].minor.yy129; yylhsminor.yy187.y=yymsp[-6].minor.yy187.y-yymsp[-1].minor.yy129;}
#line 2820 "pikchr.c"
  yymsp[-6].minor.yy187 = yylhsminor.yy187;
        break;
      case 69: /* position ::= LP position COMMA position RP */
#line 697 "pikchr.y"
{yymsp[-4].minor.yy63.x=yymsp[-3].minor.yy63.x; yymsp[-4].minor.yy63.y=yymsp[-1].minor.yy63.y;}
#line 2798 "pikchr.c"
#line 713 "pikchr.y"
{yymsp[-4].minor.yy187.x=yymsp[-3].minor.yy187.x; yymsp[-4].minor.yy187.y=yymsp[-1].minor.yy187.y;}
#line 2826 "pikchr.c"
        break;
      case 70: /* position ::= LP position RP */
#line 698 "pikchr.y"
{yymsp[-2].minor.yy63=yymsp[-1].minor.yy63;}
#line 2803 "pikchr.c"
#line 714 "pikchr.y"
{yymsp[-2].minor.yy187=yymsp[-1].minor.yy187;}
#line 2831 "pikchr.c"
        break;
      case 71: /* position ::= expr between position AND position */
#line 700 "pikchr.y"
{yylhsminor.yy63 = pik_position_between(yymsp[-4].minor.yy21,yymsp[-2].minor.yy63,yymsp[0].minor.yy63);}
#line 2808 "pikchr.c"
  yymsp[-4].minor.yy63 = yylhsminor.yy63;
#line 716 "pikchr.y"
{yylhsminor.yy187 = pik_position_between(yymsp[-4].minor.yy129,yymsp[-2].minor.yy187,yymsp[0].minor.yy187);}
#line 2836 "pikchr.c"
  yymsp[-4].minor.yy187 = yylhsminor.yy187;
        break;
      case 72: /* position ::= expr LT position COMMA position GT */
#line 702 "pikchr.y"
{yylhsminor.yy63 = pik_position_between(yymsp[-5].minor.yy21,yymsp[-3].minor.yy63,yymsp[-1].minor.yy63);}
#line 2814 "pikchr.c"
  yymsp[-5].minor.yy63 = yylhsminor.yy63;
#line 718 "pikchr.y"
{yylhsminor.yy187 = pik_position_between(yymsp[-5].minor.yy129,yymsp[-3].minor.yy187,yymsp[-1].minor.yy187);}
#line 2842 "pikchr.c"
  yymsp[-5].minor.yy187 = yylhsminor.yy187;
        break;
      case 73: /* position ::= expr ABOVE position */
#line 703 "pikchr.y"
{yylhsminor.yy63=yymsp[0].minor.yy63; yylhsminor.yy63.y += yymsp[-2].minor.yy21;}
#line 2820 "pikchr.c"
  yymsp[-2].minor.yy63 = yylhsminor.yy63;
#line 719 "pikchr.y"
{yylhsminor.yy187=yymsp[0].minor.yy187; yylhsminor.yy187.y += yymsp[-2].minor.yy129;}
#line 2848 "pikchr.c"
  yymsp[-2].minor.yy187 = yylhsminor.yy187;
        break;
      case 74: /* position ::= expr BELOW position */
#line 704 "pikchr.y"
{yylhsminor.yy63=yymsp[0].minor.yy63; yylhsminor.yy63.y -= yymsp[-2].minor.yy21;}
#line 2826 "pikchr.c"
  yymsp[-2].minor.yy63 = yylhsminor.yy63;
#line 720 "pikchr.y"
{yylhsminor.yy187=yymsp[0].minor.yy187; yylhsminor.yy187.y -= yymsp[-2].minor.yy129;}
#line 2854 "pikchr.c"
  yymsp[-2].minor.yy187 = yylhsminor.yy187;
        break;
      case 75: /* position ::= expr LEFT OF position */
#line 705 "pikchr.y"
{yylhsminor.yy63=yymsp[0].minor.yy63; yylhsminor.yy63.x -= yymsp[-3].minor.yy21;}
#line 2832 "pikchr.c"
  yymsp[-3].minor.yy63 = yylhsminor.yy63;
#line 721 "pikchr.y"
{yylhsminor.yy187=yymsp[0].minor.yy187; yylhsminor.yy187.x -= yymsp[-3].minor.yy129;}
#line 2860 "pikchr.c"
  yymsp[-3].minor.yy187 = yylhsminor.yy187;
        break;
      case 76: /* position ::= expr RIGHT OF position */
#line 706 "pikchr.y"
{yylhsminor.yy63=yymsp[0].minor.yy63; yylhsminor.yy63.x += yymsp[-3].minor.yy21;}
#line 2838 "pikchr.c"
  yymsp[-3].minor.yy63 = yylhsminor.yy63;
#line 722 "pikchr.y"
{yylhsminor.yy187=yymsp[0].minor.yy187; yylhsminor.yy187.x += yymsp[-3].minor.yy129;}
#line 2866 "pikchr.c"
  yymsp[-3].minor.yy187 = yylhsminor.yy187;
        break;
      case 77: /* position ::= expr ON HEADING EDGEPT OF position */
#line 708 "pikchr.y"
{yylhsminor.yy63 = pik_position_at_hdg(yymsp[-5].minor.yy21,&yymsp[-2].minor.yy0,yymsp[0].minor.yy63);}
#line 2844 "pikchr.c"
  yymsp[-5].minor.yy63 = yylhsminor.yy63;
#line 724 "pikchr.y"
{yylhsminor.yy187 = pik_position_at_hdg(yymsp[-5].minor.yy129,&yymsp[-2].minor.yy0,yymsp[0].minor.yy187);}
#line 2872 "pikchr.c"
  yymsp[-5].minor.yy187 = yylhsminor.yy187;
        break;
      case 78: /* position ::= expr HEADING EDGEPT OF position */
#line 710 "pikchr.y"
{yylhsminor.yy63 = pik_position_at_hdg(yymsp[-4].minor.yy21,&yymsp[-2].minor.yy0,yymsp[0].minor.yy63);}
#line 2850 "pikchr.c"
  yymsp[-4].minor.yy63 = yylhsminor.yy63;
#line 726 "pikchr.y"
{yylhsminor.yy187 = pik_position_at_hdg(yymsp[-4].minor.yy129,&yymsp[-2].minor.yy0,yymsp[0].minor.yy187);}
#line 2878 "pikchr.c"
  yymsp[-4].minor.yy187 = yylhsminor.yy187;
        break;
      case 79: /* position ::= expr EDGEPT OF position */
#line 712 "pikchr.y"
{yylhsminor.yy63 = pik_position_at_hdg(yymsp[-3].minor.yy21,&yymsp[-2].minor.yy0,yymsp[0].minor.yy63);}
#line 2856 "pikchr.c"
  yymsp[-3].minor.yy63 = yylhsminor.yy63;
#line 728 "pikchr.y"
{yylhsminor.yy187 = pik_position_at_hdg(yymsp[-3].minor.yy129,&yymsp[-2].minor.yy0,yymsp[0].minor.yy187);}
#line 2884 "pikchr.c"
  yymsp[-3].minor.yy187 = yylhsminor.yy187;
        break;
      case 80: /* position ::= expr ON HEADING expr FROM position */
#line 714 "pikchr.y"
{yylhsminor.yy63 = pik_position_at_angle(yymsp[-5].minor.yy21,yymsp[-2].minor.yy21,yymsp[0].minor.yy63);}
#line 2862 "pikchr.c"
  yymsp[-5].minor.yy63 = yylhsminor.yy63;
#line 730 "pikchr.y"
{yylhsminor.yy187 = pik_position_at_angle(yymsp[-5].minor.yy129,yymsp[-2].minor.yy129,yymsp[0].minor.yy187);}
#line 2890 "pikchr.c"
  yymsp[-5].minor.yy187 = yylhsminor.yy187;
        break;
      case 81: /* position ::= expr HEADING expr FROM position */
#line 716 "pikchr.y"
{yylhsminor.yy63 = pik_position_at_angle(yymsp[-4].minor.yy21,yymsp[-2].minor.yy21,yymsp[0].minor.yy63);}
#line 2868 "pikchr.c"
  yymsp[-4].minor.yy63 = yylhsminor.yy63;
#line 732 "pikchr.y"
{yylhsminor.yy187 = pik_position_at_angle(yymsp[-4].minor.yy129,yymsp[-2].minor.yy129,yymsp[0].minor.yy187);}
#line 2896 "pikchr.c"
  yymsp[-4].minor.yy187 = yylhsminor.yy187;
        break;
      case 82: /* place ::= edge OF object */
#line 728 "pikchr.y"
{yylhsminor.yy63 = pik_place_of_elem(p,yymsp[0].minor.yy162,&yymsp[-2].minor.yy0);}
#line 2874 "pikchr.c"
  yymsp[-2].minor.yy63 = yylhsminor.yy63;
#line 744 "pikchr.y"
{yylhsminor.yy187 = pik_place_of_elem(p,yymsp[0].minor.yy54,&yymsp[-2].minor.yy0);}
#line 2902 "pikchr.c"
  yymsp[-2].minor.yy187 = yylhsminor.yy187;
        break;
      case 83: /* place2 ::= object */
#line 729 "pikchr.y"
{yylhsminor.yy63 = pik_place_of_elem(p,yymsp[0].minor.yy162,0);}
#line 2880 "pikchr.c"
  yymsp[0].minor.yy63 = yylhsminor.yy63;
#line 745 "pikchr.y"
{yylhsminor.yy187 = pik_place_of_elem(p,yymsp[0].minor.yy54,0);}
#line 2908 "pikchr.c"
  yymsp[0].minor.yy187 = yylhsminor.yy187;
        break;
      case 84: /* place2 ::= object DOT_E edge */
#line 730 "pikchr.y"
{yylhsminor.yy63 = pik_place_of_elem(p,yymsp[-2].minor.yy162,&yymsp[0].minor.yy0);}
#line 2886 "pikchr.c"
  yymsp[-2].minor.yy63 = yylhsminor.yy63;
#line 746 "pikchr.y"
{yylhsminor.yy187 = pik_place_of_elem(p,yymsp[-2].minor.yy54,&yymsp[0].minor.yy0);}
#line 2914 "pikchr.c"
  yymsp[-2].minor.yy187 = yylhsminor.yy187;
        break;
      case 85: /* place2 ::= NTH VERTEX OF object */
#line 731 "pikchr.y"
{yylhsminor.yy63 = pik_nth_vertex(p,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,yymsp[0].minor.yy162);}
#line 2892 "pikchr.c"
  yymsp[-3].minor.yy63 = yylhsminor.yy63;
#line 747 "pikchr.y"
{yylhsminor.yy187 = pik_nth_vertex(p,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,yymsp[0].minor.yy54);}
#line 2920 "pikchr.c"
  yymsp[-3].minor.yy187 = yylhsminor.yy187;
        break;
      case 86: /* object ::= nth */
#line 743 "pikchr.y"
{yylhsminor.yy162 = pik_find_nth(p,0,&yymsp[0].minor.yy0);}
#line 2898 "pikchr.c"
  yymsp[0].minor.yy162 = yylhsminor.yy162;
#line 759 "pikchr.y"
{yylhsminor.yy54 = pik_find_nth(p,0,&yymsp[0].minor.yy0);}
#line 2926 "pikchr.c"
  yymsp[0].minor.yy54 = yylhsminor.yy54;
        break;
      case 87: /* object ::= nth OF|IN object */
#line 744 "pikchr.y"
{yylhsminor.yy162 = pik_find_nth(p,yymsp[0].minor.yy162,&yymsp[-2].minor.yy0);}
#line 2904 "pikchr.c"
  yymsp[-2].minor.yy162 = yylhsminor.yy162;
#line 760 "pikchr.y"
{yylhsminor.yy54 = pik_find_nth(p,yymsp[0].minor.yy54,&yymsp[-2].minor.yy0);}
#line 2932 "pikchr.c"
  yymsp[-2].minor.yy54 = yylhsminor.yy54;
        break;
      case 88: /* objectname ::= THIS */
#line 746 "pikchr.y"
{yymsp[0].minor.yy162 = p->cur;}
#line 2910 "pikchr.c"
#line 762 "pikchr.y"
{yymsp[0].minor.yy54 = p->cur;}
#line 2938 "pikchr.c"
        break;
      case 89: /* objectname ::= PLACENAME */
#line 747 "pikchr.y"
{yylhsminor.yy162 = pik_find_byname(p,0,&yymsp[0].minor.yy0);}
#line 2915 "pikchr.c"
  yymsp[0].minor.yy162 = yylhsminor.yy162;
#line 763 "pikchr.y"
{yylhsminor.yy54 = pik_find_byname(p,0,&yymsp[0].minor.yy0);}
#line 2943 "pikchr.c"
  yymsp[0].minor.yy54 = yylhsminor.yy54;
        break;
      case 90: /* objectname ::= objectname DOT_U PLACENAME */
#line 749 "pikchr.y"
{yylhsminor.yy162 = pik_find_byname(p,yymsp[-2].minor.yy162,&yymsp[0].minor.yy0);}
#line 2921 "pikchr.c"
  yymsp[-2].minor.yy162 = yylhsminor.yy162;
#line 765 "pikchr.y"
{yylhsminor.yy54 = pik_find_byname(p,yymsp[-2].minor.yy54,&yymsp[0].minor.yy0);}
#line 2949 "pikchr.c"
  yymsp[-2].minor.yy54 = yylhsminor.yy54;
        break;
      case 91: /* nth ::= NTH CLASSNAME */
#line 751 "pikchr.y"
#line 767 "pikchr.y"
{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = pik_nth_value(p,&yymsp[-1].minor.yy0); }
#line 2927 "pikchr.c"
#line 2955 "pikchr.c"
  yymsp[-1].minor.yy0 = yylhsminor.yy0;
        break;
      case 92: /* nth ::= NTH LAST CLASSNAME */
#line 752 "pikchr.y"
#line 768 "pikchr.y"
{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = -pik_nth_value(p,&yymsp[-2].minor.yy0); }
#line 2933 "pikchr.c"
#line 2961 "pikchr.c"
  yymsp[-2].minor.yy0 = yylhsminor.yy0;
        break;
      case 93: /* nth ::= LAST CLASSNAME */
#line 753 "pikchr.y"
#line 769 "pikchr.y"
{yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.eCode = -1;}
#line 2939 "pikchr.c"
#line 2967 "pikchr.c"
        break;
      case 94: /* nth ::= LAST */
#line 754 "pikchr.y"
#line 770 "pikchr.y"
{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = -1;}
#line 2944 "pikchr.c"
#line 2972 "pikchr.c"
  yymsp[0].minor.yy0 = yylhsminor.yy0;
        break;
      case 95: /* nth ::= NTH LB RB */
#line 755 "pikchr.y"
#line 771 "pikchr.y"
{yylhsminor.yy0=yymsp[-1].minor.yy0; yylhsminor.yy0.eCode = pik_nth_value(p,&yymsp[-2].minor.yy0);}
#line 2950 "pikchr.c"
#line 2978 "pikchr.c"
  yymsp[-2].minor.yy0 = yylhsminor.yy0;
        break;
      case 96: /* nth ::= NTH LAST LB RB */
#line 756 "pikchr.y"
#line 772 "pikchr.y"
{yylhsminor.yy0=yymsp[-1].minor.yy0; yylhsminor.yy0.eCode = -pik_nth_value(p,&yymsp[-3].minor.yy0);}
#line 2956 "pikchr.c"
#line 2984 "pikchr.c"
  yymsp[-3].minor.yy0 = yylhsminor.yy0;
        break;
      case 97: /* nth ::= LAST LB RB */
#line 757 "pikchr.y"
#line 773 "pikchr.y"
{yymsp[-2].minor.yy0=yymsp[-1].minor.yy0; yymsp[-2].minor.yy0.eCode = -1; }
#line 2962 "pikchr.c"
#line 2990 "pikchr.c"
        break;
      case 98: /* expr ::= expr PLUS expr */
#line 759 "pikchr.y"
{yylhsminor.yy21=yymsp[-2].minor.yy21+yymsp[0].minor.yy21;}
#line 2967 "pikchr.c"
  yymsp[-2].minor.yy21 = yylhsminor.yy21;
#line 775 "pikchr.y"
{yylhsminor.yy129=yymsp[-2].minor.yy129+yymsp[0].minor.yy129;}
#line 2995 "pikchr.c"
  yymsp[-2].minor.yy129 = yylhsminor.yy129;
        break;
      case 99: /* expr ::= expr MINUS expr */
#line 760 "pikchr.y"
{yylhsminor.yy21=yymsp[-2].minor.yy21-yymsp[0].minor.yy21;}
#line 2973 "pikchr.c"
  yymsp[-2].minor.yy21 = yylhsminor.yy21;
#line 776 "pikchr.y"
{yylhsminor.yy129=yymsp[-2].minor.yy129-yymsp[0].minor.yy129;}
#line 3001 "pikchr.c"
  yymsp[-2].minor.yy129 = yylhsminor.yy129;
        break;
      case 100: /* expr ::= expr STAR expr */
#line 761 "pikchr.y"
{yylhsminor.yy21=yymsp[-2].minor.yy21*yymsp[0].minor.yy21;}
#line 2979 "pikchr.c"
  yymsp[-2].minor.yy21 = yylhsminor.yy21;
#line 777 "pikchr.y"
{yylhsminor.yy129=yymsp[-2].minor.yy129*yymsp[0].minor.yy129;}
#line 3007 "pikchr.c"
  yymsp[-2].minor.yy129 = yylhsminor.yy129;
        break;
      case 101: /* expr ::= expr SLASH expr */
#line 762 "pikchr.y"
#line 778 "pikchr.y"
{
  if( yymsp[0].minor.yy21==0.0 ){ pik_error(p, &yymsp[-1].minor.yy0, "division by zero"); yylhsminor.yy21 = 0.0; }
  else{ yylhsminor.yy21 = yymsp[-2].minor.yy21/yymsp[0].minor.yy21; }
  if( yymsp[0].minor.yy129==0.0 ){ pik_error(p, &yymsp[-1].minor.yy0, "division by zero"); yylhsminor.yy129 = 0.0; }
  else{ yylhsminor.yy129 = yymsp[-2].minor.yy129/yymsp[0].minor.yy129; }
}
#line 2988 "pikchr.c"
  yymsp[-2].minor.yy21 = yylhsminor.yy21;
#line 3016 "pikchr.c"
  yymsp[-2].minor.yy129 = yylhsminor.yy129;
        break;
      case 102: /* expr ::= MINUS expr */
#line 766 "pikchr.y"
{yymsp[-1].minor.yy21=-yymsp[0].minor.yy21;}
#line 2994 "pikchr.c"
#line 782 "pikchr.y"
{yymsp[-1].minor.yy129=-yymsp[0].minor.yy129;}
#line 3022 "pikchr.c"
        break;
      case 103: /* expr ::= PLUS expr */
#line 767 "pikchr.y"
{yymsp[-1].minor.yy21=yymsp[0].minor.yy21;}
#line 2999 "pikchr.c"
#line 783 "pikchr.y"
{yymsp[-1].minor.yy129=yymsp[0].minor.yy129;}
#line 3027 "pikchr.c"
        break;
      case 104: /* expr ::= LP expr RP */
#line 768 "pikchr.y"
{yymsp[-2].minor.yy21=yymsp[-1].minor.yy21;}
#line 3004 "pikchr.c"
#line 784 "pikchr.y"
{yymsp[-2].minor.yy129=yymsp[-1].minor.yy129;}
#line 3032 "pikchr.c"
        break;
      case 105: /* expr ::= LP FILL|COLOR|THICKNESS RP */
#line 769 "pikchr.y"
{yymsp[-2].minor.yy21=pik_get_var(p,&yymsp[-1].minor.yy0);}
#line 3009 "pikchr.c"
#line 785 "pikchr.y"
{yymsp[-2].minor.yy129=pik_get_var(p,&yymsp[-1].minor.yy0);}
#line 3037 "pikchr.c"
        break;
      case 106: /* expr ::= NUMBER */
#line 770 "pikchr.y"
{yylhsminor.yy21=pik_atof(&yymsp[0].minor.yy0);}
#line 3014 "pikchr.c"
  yymsp[0].minor.yy21 = yylhsminor.yy21;
#line 786 "pikchr.y"
{yylhsminor.yy129=pik_atof(&yymsp[0].minor.yy0);}
#line 3042 "pikchr.c"
  yymsp[0].minor.yy129 = yylhsminor.yy129;
        break;
      case 107: /* expr ::= ID */
#line 771 "pikchr.y"
{yylhsminor.yy21=pik_get_var(p,&yymsp[0].minor.yy0);}
#line 3020 "pikchr.c"
  yymsp[0].minor.yy21 = yylhsminor.yy21;
#line 787 "pikchr.y"
{yylhsminor.yy129=pik_get_var(p,&yymsp[0].minor.yy0);}
#line 3048 "pikchr.c"
  yymsp[0].minor.yy129 = yylhsminor.yy129;
        break;
      case 108: /* expr ::= FUNC1 LP expr RP */
#line 772 "pikchr.y"
{yylhsminor.yy21 = pik_func(p,&yymsp[-3].minor.yy0,yymsp[-1].minor.yy21,0.0);}
#line 3026 "pikchr.c"
  yymsp[-3].minor.yy21 = yylhsminor.yy21;
#line 788 "pikchr.y"
{yylhsminor.yy129 = pik_func(p,&yymsp[-3].minor.yy0,yymsp[-1].minor.yy129,0.0);}
#line 3054 "pikchr.c"
  yymsp[-3].minor.yy129 = yylhsminor.yy129;
        break;
      case 109: /* expr ::= FUNC2 LP expr COMMA expr RP */
#line 773 "pikchr.y"
{yylhsminor.yy21 = pik_func(p,&yymsp[-5].minor.yy0,yymsp[-3].minor.yy21,yymsp[-1].minor.yy21);}
#line 3032 "pikchr.c"
  yymsp[-5].minor.yy21 = yylhsminor.yy21;
#line 789 "pikchr.y"
{yylhsminor.yy129 = pik_func(p,&yymsp[-5].minor.yy0,yymsp[-3].minor.yy129,yymsp[-1].minor.yy129);}
#line 3060 "pikchr.c"
  yymsp[-5].minor.yy129 = yylhsminor.yy129;
        break;
      case 110: /* expr ::= DIST LP position COMMA position RP */
#line 774 "pikchr.y"
{yymsp[-5].minor.yy21 = pik_dist(&yymsp[-3].minor.yy63,&yymsp[-1].minor.yy63);}
#line 3038 "pikchr.c"
#line 790 "pikchr.y"
{yymsp[-5].minor.yy129 = pik_dist(&yymsp[-3].minor.yy187,&yymsp[-1].minor.yy187);}
#line 3066 "pikchr.c"
        break;
      case 111: /* expr ::= place2 DOT_XY X */
#line 775 "pikchr.y"
{yylhsminor.yy21 = yymsp[-2].minor.yy63.x;}
#line 3043 "pikchr.c"
  yymsp[-2].minor.yy21 = yylhsminor.yy21;
#line 791 "pikchr.y"
{yylhsminor.yy129 = yymsp[-2].minor.yy187.x;}
#line 3071 "pikchr.c"
  yymsp[-2].minor.yy129 = yylhsminor.yy129;
        break;
      case 112: /* expr ::= place2 DOT_XY Y */
#line 776 "pikchr.y"
{yylhsminor.yy21 = yymsp[-2].minor.yy63.y;}
#line 3049 "pikchr.c"
  yymsp[-2].minor.yy21 = yylhsminor.yy21;
#line 792 "pikchr.y"
{yylhsminor.yy129 = yymsp[-2].minor.yy187.y;}
#line 3077 "pikchr.c"
  yymsp[-2].minor.yy129 = yylhsminor.yy129;
        break;
      case 113: /* expr ::= object DOT_L numproperty */
      case 114: /* expr ::= object DOT_L dashproperty */ yytestcase(yyruleno==114);
      case 115: /* expr ::= object DOT_L colorproperty */ yytestcase(yyruleno==115);
#line 777 "pikchr.y"
{yylhsminor.yy21=pik_property_of(yymsp[-2].minor.yy162,&yymsp[0].minor.yy0);}
#line 3057 "pikchr.c"
  yymsp[-2].minor.yy21 = yylhsminor.yy21;
#line 793 "pikchr.y"
{yylhsminor.yy129=pik_property_of(yymsp[-2].minor.yy54,&yymsp[0].minor.yy0);}
#line 3085 "pikchr.c"
  yymsp[-2].minor.yy129 = yylhsminor.yy129;
        break;
      default:
      /* (116) lvalue ::= ID */ yytestcase(yyruleno==116);
      /* (117) lvalue ::= FILL */ yytestcase(yyruleno==117);
      /* (118) lvalue ::= COLOR */ yytestcase(yyruleno==118);
      /* (119) lvalue ::= THICKNESS */ yytestcase(yyruleno==119);
      /* (120) rvalue ::= expr */ yytestcase(yyruleno==120);
3128
3129
3130
3131
3132
3133
3134
3135

3136
3137
3138
3139
3140
3141
3142
3143

3144
3145
3146
3147
3148
3149
3150
3181
3182
3183
3184
3185
3186
3187

3188
3189
3190
3191
3192
3193
3194
3195

3196
3197
3198
3199
3200
3201
3202
3203







-
+







-
+







  int yymajor,                   /* The major type of the error token */
  pik_parserTOKENTYPE yyminor         /* The minor type of the error token */
){
  pik_parserARG_FETCH
  pik_parserCTX_FETCH
#define TOKEN yyminor
/************ Begin %syntax_error code ****************************************/
#line 537 "pikchr.y"
#line 551 "pikchr.y"

  if( TOKEN.z && TOKEN.z[0] ){
    pik_error(p, &TOKEN, "syntax error");
  }else{
    pik_error(p, 0, "syntax error");
  }
  UNUSED_PARAMETER(yymajor);
#line 3168 "pikchr.c"
#line 3196 "pikchr.c"
/************ End %syntax_error code ******************************************/
  pik_parserARG_STORE /* Suppress warning about unused %extra_argument variable */
  pik_parserCTX_STORE
}

/*
** The following is executed when the parser accepts
3405
3406
3407
3408
3409
3410
3411
3412

3413
3414
3415
3416
3417
3418
3419
3458
3459
3460
3461
3462
3463
3464

3465
3466
3467
3468
3469
3470
3471
3472







-
+







  assert( iToken<(int)(sizeof(yyFallback)/sizeof(yyFallback[0])) );
  return yyFallback[iToken];
#else
  (void)iToken;
  return 0;
#endif
}
#line 782 "pikchr.y"
#line 798 "pikchr.y"



/* Chart of the 148 official CSS color names with their
** corresponding RGB values thru Color Module Level 4:
** https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
**
3632
3633
3634
3635
3636
3637
3638
3639

3640
3641
3642
3643
3644
3645
3646
3647
3648


3649
3650
3651


3652
3653
3654
3655
3656



3657
3658
3659
3660


3661
3662













3663
3664
3665
3666
3667
3668
3669
3670

3671
3672
3673
3674
3675
3676
3677
3685
3686
3687
3688
3689
3690
3691

3692
3693
3694
3695
3696
3697
3698
3699


3700
3701
3702


3703
3704
3705
3706
3707
3708

3709
3710
3711
3712
3713
3714
3715
3716
3717


3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737

3738
3739
3740
3741
3742
3743
3744
3745







-
+







-
-
+
+

-
-
+
+




-
+
+
+




+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+







-
+







}
/* Hack: Arcs are here rendered as quadratic Bezier curves rather
** than true arcs.  Multiple reasons: (1) the legacy-PIC parameters
** that control arcs are obscure and I could not figure out what they
** mean based on available documentation.  (2) Arcs are rarely used,
** and so do not seem that important.
*/
static PPoint arcControlPoint(int cw, PPoint f, PPoint t, PNum rScale){
static PPoint arcControlPoint(int cw, PPoint f, PPoint t){
  PPoint m;
  PNum dx, dy;
  m.x = 0.5*(f.x+t.x);
  m.y = 0.5*(f.y+t.y);
  dx = t.x - f.x;
  dy = t.y - f.y;
  if( cw ){
    m.x -= 0.5*rScale*dy;
    m.y += 0.5*rScale*dx;
    m.x -= 0.5*dy;
    m.y += 0.5*dx;
  }else{
    m.x += 0.5*rScale*dy;
    m.y -= 0.5*rScale*dx;
    m.x += 0.5*dy;
    m.y -= 0.5*dx;
  }
  return m;
}
static void arcCheck(Pik *p, PObj *pObj){
  PPoint m;
  PPoint f, m, t;
  PNum sw;
  int i;
  if( p->nTPath>2 ){
    pik_error(p, &pObj->errTok, "arc geometry error");
    return;
  }
  f = p->aTPath[0];
  t = p->aTPath[1];
  m = arcControlPoint(pObj->cw, p->aTPath[0], p->aTPath[1], 0.5);
  pik_bbox_add_xy(&pObj->bbox, m.x, m.y);
  m = arcControlPoint(pObj->cw, f, t);
  sw = pObj->sw;
  for(i=1; i<16; i++){
    PNum t1, t2, a, b, c, x, y;
    t1 = 0.0625*i;
    t2 = 1.0 - t1;
    a = t2*t2;
    b = 2*t1*t2;
    c = t1*t1;
    x = a*f.x + b*m.x + c*t.x;
    y = a*f.y + b*m.y + c*t.y;
    pik_bbox_addellipse(&pObj->bbox, x, y, sw, sw);
  }
}
static void arcRender(Pik *p, PObj *pObj){
  PPoint f, m, t;
  if( pObj->nPath<2 ) return;
  if( pObj->sw<0.0 ) return;
  f = pObj->aPath[0];
  t = pObj->aPath[1];
  m = arcControlPoint(pObj->cw,f,t,1.0);
  m = arcControlPoint(pObj->cw,f,t);
  if( pObj->larrow ){
    pik_draw_arrowhead(p,&m,&f,pObj);
  }
  if( pObj->rarrow ){
    pik_draw_arrowhead(p,&m,&t,pObj);
  }
  pik_append_xy(p,"<path d=\"M", f.x, f.y);
4336
4337
4338
4339
4340
4341
4342
4343

4344
4345
4346
4347
4348
4349
4350
4404
4405
4406
4407
4408
4409
4410

4411
4412
4413
4414
4415
4416
4417
4418







-
+







  pObj->sw = 0.0;
}
static PPoint textOffset(Pik *p, PObj *pObj, int cp){
  /* Automatically slim-down the width and height of text
  ** statements so that the bounding box tightly encloses the text,
  ** then get boxOffset() to do the offset computation.
  */
  pik_size_to_fit(p, &pObj->errTok,3);
  pik_size_to_fit(p, pObj, &pObj->errTok,3);
  return boxOffset(p, pObj, cp);
}
static void textRender(Pik *p, PObj *pObj){
  pik_append_txt(p, pObj, 0);
}


6345
6346
6347
6348
6349
6350
6351
6352

6353
6354
6355
6356
6357

6358
6359
6360
6361
6362
6363
6364
6413
6414
6415
6416
6417
6418
6419

6420

6421
6422
6423

6424
6425
6426
6427
6428
6429
6430
6431







-
+
-



-
+







**
** The eWhich parameter is:
**
**    1:   Fit horizontally only
**    2:   Fit vertically only
**    3:   Fit both ways
*/
static void pik_size_to_fit(Pik *p, PToken *pFit, int eWhich){
static void pik_size_to_fit(Pik *p, PObj *pObj, PToken *pFit, int eWhich){
  PObj *pObj;
  PNum w, h;
  PBox bbox;
  if( p->nErr ) return;
  pObj = p->cur;
  if( pObj==0 ) pObj = p->cur;

  if( pObj->nTxt==0 ){
    pik_error(0, pFit, "no text to fit to");
    return;
  }
  if( pObj->type->xFit==0 ) return;
  pik_bbox_init(&bbox);
6493
6494
6495
6496
6497
6498
6499
6500

6501
6502

6503
6504
6505
6506
6507
6508
6509
6560
6561
6562
6563
6564
6565
6566

6567
6568

6569
6570
6571
6572
6573
6574
6575
6576







-
+

-
+







    const char *zClr;
    int c1, c2;
    unsigned int i;
    mid = (first+last)/2;
    zClr = aColor[mid].zName;
    for(i=0; i<pId->n; i++){
      c1 = zClr[i]&0x7f;
      if( isupper(c1) ) c1 = tolower(c1);
      if( IsUpper(c1) ) c1 = ToLower(c1);
      c2 = pId->z[i]&0x7f;
      if( isupper(c2) ) c2 = tolower(c2);
      if( IsUpper(c2) ) c2 = ToLower(c2);
      c = c2 - c1;
      if( c ) break;
    }
    if( c==0 && aColor[mid].zName[pId->n] ) c = -1;
    if( c==0 ) return (double)aColor[mid].val;
    if( c>0 ){
      first = mid+1;
6894
6895
6896
6897
6898
6899
6900
6901

6902
6903

6904
6905
6906
6907
6908
6909
6910

6911
6912
6913
6914
6915
6916
6917
6961
6962
6963
6964
6965
6966
6967

6968
6969

6970
6971
6972
6973
6974
6975
6976

6977
6978
6979
6980
6981
6982
6983
6984







-
+

-
+






-
+







    /* A height or width less than or equal to zero means "autofit".
    ** Change the height or width to be big enough to contain the text,
    */
    if( pObj->h<=0.0 ){
      if( pObj->nTxt==0 ){
        pObj->h = 0.0;
      }else if( pObj->w<=0.0 ){
        pik_size_to_fit(p, &pObj->errTok, 3);
        pik_size_to_fit(p, pObj, &pObj->errTok, 3);
      }else{
        pik_size_to_fit(p, &pObj->errTok, 2);
        pik_size_to_fit(p, pObj, &pObj->errTok, 2);
      }
    }
    if( pObj->w<=0.0 ){
      if( pObj->nTxt==0 ){
        pObj->w = 0.0;
      }else{
        pik_size_to_fit(p, &pObj->errTok, 1);
        pik_size_to_fit(p, pObj, &pObj->errTok, 1);
      }
    }
    ofst = pik_elem_offset(p, pObj, pObj->eWith);
    dx = (pObj->with.x - ofst.x) - pObj->ptAt.x;
    dy = (pObj->with.y - ofst.y) - pObj->ptAt.y;
    if( dx!=0 || dy!=0 ){
      pik_elem_move(pObj, dx, dy);
7233
7234
7235
7236
7237
7238
7239
7240


7241
7242
7243
7244
7245
7246
7247
7300
7301
7302
7303
7304
7305
7306

7307
7308
7309
7310
7311
7312
7313
7314
7315







-
+
+







      p->wSVG = pik_round(p->wSVG*pikScale);
      p->hSVG = pik_round(p->hSVG*pikScale);
      pik_append_num(p, " width=\"", p->wSVG);
      pik_append_num(p, "\" height=\"", p->hSVG);
      pik_append(p, "\"", 1);
    }
    pik_append_dis(p, " viewBox=\"0 0 ",w,"");
    pik_append_dis(p, " ",h,"\">\n");
    pik_append_dis(p, " ",h,"\"");
    pik_append(p, " data-pikchr-date=\"" MANIFEST_ISODATE "\">\n", -1);
    pik_elist_render(p, pList);
    pik_append(p,"</svg>\n", -1);
  }else{
    p->wSVG = -1;
    p->hSVG = -1;
  }
  pik_elist_free(p, pList);
7317
7318
7319
7320
7321
7322
7323

7324
7325
7326
7327
7328
7329
7330
7385
7386
7387
7388
7389
7390
7391
7392
7393
7394
7395
7396
7397
7398
7399







+







  { "mono",       4,   T_MONO,      0,         0        },
  { "monospace",  9,   T_MONO,      0,         0        },
  { "n",          1,   T_EDGEPT,    0,         CP_N     },
  { "ne",         2,   T_EDGEPT,    0,         CP_NE    },
  { "north",      5,   T_EDGEPT,    0,         CP_N     },
  { "nw",         2,   T_EDGEPT,    0,         CP_NW    },
  { "of",         2,   T_OF,        0,         0        },
  { "pikchr_date",11,  T_ISODATE,   0,         0,       },
  { "previous",   8,   T_LAST,      0,         0,       },
  { "print",      5,   T_PRINT,     0,         0        },
  { "rad",        3,   T_RADIUS,    0,         0        },
  { "radius",     6,   T_RADIUS,    0,         0        },
  { "right",      5,   T_RIGHT,     DIR_RIGHT, CP_E     },
  { "rjust",      5,   T_RJUST,     0,         0        },
  { "s",          1,   T_EDGEPT,    0,         CP_S     },
7603
7604
7605
7606
7607
7608
7609
7610

7611
7612
7613
7614
7615
7616
7617
7618
7619
7620
7621
7622
7623
7624
7625
7626
7627
7628
7629
7630

7631
7632
7633
7634


7635
7636
7637
7638
7639
7640
7641
7642
7643
7644
7645
7646
7647
7648
7649

7650
7651
7652
7653
7654
7655
7656
7672
7673
7674
7675
7676
7677
7678

7679
7680
7681
7682
7683
7684
7685
7686
7687
7688
7689
7690
7691
7692
7693
7694
7695
7696
7697
7698

7699
7700
7701


7702
7703
7704
7705
7706
7707
7708
7709
7710
7711
7712
7713
7714
7715
7716
7717

7718
7719
7720
7721
7722
7723
7724
7725







-
+



















-
+


-
-
+
+














-
+







      pToken->eType = T_ERROR;
      return 1;
    }
    default: {
      c = z[0];
      if( c=='.' ){
        unsigned char c1 = z[1];
        if( islower(c1) ){
        if( IsLower(c1) ){
          const PikWord *pFound;
          for(i=2; (c = z[i])>='a' && c<='z'; i++){}
          pFound = pik_find_word((const char*)z+1, i-1,
                                    pik_keywords, count(pik_keywords));
          if( pFound && (pFound->eEdge>0 ||
                         pFound->eType==T_EDGEPT ||
                         pFound->eType==T_START ||
                         pFound->eType==T_END )
          ){
            /* Dot followed by something that is a 2-D place value */
            pToken->eType = T_DOT_E;
          }else if( pFound && (pFound->eType==T_X || pFound->eType==T_Y) ){
            /* Dot followed by "x" or "y" */
            pToken->eType = T_DOT_XY;
          }else{
            /* Any other "dot" */
            pToken->eType = T_DOT_L;
          }
          return 1;
        }else if( isdigit(c1) ){
        }else if( IsDigit(c1) ){
          i = 0;
          /* no-op.  Fall through to number handling */
        }else if( isupper(c1) ){
          for(i=2; (c = z[i])!=0 && (isalnum(c) || c=='_'); i++){}
        }else if( IsUpper(c1) ){
          for(i=2; (c = z[i])!=0 && (IsAlnum(c) || c=='_'); i++){}
          pToken->eType = T_DOT_U;
          return 1;
        }else{
          pToken->eType = T_ERROR;
          return 1;
        }
      }
      if( (c>='0' && c<='9') || c=='.' ){
        int nDigit;
        int isInt = 1;
        if( c!='.' ){
          nDigit = 1;
          for(i=1; (c = z[i])>='0' && c<='9'; i++){ nDigit++; }
          if( i==1 && (c=='x' || c=='X') ){
            for(i=2; (c = z[i])!=0 && isxdigit(c); i++){}
            for(i=2; (c = z[i])!=0 && IsXDigit(c); i++){}
            pToken->eType = T_NUMBER;
            return i;
          }
        }else{
          isInt = 0;
          nDigit = 0;
          i = 0;
7698
7699
7700
7701
7702
7703
7704
7705

7706
7707

7708
7709
7710
7711
7712
7713
7714
7715
7716
7717
7718
7719
7720
7721
7722
7723
7724

7725
7726
7727

7728
7729
7730
7731
7732

7733
7734
7735
7736
7737
7738
7739
7767
7768
7769
7770
7771
7772
7773

7774
7775

7776
7777
7778
7779
7780
7781
7782
7783
7784
7785
7786
7787
7788
7789
7790
7791
7792

7793
7794
7795

7796
7797
7798
7799
7800

7801
7802
7803
7804
7805
7806
7807
7808







-
+

-
+
















-
+


-
+




-
+







         || (c=='p' && c2=='x')
         || (c=='p' && c2=='c')
        ){
          i += 2;
        }
        pToken->eType = T_NUMBER;
        return i;
      }else if( islower(c) ){
      }else if( IsLower(c) ){
        const PikWord *pFound;
        for(i=1; (c =  z[i])!=0 && (isalnum(c) || c=='_'); i++){}
        for(i=1; (c =  z[i])!=0 && (IsAlnum(c) || c=='_'); i++){}
        pFound = pik_find_word((const char*)z, i,
                               pik_keywords, count(pik_keywords));
        if( pFound ){
          pToken->eType = pFound->eType;
          pToken->eCode = pFound->eCode;
          pToken->eEdge = pFound->eEdge;
          return i;
        }
        pToken->n = i;
        if( pik_find_class(pToken)!=0 ){
          pToken->eType = T_CLASSNAME;
        }else{
          pToken->eType = T_ID;
        }
        return i;
      }else if( c>='A' && c<='Z' ){
        for(i=1; (c =  z[i])!=0 && (isalnum(c) || c=='_'); i++){}
        for(i=1; (c =  z[i])!=0 && (IsAlnum(c) || c=='_'); i++){}
        pToken->eType = T_PLACENAME;
        return i;
      }else if( c=='$' && z[1]>='1' && z[1]<='9' && !isdigit(z[2]) ){
      }else if( c=='$' && z[1]>='1' && z[1]<='9' && !IsDigit(z[2]) ){
        pToken->eType = T_PARAMETER;
        pToken->eCode = z[1] - '1';
        return 2;
      }else if( c=='_' || c=='$' || c=='@' ){
        for(i=1; (c =  z[i])!=0 && (isalnum(c) || c=='_'); i++){}
        for(i=1; (c =  z[i])!=0 && (IsAlnum(c) || c=='_'); i++){}
        pToken->eType = T_ID;
        return i;
      }else{
        pToken->eType = T_ERROR;
        return 1;
      }
    }
7812
7813
7814
7815
7816
7817
7818
7819
7820


7821
7822
7823
7824
7825
7826
7827
7881
7882
7883
7884
7885
7886
7887


7888
7889
7890
7891
7892
7893
7894
7895
7896







-
-
+
+







  if( z[i]==')' ){
    args[nArg].n = i - iStart;
    /* Remove leading and trailing whitespace from each argument.
    ** If what remains is one of $1, $2, ... $9 then transfer the
    ** corresponding argument from the outer context */
    for(j=0; j<=nArg; j++){
      PToken *t = &args[j];
      while( t->n>0 && isspace(t->z[0]) ){ t->n--; t->z++; }
      while( t->n>0 && isspace(t->z[t->n-1]) ){ t->n--; }
      while( t->n>0 && IsSpace(t->z[0]) ){ t->n--; t->z++; }
      while( t->n>0 && IsSpace(t->z[t->n-1]) ){ t->n--; }
      if( t->n==2 && t->z[0]=='$' && t->z[1]>='1' && t->z[1]<='9' ){
        if( pOuter ) *t = pOuter[t->z[1]-'1'];
        else t->n = 0;
      }
    }
    return i+1;
  }
7894
7895
7896
7897
7898
7899
7900
7901

7902
7903
7904
7905
7906





7907
7908
7909
7910
7911










7912
7913
7914
7915
7916
7917
7918
7963
7964
7965
7966
7967
7968
7969

7970
7971
7972
7973
7974
7975
7976
7977
7978
7979
7980
7981
7982
7983
7984
7985
7986
7987
7988
7989
7990
7991
7992
7993
7994
7995
7996
7997
7998
7999
8000
8001
8002







-
+





+
+
+
+
+





+
+
+
+
+
+
+
+
+
+







      pik_tokenize(p, &pMac->macroBody, pParser, args);
      p->nCtx--;
      pMac->inUse = 0;
    }else{
#if 0
      printf("******** Token %s (%d): \"%.*s\" **************\n",
             yyTokenName[token.eType], token.eType,
             (int)(isspace(token.z[0]) ? 0 : sz), token.z);
             (int)(IsSpace(token.z[0]) ? 0 : sz), token.z);
#endif
      token.n = (unsigned short)(sz & 0xffff);
      if( p->nToken++ > PIKCHR_TOKEN_LIMIT ){
        pik_error(p, &token, "script is too complex");
        break;
      }
      if( token.eType==T_ISODATE ){
        token.z = "\"" MANIFEST_ISODATE "\"";
        token.n = sizeof(MANIFEST_ISODATE)+1;
        token.eType = T_STRING;
      }
      pik_parser(pParser, token.eType, token);
    }
  }
}

/*
** Return the version name.
*/
const char *pikchr_version(void)
  /* Emscripten workaround, else it chokes on the inlined version */;

const char *pikchr_version(void){
  return RELEASE_VERSION " " MANIFEST_ISODATE;
}

/*
** Parse the PIKCHR script contained in zText[].  Return a rendering.  Or
** if an error is encountered, return the error text.  The error message
** is HTML formatted.  So regardless of what happens, the return text
** is safe to be insertd into an HTML output stream.
**
8128
8129
8130
8131
8132
8133
8134




8135
8136
8137
8138
8139
8140
8141
8212
8213
8214
8215
8216
8217
8218
8219
8220
8221
8222
8223
8224
8225
8226
8227
8228
8229







+
+
+
+







        if( zHtmlHdr==0 ){
          fprintf(stderr, "the \"%s\" option must come first\n",argv[i]);
          exit(1);
        }
        bSvgOnly = 1;
        mFlags |= PIKCHR_PLAINTEXT_ERRORS;
      }else
      if( strcmp(z,"version")==0 || strcmp(z,"v")==0 ){
        printf("pikchr %s\n", pikchr_version());
        return 0;
      }else
      {
        fprintf(stderr,"unknown option: \"%s\"\n", argv[i]);
        usage(argv[0]);
      }
      continue;
    }
    zIn = readFile(argv[i]);
8194
8195
8196
8197
8198
8199
8200
8201

8202
8203
8204
8205
8206
8207
8208
8282
8283
8284
8285
8286
8287
8288

8289
8290
8291
8292
8293
8294
8295
8296







-
+







**
**    gcc -c pikchr.so -DPIKCHR_TCL -shared pikchr.c
*/
static int pik_tcl_command(
  ClientData clientData, /* Not Used */
  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
  int objc,              /* Number of arguments */
  Tcl_Obj *CONST objv[]  /* Command arguments */
  Tcl_Obj *const objv[]  /* Command arguments */
){
  int w, h;              /* Width and height of the pikchr */
  const char *zIn;       /* Source text input */
  char *zOut;            /* SVG output text */
  Tcl_Obj *pRes;         /* The result TCL object */

  (void)clientData;
8240
8241
8242
8243
8244
8245
8246
8247

8328
8329
8330
8331
8332
8333
8334

8335







-
+
  return TCL_OK;
}


#endif /* PIKCHR_TCL */


#line 8272 "pikchr.c"
#line 8335 "pikchr.c"
Changes to extsrc/pikchr.js.
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
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
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
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
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
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
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

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






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
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
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
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
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
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
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
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
877
878

879
880
881
882
883
884
885
886



887


-
+


-
-
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+

+


-
-
-
+
+
+


+
+
+
+
+
+
+
+
+
+
+
+
+







-
+


-
-
+
-
-



-
-
-
-
+
+
+
+


+
-
+

+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
+
+
-
-
-
-
+
-
-
+
+



-
+

+


+
+


+
+
+
+




-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+


+
+
+
+
+


+
+
+


-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+


-
-
+
+
-
-
+


+


+


+


-
-
-
-

-
+
-
-
-
+
+
+

-
-
+



-
-
+
+



-
+
-
-
-
+
+
+

-
-
+



-
+



-
+



-
+


+
+
+
+
+
+
+
+
+
+
+
+
+







-
+
-
-
+
-



-
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+


-
+
-
-
+
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


+
+
+
+


+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+




-
-
-
-
-
-
-
+
-

-
+


-
+


-
-
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
+
+
+
+
-
-
-
+
+
-
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+

-
-
+
-
-
-
+
+
+
+

-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
-
-
-
+
+

-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
-
-
+
+
+

-
-
+
+
+

-
+
-
+

-
-
+
+
+

-
-
+
+
+

-
-
+
+
+

-
+
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
+
+
+
-

-
-
-
+

-
-
-
+

+
+
+
-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
+
-
-
-
+

-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+

-
+
-
-
-
-
-
-
-
-
+
+
+
+
-
-
+
+
+
+
+
+
+
+


+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
-
-
+
+
+
+



-
-
-
-
+
+
+
+
+

-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+
-
-
-
-
-
-
-
-
-

-
-
+
+
+
+


-
-
+
+
-
+

-
+
-
-

-
-
+
+
-
+

-
-
+
+
-
+

+
+



+
+
+
+









+
+

+
-
-
+
+


+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
-
-
-
-
-
+
+
+
+
+



-
-
-
-
+
+
+
+




+
+
+
+
+
+
+

-
+
+






-
-
-
+

var initPikchrModule = (() => {
  var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
  var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
  
  return (
function(config) {
  var initPikchrModule = config || {};
function(moduleArg = {}) {
  var moduleRtn;

// include: shell.js
// The Module object: Our interface to the outside world. We import
// and export values on it. There are various ways Module can be used:
// 1. Not defined. We create it here
// 2. A function parameter, function(moduleArg) => Promise<Module>
// 3. pre-run appended it, var Module = {}; ..generated code..
// 4. External script tag defines var Module.
// We need to check if Module already exists (e.g. case 3 above).
// Substitution will be replaced with actual code on later stage of the build,
// this way Closure Compiler will not mangle it (e.g. case 4. above).
// Note that if you want to run closure, and also to use Module
// after the generated code, you will need to define   var Module = {};
// before the code. Then that object will be used in the code, and you
// can continue to use Module afterwards as well.
var Module = typeof initPikchrModule != "undefined" ? initPikchrModule : {};
var Module = moduleArg;

// Set up the promise that indicates the Module is initialized
var readyPromiseResolve, readyPromiseReject;

Module["ready"] = new Promise(function(resolve, reject) {
 readyPromiseResolve = resolve;
 readyPromiseReject = reject;
var readyPromise = new Promise((resolve, reject) => {
  readyPromiseResolve = resolve;
  readyPromiseReject = reject;
});

// Determine the runtime environment we are in. You can customize this by
// setting the ENVIRONMENT setting at compile time (see settings.js).
var ENVIRONMENT_IS_WEB = true;

var ENVIRONMENT_IS_WORKER = false;

// --pre-jses are emitted after the Module integration code, so that they can
// refer to Module (if they choose; they can also define Module)
// Sometimes an existing Module object exists with properties
// meant to overwrite the default module functionality. Here
// we collect those properties and reapply _after_ we configure
// the current environment's defaults to avoid having to be so
// defensive during initialization.
var moduleOverrides = Object.assign({}, Module);

var arguments_ = [];

var thisProgram = "./this.program";

var quit_ = (status, toThrow) => {
 throw toThrow;
  throw toThrow;
};

var ENVIRONMENT_IS_WEB = true;

// `/` should be present at the end if `scriptDirectory` is not empty
var ENVIRONMENT_IS_WORKER = false;

var scriptDirectory = "";

function locateFile(path) {
 if (Module["locateFile"]) {
  return Module["locateFile"](path, scriptDirectory);
 }
 return scriptDirectory + path;
  if (Module["locateFile"]) {
    return Module["locateFile"](path, scriptDirectory);
  }
  return scriptDirectory + path;
}

// Hooks that are implemented differently in different runtime environments.
var read_, readAsync, readBinary, setWindowTitle;
var readAsync, readBinary;

// Note that this includes Node.js workers when relevant (pthreads is enabled).
// Node.js workers are detected as a combination of ENVIRONMENT_IS_WORKER and
// ENVIRONMENT_IS_NODE.
if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
 if (ENVIRONMENT_IS_WORKER) {
  scriptDirectory = self.location.href;
 } else if (typeof document != "undefined" && document.currentScript) {
  scriptDirectory = document.currentScript.src;
 }
 if (_scriptDir) {
  scriptDirectory = _scriptDir;
 }
 if (scriptDirectory.indexOf("blob:") !== 0) {
  scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, "").lastIndexOf("/") + 1);
 } else {
  scriptDirectory = "";
 }
 {
  read_ = url => {
  if (ENVIRONMENT_IS_WORKER) {
    // Check worker, not web, since window could be polyfilled
    scriptDirectory = self.location.href;
  } else if (typeof document != "undefined" && document.currentScript) {
    // web
    scriptDirectory = document.currentScript.src;
  }
  // When MODULARIZE, this JS may be executed later, after document.currentScript
  // is gone, so we saved it, and we use it here instead of any other info.
  if (_scriptName) {
    scriptDirectory = _scriptName;
  }
  // blob urls look like blob:http://site.com/etc/etc and we cannot infer anything from them.
  // otherwise, slice off the final part of the url to find the script directory.
  // if scriptDirectory does not contain a slash, lastIndexOf will return -1,
  // and scriptDirectory will correctly be replaced with an empty string.
  // If scriptDirectory contains a query (starting with ?) or a fragment (starting with #),
  // they are removed because they could contain a slash.
  if (scriptDirectory.startsWith("blob:")) {
    scriptDirectory = "";
  } else {
    scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, "").lastIndexOf("/") + 1);
  }
  {
    // include: web_or_worker_shell_read.js
   var xhr = new XMLHttpRequest();
   xhr.open("GET", url, false);
   xhr.send(null);
   return xhr.responseText;
  };
  if (ENVIRONMENT_IS_WORKER) {
   readBinary = url => {
    readAsync = url => fetch(url, {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, false);
    xhr.responseType = "arraybuffer";
      credentials: "same-origin"
    }).then(response => {
    xhr.send(null);
    return new Uint8Array(xhr.response);
   };
  }
  readAsync = (url, onload, onerror) => {
   var xhr = new XMLHttpRequest();
   xhr.open("GET", url, true);
   xhr.responseType = "arraybuffer";
   xhr.onload = () => {
    if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
     onload(xhr.response);
     return;
    }
      if (response.ok) {
        return response.arrayBuffer();
      }
    onerror();
   };
      return Promise.reject(new Error(response.status + " : " + response.url));
    });
   xhr.onerror = onerror;
   xhr.send(null);
  };
 }
  }
 setWindowTitle = title => document.title = title;
} else {}
} else // end include: web_or_worker_shell_read.js
{}

var out = Module["print"] || console.log.bind(console);

var err = Module["printErr"] || console.warn.bind(console);
var err = Module["printErr"] || console.error.bind(console);

// Merge back in the overrides
Object.assign(Module, moduleOverrides);

// Free the object hierarchy contained in the overrides, this lets the GC
// reclaim data used.
moduleOverrides = null;

// Emit code to handle expected values on the Module object. This applies Module.x
// to the proper local x. This has two benefits: first, we only emit it if it is
// expected to arrive, and second, by using a local everywhere else that can be
// minified.
if (Module["arguments"]) arguments_ = Module["arguments"];

if (Module["thisProgram"]) thisProgram = Module["thisProgram"];

if (Module["quit"]) quit_ = Module["quit"];

var wasmBinary;

if (Module["wasmBinary"]) wasmBinary = Module["wasmBinary"];

var noExitRuntime = Module["noExitRuntime"] || true;

if (typeof WebAssembly != "object") {
 abort("no native wasm support detected");
}

// perform assertions in shell.js after we set up out() and err(), as otherwise if an assertion fails it cannot print the message
// end include: shell.js
// include: preamble.js
// === Preamble library stuff ===
// Documentation for the public APIs defined in this file must be updated in:
//    site/source/docs/api_reference/preamble.js.rst
// A prebuilt local version of the documentation is available at:
//    site/build/text/docs/api_reference/preamble.js.txt
// You can also build docs locally as HTML or other formats in site/
// An online HTML version (which may be of a different version of Emscripten)
//    is up at http://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html
var wasmBinary = Module["wasmBinary"];

// Wasm globals
var wasmMemory;

//========================================
// Runtime essentials
//========================================
// whether we are quitting the application. no code should run after this.
// set in exit() and abort()
var ABORT = false;

// set by exit() and abort().  Passed to 'onExit' handler.
// NOTE: This is also used as the process return code code in shell environments
// but only when noExitRuntime is false.
var EXITSTATUS;

var UTF8Decoder = typeof TextDecoder != "undefined" ? new TextDecoder("utf8") : undefined;

// Memory management
function UTF8ArrayToString(heapOrArray, idx, maxBytesToRead) {
 var endIdx = idx + maxBytesToRead;
 var endPtr = idx;
 while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr;
 if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) {
  return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr));
 }
 var str = "";
 while (idx < endPtr) {
  var u0 = heapOrArray[idx++];
  if (!(u0 & 128)) {
   str += String.fromCharCode(u0);
   continue;
  }
  var u1 = heapOrArray[idx++] & 63;
  if ((u0 & 224) == 192) {
   str += String.fromCharCode((u0 & 31) << 6 | u1);
   continue;
  }
  var u2 = heapOrArray[idx++] & 63;
  if ((u0 & 240) == 224) {
   u0 = (u0 & 15) << 12 | u1 << 6 | u2;
  } else {
   u0 = (u0 & 7) << 18 | u1 << 12 | u2 << 6 | heapOrArray[idx++] & 63;
  }
  if (u0 < 65536) {
   str += String.fromCharCode(u0);
  } else {
   var ch = u0 - 65536;
   str += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023);
  }
 }
 return str;
}

var /** @type {!Int8Array} */ HEAP8, /** @type {!Uint8Array} */ HEAPU8, /** @type {!Int16Array} */ HEAP16, /** @type {!Uint16Array} */ HEAPU16, /** @type {!Int32Array} */ HEAP32, /** @type {!Uint32Array} */ HEAPU32, /** @type {!Float32Array} */ HEAPF32, /** @type {!Float64Array} */ HEAPF64;
function UTF8ToString(ptr, maxBytesToRead) {
 return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : "";
}


// include: runtime_shared.js
function stringToUTF8Array(str, heap, outIdx, maxBytesToWrite) {
 if (!(maxBytesToWrite > 0)) return 0;
 var startIdx = outIdx;
 var endIdx = outIdx + maxBytesToWrite - 1;
 for (var i = 0; i < str.length; ++i) {
  var u = str.charCodeAt(i);
  if (u >= 55296 && u <= 57343) {
   var u1 = str.charCodeAt(++i);
   u = 65536 + ((u & 1023) << 10) | u1 & 1023;
  }
  if (u <= 127) {
   if (outIdx >= endIdx) break;
   heap[outIdx++] = u;
  } else if (u <= 2047) {
   if (outIdx + 1 >= endIdx) break;
   heap[outIdx++] = 192 | u >> 6;
   heap[outIdx++] = 128 | u & 63;
  } else if (u <= 65535) {
   if (outIdx + 2 >= endIdx) break;
   heap[outIdx++] = 224 | u >> 12;
   heap[outIdx++] = 128 | u >> 6 & 63;
   heap[outIdx++] = 128 | u & 63;
  } else {
   if (outIdx + 3 >= endIdx) break;
   heap[outIdx++] = 240 | u >> 18;
   heap[outIdx++] = 128 | u >> 12 & 63;
   heap[outIdx++] = 128 | u >> 6 & 63;
   heap[outIdx++] = 128 | u & 63;
  }
 }
 heap[outIdx] = 0;
 return outIdx - startIdx;
}

function stringToUTF8(str, outPtr, maxBytesToWrite) {
 return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite);
}

var HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64;

function updateMemoryViews() {
 var b = wasmMemory.buffer;
 Module["HEAP8"] = HEAP8 = new Int8Array(b);
 Module["HEAP16"] = HEAP16 = new Int16Array(b);
  var b = wasmMemory.buffer;
  Module["HEAP8"] = HEAP8 = new Int8Array(b);
  Module["HEAP16"] = HEAP16 = new Int16Array(b);
 Module["HEAP32"] = HEAP32 = new Int32Array(b);
 Module["HEAPU8"] = HEAPU8 = new Uint8Array(b);
 Module["HEAPU16"] = HEAPU16 = new Uint16Array(b);
 Module["HEAPU32"] = HEAPU32 = new Uint32Array(b);
 Module["HEAPF32"] = HEAPF32 = new Float32Array(b);
 Module["HEAPF64"] = HEAPF64 = new Float64Array(b);
  Module["HEAPU8"] = HEAPU8 = new Uint8Array(b);
  Module["HEAPU16"] = HEAPU16 = new Uint16Array(b);
  Module["HEAP32"] = HEAP32 = new Int32Array(b);
  Module["HEAPU32"] = HEAPU32 = new Uint32Array(b);
  Module["HEAPF32"] = HEAPF32 = new Float32Array(b);
  Module["HEAPF64"] = HEAPF64 = new Float64Array(b);
}

var INITIAL_MEMORY = Module["INITIAL_MEMORY"] || 16777216;

// end include: runtime_shared.js
// include: runtime_stack_check.js
var wasmTable;

// end include: runtime_stack_check.js
var __ATPRERUN__ = [];

// functions called before the runtime is initialized
var __ATINIT__ = [];

// functions called during shutdown
var __ATPOSTRUN__ = [];

// functions called after the main() is called
var runtimeInitialized = false;

function keepRuntimeAlive() {
 return noExitRuntime;
}

function preRun() {
 if (Module["preRun"]) {
  var preRuns = Module["preRun"];
  if (typeof Module["preRun"] == "function") Module["preRun"] = [ Module["preRun"] ];
  while (Module["preRun"].length) {
   addOnPreRun(Module["preRun"].shift());
  if (preRuns) {
    if (typeof preRuns == "function") preRuns = [ preRuns ];
    preRuns.forEach(addOnPreRun);
  }
 }
 callRuntimeCallbacks(__ATPRERUN__);
  callRuntimeCallbacks(__ATPRERUN__);
}

function initRuntime() {
 runtimeInitialized = true;
 callRuntimeCallbacks(__ATINIT__);
  runtimeInitialized = true;
  callRuntimeCallbacks(__ATINIT__);
}

function postRun() {
 if (Module["postRun"]) {
  var postRuns = Module["postRun"];
  if (typeof Module["postRun"] == "function") Module["postRun"] = [ Module["postRun"] ];
  while (Module["postRun"].length) {
   addOnPostRun(Module["postRun"].shift());
  if (postRuns) {
    if (typeof postRuns == "function") postRuns = [ postRuns ];
    postRuns.forEach(addOnPostRun);
  }
 }
 callRuntimeCallbacks(__ATPOSTRUN__);
  callRuntimeCallbacks(__ATPOSTRUN__);
}

function addOnPreRun(cb) {
 __ATPRERUN__.unshift(cb);
  __ATPRERUN__.unshift(cb);
}

function addOnInit(cb) {
 __ATINIT__.unshift(cb);
  __ATINIT__.unshift(cb);
}

function addOnPostRun(cb) {
 __ATPOSTRUN__.unshift(cb);
  __ATPOSTRUN__.unshift(cb);
}

// include: runtime_math.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/fround
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc
// end include: runtime_math.js
// A counter of dependencies for calling run(). If we need to
// do asynchronous work before running, increment this and
// decrement it. Incrementing must happen in a place like
// Module.preRun (used by emcc to add file preloading).
// Note that you can add dependencies in preRun, even though
// it happens right before run - run will be postponed until
// the dependencies are met.
var runDependencies = 0;

var runDependencyWatcher = null;

var dependenciesFulfilled = null;

function addRunDependency(id) {
 runDependencies++;
  runDependencies++;
 if (Module["monitorRunDependencies"]) {
  Module["monitorRunDependencies"](runDependencies);
  Module["monitorRunDependencies"]?.(runDependencies);
 }
}

function removeRunDependency(id) {
 runDependencies--;
  runDependencies--;
 if (Module["monitorRunDependencies"]) {
  Module["monitorRunDependencies"](runDependencies);
  Module["monitorRunDependencies"]?.(runDependencies);
 }
 if (runDependencies == 0) {
  if (runDependencyWatcher !== null) {
   clearInterval(runDependencyWatcher);
   runDependencyWatcher = null;
  }
  if (dependenciesFulfilled) {
   var callback = dependenciesFulfilled;
   dependenciesFulfilled = null;
   callback();
  }
 }
  if (runDependencies == 0) {
    if (runDependencyWatcher !== null) {
      clearInterval(runDependencyWatcher);
      runDependencyWatcher = null;
    }
    if (dependenciesFulfilled) {
      var callback = dependenciesFulfilled;
      dependenciesFulfilled = null;
      callback();
    }
  }
}

function abort(what) {
/** @param {string|number=} what */ function abort(what) {
 if (Module["onAbort"]) {
  Module["onAbort"](what);
  Module["onAbort"]?.(what);
 }
 what = "Aborted(" + what + ")";
 err(what);
 ABORT = true;
  what = "Aborted(" + what + ")";
  // TODO(sbc): Should we remove printing and leave it up to whoever
  // catches the exception?
  err(what);
  ABORT = true;
 EXITSTATUS = 1;
 what += ". Build with -sASSERTIONS for more info.";
 var e = new WebAssembly.RuntimeError(what);
 readyPromiseReject(e);
 throw e;
  what += ". Build with -sASSERTIONS for more info.";
  // Use a wasm runtime error, because a JS error might be seen as a foreign
  // exception, which means we'd run destructors on it. We need the error to
  // simply make the program stop.
  // FIXME This approach does not work in Wasm EH because it currently does not assume
  // all RuntimeErrors are from traps; it decides whether a RuntimeError is from
  // a trap or not based on a hidden field within the object. So at the moment
  // we don't have a way of throwing a wasm trap from JS. TODO Make a JS API that
  // allows this in the wasm spec.
  // Suppress closure compiler warning here. Closure compiler's builtin extern
  // definition for WebAssembly.RuntimeError claims it takes no arguments even
  // though it can.
  // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure gets fixed.
  /** @suppress {checkTypes} */ var e = new WebAssembly.RuntimeError(what);
  readyPromiseReject(e);
  // Throw the error whether or not MODULARIZE is set because abort is used
  // in code paths apart from instantiation where an exception is expected
  // to be thrown when abort is called.
  throw e;
}

// include: memoryprofiler.js
// end include: memoryprofiler.js
// include: URIUtils.js
// Prefix of data URIs emitted by SINGLE_FILE and related options.
var dataURIPrefix = "data:application/octet-stream;base64,";

/**
 * Indicates whether filename is a base64 data URI.
 * @noinline
 */ var isDataURI = filename => filename.startsWith(dataURIPrefix);

// end include: URIUtils.js
// include: runtime_exceptions.js
// end include: runtime_exceptions.js
function findWasmBinary() {
  var f = "pikchr-v2813665466.wasm";
function isDataURI(filename) {
 return filename.startsWith(dataURIPrefix);
  if (!isDataURI(f)) {
    return locateFile(f);
  }
  return f;
}

var wasmBinaryFile;

wasmBinaryFile = "pikchr.wasm";

if (!isDataURI(wasmBinaryFile)) {
 wasmBinaryFile = locateFile(wasmBinaryFile);
}

function getBinary(file) {
function getBinarySync(file) {
 try {
  if (file == wasmBinaryFile && wasmBinary) {
   return new Uint8Array(wasmBinary);
    return new Uint8Array(wasmBinary);
  }
  if (readBinary) {
   return readBinary(file);
    return readBinary(file);
  }
  throw "both async and sync fetching of the wasm failed";
 } catch (err) {
  abort(err);
 }
}
}


function getBinaryPromise() {
 if (!wasmBinary && (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER)) {
  if (typeof fetch == "function") {
   return fetch(wasmBinaryFile, {
    credentials: "same-origin"
   }).then(function(response) {
function getBinaryPromise(binaryFile) {
  // If we don't have the binary yet, load it asynchronously using readAsync.
  if (!wasmBinary) {
    // Fetch the binary using readAsync
    return readAsync(binaryFile).then(response => new Uint8Array(/** @type{!ArrayBuffer} */ (response)), // Fall back to getBinarySync if readAsync fails
    () => getBinarySync(binaryFile));
  }
  // Otherwise, getBinarySync should be able to get it synchronously
  return Promise.resolve().then(() => getBinarySync(binaryFile));
    if (!response["ok"]) {
     throw "failed to load wasm binary file at '" + wasmBinaryFile + "'";
    }
    return response["arrayBuffer"]();
   }).catch(function() {
    return getBinary(wasmBinaryFile);
   });
  }
 }
 return Promise.resolve().then(function() {
  return getBinary(wasmBinaryFile);
 });
}

}

function instantiateArrayBuffer(binaryFile, imports, receiver) {
  return getBinaryPromise(binaryFile).then(binary => WebAssembly.instantiate(binary, imports)).then(receiver, reason => {
    err(`failed to asynchronously prepare wasm: ${reason}`);
    abort(reason);
  });
}

function instantiateAsync(binary, binaryFile, imports, callback) {
  if (!binary && typeof WebAssembly.instantiateStreaming == "function" && !isDataURI(binaryFile) && typeof fetch == "function") {
    return fetch(binaryFile, {
      credentials: "same-origin"
    }).then(response => {
      // Suppress closure warning here since the upstream definition for
      // instantiateStreaming only allows Promise<Repsponse> rather than
      // an actual Response.
      // TODO(https://github.com/google/closure-compiler/pull/3913): Remove if/when upstream closure is fixed.
      /** @suppress {checkTypes} */ var result = WebAssembly.instantiateStreaming(response, imports);
      return result.then(callback, function(reason) {
        // We expect the most common failure cause to be a bad MIME type for the binary,
        // in which case falling back to ArrayBuffer instantiation should work.
        err(`wasm streaming compile failed: ${reason}`);
        err("falling back to ArrayBuffer instantiation");
        return instantiateArrayBuffer(binaryFile, imports, callback);
      });
    });
  }
  return instantiateArrayBuffer(binaryFile, imports, callback);
}

function getWasmImports() {
  // prepare imports
  return {
    "a": wasmImports
  };
}

// Create the wasm instance.
// Receives the wasm imports, returns the exports.
function createWasm() {
 var info = {
  "a": asmLibraryArg
  var info = getWasmImports();
  // Load the wasm module and create an instance of using native support in the JS engine.
  // handle a generated wasm instance, receiving its exports and
  // performing other necessary setup
 };
 function receiveInstance(instance, module) {
  var exports = instance.exports;
  /** @param {WebAssembly.Module=} module*/ function receiveInstance(instance, module) {
    wasmExports = instance.exports;
  Module["asm"] = exports;
  wasmMemory = Module["asm"]["d"];
  updateMemoryViews();
    wasmMemory = wasmExports["d"];
    updateMemoryViews();
  wasmTable = Module["asm"]["g"];
  addOnInit(Module["asm"]["e"]);
  removeRunDependency("wasm-instantiate");
 }
 addRunDependency("wasm-instantiate");
 function receiveInstantiationResult(result) {
  receiveInstance(result["instance"]);
 }
    addOnInit(wasmExports["e"]);
    removeRunDependency("wasm-instantiate");
    return wasmExports;
  }
  // wait for the pthread pool (if any)
  addRunDependency("wasm-instantiate");
  // Prefer streaming instantiation if available.
  function receiveInstantiationResult(result) {
    // 'result' is a ResultObject object which has both the module and instance.
    // receiveInstance() will swap in the exports (to Module.asm) so they can be called
    // TODO: Due to Closure regression https://github.com/google/closure-compiler/issues/3193, the above line no longer optimizes out down to the following line.
    // When the regression is fixed, can restore the above PTHREADS-enabled path.
    receiveInstance(result["instance"]);
  }
 function instantiateArrayBuffer(receiver) {
  return getBinaryPromise().then(function(binary) {
   return WebAssembly.instantiate(binary, info);
  }).then(function(instance) {
   return instance;
  }).then(receiver, function(reason) {
   err("failed to asynchronously prepare wasm: " + reason);
   abort(reason);
  });
 }
 function instantiateAsync() {
  if (!wasmBinary && typeof WebAssembly.instantiateStreaming == "function" && !isDataURI(wasmBinaryFile) && typeof fetch == "function") {
   return fetch(wasmBinaryFile, {
    credentials: "same-origin"
   }).then(function(response) {
    var result = WebAssembly.instantiateStreaming(response, info);
    return result.then(receiveInstantiationResult, function(reason) {
     err("wasm streaming compile failed: " + reason);
  // User shell pages can write their own Module.instantiateWasm = function(imports, successCallback) callback
  // to manually instantiate the Wasm module themselves. This allows pages to
  // run the instantiation parallel to any other async startup actions they are
  // performing.
     err("falling back to ArrayBuffer instantiation");
     return instantiateArrayBuffer(receiveInstantiationResult);
    });
   });
  } else {
  // Also pthreads and wasm workers initialize the wasm instance through this
  // path.
   return instantiateArrayBuffer(receiveInstantiationResult);
  }
 }
 if (Module["instantiateWasm"]) {
  try {
   var exports = Module["instantiateWasm"](info, receiveInstance);
  if (Module["instantiateWasm"]) {
    try {
      return Module["instantiateWasm"](info, receiveInstance);
   return exports;
  } catch (e) {
   err("Module.instantiateWasm callback failed with error: " + e);
   readyPromiseReject(e);
  }
 }
 instantiateAsync().catch(readyPromiseReject);
 return {};
    } catch (e) {
      err(`Module.instantiateWasm callback failed with error: ${e}`);
      // If instantiation fails, reject the module ready promise.
      readyPromiseReject(e);
    }
  }
  wasmBinaryFile ??= findWasmBinary();
  // If instantiation fails, reject the module ready promise.
  instantiateAsync(wasmBinary, wasmBinaryFile, info, receiveInstantiationResult).catch(readyPromiseReject);
  return {};
}

// include: runtime_debug.js
// end include: runtime_debug.js
// === Body ===
// end include: preamble.js
/** @constructor */ function ExitStatus(status) {
  this.name = "ExitStatus";
  this.message = `Program terminated with exit(${status})`;
  this.status = status;
}

var callRuntimeCallbacks = callbacks => {
  // Pass the module as the first argument.
  callbacks.forEach(f => f(Module));
};

/**

var tempDouble;

var tempI64;
     * @param {number} ptr
     * @param {string} type
     */ function getValue(ptr, type = "i8") {
  if (type.endsWith("*")) type = "*";
  switch (type) {
   case "i1":
    return HEAP8[ptr];

function ExitStatus(status) {
 this.name = "ExitStatus";
   case "i8":
 this.message = "Program terminated with exit(" + status + ")";
 this.status = status;
}
    return HEAP8[ptr];

   case "i16":
    return HEAP16[((ptr) >> 1)];

function callRuntimeCallbacks(callbacks) {
 while (callbacks.length > 0) {
  callbacks.shift()(Module);
 }
}
   case "i32":
    return HEAP32[((ptr) >> 2)];

   case "i64":
    abort("to do getValue(i64) use WASM_BIGINT");

   case "float":
    return HEAPF32[((ptr) >> 2)];

function getValue(ptr, type = "i8") {
 if (type.endsWith("*")) type = "*";
 switch (type) {
 case "i1":
  return HEAP8[ptr >> 0];
   case "double":
    return HEAPF64[((ptr) >> 3)];

 case "i8":
  return HEAP8[ptr >> 0];
   case "*":
    return HEAPU32[((ptr) >> 2)];

   default:
    abort(`invalid type for getValue: ${type}`);
  }
}

var noExitRuntime = Module["noExitRuntime"] || true;

/**
     * @param {number} ptr
     * @param {number} value
     * @param {string} type
     */ function setValue(ptr, value, type = "i8") {
  if (type.endsWith("*")) type = "*";
  switch (type) {
   case "i1":
    HEAP8[ptr] = value;
    break;

   case "i8":
    HEAP8[ptr] = value;
    break;

 case "i16":
  return HEAP16[ptr >> 1];
   case "i16":
    HEAP16[((ptr) >> 1)] = value;
    break;

 case "i32":
  return HEAP32[ptr >> 2];
   case "i32":
    HEAP32[((ptr) >> 2)] = value;
    break;

 case "i64":
   case "i64":
  return HEAP32[ptr >> 2];
    abort("to do setValue(i64) use WASM_BIGINT");

 case "float":
  return HEAPF32[ptr >> 2];
   case "float":
    HEAPF32[((ptr) >> 2)] = value;
    break;

 case "double":
  return HEAPF64[ptr >> 3];
   case "double":
    HEAPF64[((ptr) >> 3)] = value;
    break;

 case "*":
  return HEAPU32[ptr >> 2];
   case "*":
    HEAPU32[((ptr) >> 2)] = value;
    break;

 default:
   default:
  abort("invalid type for getValue: " + type);
 }
 return null;
}

    abort(`invalid type for setValue: ${type}`);
  }
function setValue(ptr, value, type = "i8") {
 if (type.endsWith("*")) type = "*";
 switch (type) {
 case "i1":
  HEAP8[ptr >> 0] = value;
  break;

 case "i8":
  HEAP8[ptr >> 0] = value;
}

var stackRestore = val => __emscripten_stack_restore(val);
  break;

 case "i16":
  HEAP16[ptr >> 1] = value;
  break;
var stackSave = () => _emscripten_stack_get_current();

 case "i32":
  HEAP32[ptr >> 2] = value;
  break;
var UTF8Decoder = typeof TextDecoder != "undefined" ? new TextDecoder : undefined;

/**
     * Given a pointer 'idx' to a null-terminated UTF8-encoded string in the given
     * array that contains uint8 values, returns a copy of that string as a
 case "i64":
     * Javascript String object.
  tempI64 = [ value >>> 0, (tempDouble = value, +Math.abs(tempDouble) >= 1 ? tempDouble > 0 ? (Math.min(+Math.floor(tempDouble / 4294967296), 4294967295) | 0) >>> 0 : ~~+Math.ceil((tempDouble - +(~~tempDouble >>> 0)) / 4294967296) >>> 0 : 0) ], 
  HEAP32[ptr >> 2] = tempI64[0], HEAP32[ptr + 4 >> 2] = tempI64[1];
  break;

 case "float":
     * heapOrArray is either a regular array, or a JavaScript typed array view.
     * @param {number=} idx
     * @param {number=} maxBytesToRead
     * @return {string}
     */ var UTF8ArrayToString = (heapOrArray, idx = 0, maxBytesToRead = NaN) => {
  var endIdx = idx + maxBytesToRead;
  var endPtr = idx;
  // TextDecoder needs to know the byte length in advance, it doesn't stop on
  // null terminator by itself.  Also, use the length info to avoid running tiny
  // strings through TextDecoder, since .subarray() allocates garbage.
  // (As a tiny code save trick, compare endPtr against endIdx using a negation,
  // so that undefined/NaN means Infinity)
  while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr;
  if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) {
    return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr));
  }
  var str = "";
  HEAPF32[ptr >> 2] = value;
  break;

  // If building with TextDecoder, we have already computed the string length
  // above, so test loop end condition against that
  while (idx < endPtr) {
    // For UTF8 byte structure, see:
    // http://en.wikipedia.org/wiki/UTF-8#Description
    // https://www.ietf.org/rfc/rfc2279.txt
    // https://tools.ietf.org/html/rfc3629
    var u0 = heapOrArray[idx++];
    if (!(u0 & 128)) {
      str += String.fromCharCode(u0);
      continue;
    }
 case "double":
  HEAPF64[ptr >> 3] = value;
    var u1 = heapOrArray[idx++] & 63;
    if ((u0 & 224) == 192) {
      str += String.fromCharCode(((u0 & 31) << 6) | u1);
      continue;
  break;

 case "*":
    }
    var u2 = heapOrArray[idx++] & 63;
    if ((u0 & 240) == 224) {
      u0 = ((u0 & 15) << 12) | (u1 << 6) | u2;
    } else {
  HEAPU32[ptr >> 2] = value;
  break;

 default:
      u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63);
    }
    if (u0 < 65536) {
      str += String.fromCharCode(u0);
    } else {
  abort("invalid type for setValue: " + type);
 }
}

      var ch = u0 - 65536;
      str += String.fromCharCode(55296 | (ch >> 10), 56320 | (ch & 1023));
    }
  }
  return str;
function ___assert_fail(condition, filename, line, func) {
 abort("Assertion failed: " + UTF8ToString(condition) + ", at: " + [ filename ? UTF8ToString(filename) : "unknown filename", line, func ? UTF8ToString(func) : "unknown function" ]);
}
};

function abortOnCannotGrowMemory(requestedSize) {
 abort("OOM");
}

/**
     * Given a pointer 'ptr' to a null-terminated UTF8-encoded string in the
     * emscripten HEAP, returns a copy of that string as a Javascript String object.
     *
function _emscripten_resize_heap(requestedSize) {
 var oldSize = HEAPU8.length;
 requestedSize = requestedSize >>> 0;
 abortOnCannotGrowMemory(requestedSize);
}
     * @param {number} ptr
     * @param {number=} maxBytesToRead - An optional length that specifies the
     *   maximum number of bytes to read. You can omit this parameter to scan the
     *   string until the first 0 byte. If maxBytesToRead is passed, and the string
     *   at [ptr, ptr+maxBytesToReadr[ contains a null byte in the middle, then the
     *   string will cut short at that byte index (i.e. maxBytesToRead will not
     *   produce a string of exact length [ptr, ptr+maxBytesToRead[) N.B. mixing
     *   frequent uses of UTF8ToString() with and without maxBytesToRead may throw
     *   JS JIT optimizations off, so it is worth to consider consistently using one
     * @return {string}
     */ var UTF8ToString = (ptr, maxBytesToRead) => ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : "";

var SYSCALLS = {
var ___assert_fail = (condition, filename, line, func) => {
 varargs: undefined,
 get: function() {
  SYSCALLS.varargs += 4;
  var ret = HEAP32[SYSCALLS.varargs - 4 >> 2];
  return ret;
 },
 getStr: function(ptr) {
  var ret = UTF8ToString(ptr);
  abort(`Assertion failed: ${UTF8ToString(condition)}, at: ` + [ filename ? UTF8ToString(filename) : "unknown filename", line, func ? UTF8ToString(func) : "unknown function" ]);
};

var abortOnCannotGrowMemory = requestedSize => {
  return ret;
 }
  abort("OOM");
};

var _emscripten_resize_heap = requestedSize => {
  var oldSize = HEAPU8.length;
  // With CAN_ADDRESS_2GB or MEMORY64, pointers are already unsigned.
  requestedSize >>>= 0;
  abortOnCannotGrowMemory(requestedSize);
};

var runtimeKeepaliveCounter = 0;

var keepRuntimeAlive = () => noExitRuntime || runtimeKeepaliveCounter > 0;

function _proc_exit(code) {
 EXITSTATUS = code;
 if (!keepRuntimeAlive()) {
  if (Module["onExit"]) Module["onExit"](code);
  ABORT = true;
 }
 quit_(code, new ExitStatus(code));
}
var _proc_exit = code => {
  EXITSTATUS = code;
  if (!keepRuntimeAlive()) {
    Module["onExit"]?.(code);
    ABORT = true;
  }
  quit_(code, new ExitStatus(code));
};

function exitJS(status, implicit) {
 EXITSTATUS = status;
 _proc_exit(status);
}
/** @suppress {duplicate } */ /** @param {boolean|number=} implicit */ var exitJS = (status, implicit) => {
  EXITSTATUS = status;
  _proc_exit(status);
};

var _exit = exitJS;

function getCFunc(ident) {
 var func = Module["_" + ident];
 return func;
}
var getCFunc = ident => {
  var func = Module["_" + ident];
  // closure exported function
  return func;
};

function writeArrayToMemory(array, buffer) {
 HEAP8.set(array, buffer);
}

function ccall(ident, returnType, argTypes, args, opts) {
 var toC = {
  "string": str => {
   var ret = 0;
   if (str !== null && str !== undefined && str !== 0) {
var writeArrayToMemory = (array, buffer) => {
  HEAP8.set(array, buffer);
};

var lengthBytesUTF8 = str => {
  var len = 0;
  for (var i = 0; i < str.length; ++i) {
    // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code
    // unit, not a Unicode code point of the character! So decode
    // UTF16->UTF32->UTF8.
    // See http://unicode.org/faq/utf_bom.html#utf16-3
    var c = str.charCodeAt(i);
    // possibly a lead surrogate
    if (c <= 127) {
      len++;
    } else if (c <= 2047) {
      len += 2;
    } else if (c >= 55296 && c <= 57343) {
      len += 4;
      ++i;
    } else {
      len += 3;
    }
  }
  return len;
};

var stringToUTF8Array = (str, heap, outIdx, maxBytesToWrite) => {
  // Parameter maxBytesToWrite is not optional. Negative values, 0, null,
  // undefined and false each don't write out any bytes.
  if (!(maxBytesToWrite > 0)) return 0;
  var startIdx = outIdx;
  var endIdx = outIdx + maxBytesToWrite - 1;
  // -1 for string null terminator.
  for (var i = 0; i < str.length; ++i) {
    // Gotcha: charCodeAt returns a 16-bit word that is a UTF-16 encoded code
    // unit, not a Unicode code point of the character! So decode
    // UTF16->UTF32->UTF8.
    // See http://unicode.org/faq/utf_bom.html#utf16-3
    // For UTF8 byte structure, see http://en.wikipedia.org/wiki/UTF-8#Description
    // and https://www.ietf.org/rfc/rfc2279.txt
    // and https://tools.ietf.org/html/rfc3629
    var u = str.charCodeAt(i);
    // possibly a lead surrogate
    if (u >= 55296 && u <= 57343) {
      var u1 = str.charCodeAt(++i);
      u = 65536 + ((u & 1023) << 10) | (u1 & 1023);
    }
    if (u <= 127) {
      if (outIdx >= endIdx) break;
      heap[outIdx++] = u;
    } else if (u <= 2047) {
      if (outIdx + 1 >= endIdx) break;
      heap[outIdx++] = 192 | (u >> 6);
      heap[outIdx++] = 128 | (u & 63);
    } else if (u <= 65535) {
      if (outIdx + 2 >= endIdx) break;
      heap[outIdx++] = 224 | (u >> 12);
      heap[outIdx++] = 128 | ((u >> 6) & 63);
      heap[outIdx++] = 128 | (u & 63);
    } else {
      if (outIdx + 3 >= endIdx) break;
      heap[outIdx++] = 240 | (u >> 18);
      heap[outIdx++] = 128 | ((u >> 12) & 63);
      heap[outIdx++] = 128 | ((u >> 6) & 63);
      heap[outIdx++] = 128 | (u & 63);
    }
  }
  // Null-terminate the pointer to the buffer.
  heap[outIdx] = 0;
  return outIdx - startIdx;
};

var stringToUTF8 = (str, outPtr, maxBytesToWrite) => stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite);

var stackAlloc = sz => __emscripten_stack_alloc(sz);

var stringToUTF8OnStack = str => {
  var size = lengthBytesUTF8(str) + 1;
  var ret = stackAlloc(size);
  stringToUTF8(str, ret, size);
  return ret;
};

/**
     * @param {string|null=} returnType
     * @param {Array=} argTypes
     * @param {Arguments|Array=} args
     * @param {Object=} opts
     */ var ccall = (ident, returnType, argTypes, args, opts) => {
  // For fast lookup of conversion functions
  var toC = {
    "string": str => {
      var ret = 0;
      if (str !== null && str !== undefined && str !== 0) {
    var len = (str.length << 2) + 1;
    ret = stackAlloc(len);
    stringToUTF8(str, ret, len);
   }
   return ret;
  },
  "array": arr => {
   var ret = stackAlloc(arr.length);
   writeArrayToMemory(arr, ret);
   return ret;
  }
 };
 function convertReturnValue(ret) {
  if (returnType === "string") {
   return UTF8ToString(ret);
  }
  if (returnType === "boolean") return Boolean(ret);
  return ret;
 }
 var func = getCFunc(ident);
 var cArgs = [];
 var stack = 0;
 if (args) {
  for (var i = 0; i < args.length; i++) {
   var converter = toC[argTypes[i]];
   if (converter) {
    if (stack === 0) stack = stackSave();
    cArgs[i] = converter(args[i]);
   } else {
    cArgs[i] = args[i];
   }
  }
 }
 var ret = func.apply(null, cArgs);
 function onDone(ret) {
  if (stack !== 0) stackRestore(stack);
  return convertReturnValue(ret);
 }
 ret = onDone(ret);
 return ret;
}
        // null string
        ret = stringToUTF8OnStack(str);
      }
      return ret;
    },
    "array": arr => {
      var ret = stackAlloc(arr.length);
      writeArrayToMemory(arr, ret);
      return ret;
    }
  };
  function convertReturnValue(ret) {
    if (returnType === "string") {
      return UTF8ToString(ret);
    }
    if (returnType === "boolean") return Boolean(ret);
    return ret;
  }
  var func = getCFunc(ident);
  var cArgs = [];
  var stack = 0;
  if (args) {
    for (var i = 0; i < args.length; i++) {
      var converter = toC[argTypes[i]];
      if (converter) {
        if (stack === 0) stack = stackSave();
        cArgs[i] = converter(args[i]);
      } else {
        cArgs[i] = args[i];
      }
    }
  }
  var ret = func(...cArgs);
  function onDone(ret) {
    if (stack !== 0) stackRestore(stack);
    return convertReturnValue(ret);
  }
  ret = onDone(ret);
  return ret;
};

/**
     * @param {string=} returnType
     * @param {Array=} argTypes
     * @param {Object=} opts
function cwrap(ident, returnType, argTypes, opts) {
     */ var cwrap = (ident, returnType, argTypes, opts) => {
 argTypes = argTypes || [];
 var numericArgs = argTypes.every(type => type === "number" || type === "boolean");
 var numericRet = returnType !== "string";
 if (numericRet && numericArgs && !opts) {
  return getCFunc(ident);
 }
  // When the function takes numbers and returns a number, we can just return
  // the original function
  var numericArgs = !argTypes || argTypes.every(type => type === "number" || type === "boolean");
  var numericRet = returnType !== "string";
  if (numericRet && numericArgs && !opts) {
    return getCFunc(ident);
  }
 return function() {
  return ccall(ident, returnType, argTypes, arguments, opts);
 };
  return (...args) => ccall(ident, returnType, argTypes, args, opts);
};
}

var asmLibraryArg = {
 "a": ___assert_fail,
 "b": _emscripten_resize_heap,
 "c": _exit
};

var asm = createWasm();

var ___wasm_call_ctors = Module["___wasm_call_ctors"] = function() {
 return (___wasm_call_ctors = Module["___wasm_call_ctors"] = Module["asm"]["e"]).apply(null, arguments);
var wasmImports = {
  /** @export */ a: ___assert_fail,
  /** @export */ b: _emscripten_resize_heap,
  /** @export */ c: _exit
};

var _pikchr = Module["_pikchr"] = function() {
 return (_pikchr = Module["_pikchr"] = Module["asm"]["f"]).apply(null, arguments);
var wasmExports = createWasm();

};
var ___wasm_call_ctors = () => (___wasm_call_ctors = wasmExports["e"])();

var stackSave = Module["stackSave"] = function() {
var _pikchr_version = Module["_pikchr_version"] = () => (_pikchr_version = Module["_pikchr_version"] = wasmExports["g"])();
 return (stackSave = Module["stackSave"] = Module["asm"]["h"]).apply(null, arguments);
};

var stackRestore = Module["stackRestore"] = function() {
 return (stackRestore = Module["stackRestore"] = Module["asm"]["i"]).apply(null, arguments);
var _pikchr = Module["_pikchr"] = (a0, a1, a2, a3, a4) => (_pikchr = Module["_pikchr"] = wasmExports["h"])(a0, a1, a2, a3, a4);

};
var __emscripten_stack_restore = a0 => (__emscripten_stack_restore = wasmExports["i"])(a0);

var stackAlloc = Module["stackAlloc"] = function() {
 return (stackAlloc = Module["stackAlloc"] = Module["asm"]["j"]).apply(null, arguments);
var __emscripten_stack_alloc = a0 => (__emscripten_stack_alloc = wasmExports["j"])(a0);

};
var _emscripten_stack_get_current = () => (_emscripten_stack_get_current = wasmExports["k"])();

// include: postamble.js
// === Auto-generated postamble setup entry stuff ===
Module["stackSave"] = stackSave;

Module["stackRestore"] = stackRestore;

Module["stackAlloc"] = stackAlloc;

Module["ccall"] = ccall;

Module["cwrap"] = cwrap;

Module["setValue"] = setValue;

Module["getValue"] = getValue;

var calledRun;

var calledPrerun;

dependenciesFulfilled = function runCaller() {
  // If run has never been called, and we should call run (INVOKE_RUN is true, and Module.noInitialRun is not false)
 if (!calledRun) run();
 if (!calledRun) dependenciesFulfilled = runCaller;
  if (!calledRun) run();
  if (!calledRun) dependenciesFulfilled = runCaller;
};

// try this again later, after new deps are fulfilled
function run(args) {
function run() {
 args = args || arguments_;
 if (runDependencies > 0) {
  return;
 }
 preRun();
 if (runDependencies > 0) {
  return;
 }
 function doRun() {
  if (calledRun) return;
  calledRun = true;
  Module["calledRun"] = true;
  if (ABORT) return;
  initRuntime();
  readyPromiseResolve(Module);
  if (Module["onRuntimeInitialized"]) Module["onRuntimeInitialized"]();
  postRun();
 }
 if (Module["setStatus"]) {
  Module["setStatus"]("Running...");
  setTimeout(function() {
  if (runDependencies > 0) {
    return;
  }
  if (!calledPrerun) {
    calledPrerun = 1;
    preRun();
    // a preRun added a dependency, run will be called later
    if (runDependencies > 0) {
      return;
    }
  }
  function doRun() {
    // run may have just been called through dependencies being fulfilled just in this very frame,
    // or while the async setStatus time below was happening
    if (calledRun) return;
    calledRun = 1;
    Module["calledRun"] = 1;
    if (ABORT) return;
    initRuntime();
    readyPromiseResolve(Module);
    Module["onRuntimeInitialized"]?.();
    postRun();
  }
  if (Module["setStatus"]) {
    Module["setStatus"]("Running...");
    setTimeout(() => {
   setTimeout(function() {
    Module["setStatus"]("");
      setTimeout(() => Module["setStatus"](""), 1);
   }, 1);
   doRun();
  }, 1);
 } else {
  doRun();
 }
      doRun();
    }, 1);
  } else {
    doRun();
  }
}

if (Module["preInit"]) {
 if (typeof Module["preInit"] == "function") Module["preInit"] = [ Module["preInit"] ];
 while (Module["preInit"].length > 0) {
  Module["preInit"].pop()();
 }
  if (typeof Module["preInit"] == "function") Module["preInit"] = [ Module["preInit"] ];
  while (Module["preInit"].length > 0) {
    Module["preInit"].pop()();
  }
}

run();

// end include: postamble.js
// include: postamble_modularize.js
// In MODULARIZE mode we wrap the generated code in a factory function
// and return either the Module itself, or a promise of the module.
// We assign to the `moduleRtn` global here and configure closure to see
// this as and extern so it won't get minified.
moduleRtn = readyPromise;

  return initPikchrModule.ready

  return moduleRtn;
}
);
})();
if (typeof exports === 'object' && typeof module === 'object')
  module.exports = initPikchrModule;
else if (typeof define === 'function' && define['amd'])
  define([], function() { return initPikchrModule; });
else if (typeof exports === 'object')
  exports["initPikchrModule"] = initPikchrModule;
  define([], () => initPikchrModule);
Changes to extsrc/pikchr.wasm.

cannot compute difference between binary files

Added extsrc/qrf.h.








































































































































































































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
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/*
** 2025-10-20
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
*************************************************************************
** Header file for the Result-Format or "resfmt" utility library for SQLite.
** See the resfmt.md documentation for additional information.
*/
#ifndef SQLITE_QRF_H
#define SQLITE_QRF_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdlib.h>
#include "sqlite3.h"

/*
** Specification used by clients to define the output format they want
*/
typedef struct sqlite3_qrf_spec sqlite3_qrf_spec;
struct sqlite3_qrf_spec {
  unsigned char iVersion;     /* Version number of this structure */
  unsigned char eStyle;       /* Formatting style.  "box", "csv", etc... */
  unsigned char eEsc;         /* How to escape control characters in text */
  unsigned char eText;        /* Quoting style for text */
  unsigned char eTitle;       /* Quating style for the text of column names */
  unsigned char eBlob;        /* Quoting style for BLOBs */
  unsigned char bTitles;      /* True to show column names */
  unsigned char bWordWrap;    /* Try to wrap on word boundaries */
  unsigned char bTextJsonb;   /* Render JSONB blobs as JSON text */
  unsigned char eDfltAlign;   /* Default alignment, no covered by aAlignment */
  unsigned char eTitleAlign;  /* Alignment for column headers */
  unsigned char bSplitColumn; /* Wrap single-column output into many columns */
  unsigned char bBorder;      /* Show outer border in Box and Table styles */
  short int nWrap;            /* Wrap columns wider than this */
  short int nScreenWidth;     /* Maximum overall table width */
  short int nLineLimit;       /* Maximum number of lines for any row */
  short int nTitleLimit;      /* Maximum number of characters in a title */
  int nCharLimit;             /* Maximum number of characters in a cell */
  int nWidth;                 /* Number of entries in aWidth[] */
  int nAlign;                 /* Number of entries in aAlignment[] */
  short int *aWidth;          /* Column widths */
  unsigned char *aAlign;      /* Column alignments */
  char *zColumnSep;           /* Alternative column separator */
  char *zRowSep;              /* Alternative row separator */
  char *zTableName;           /* Output table name */
  char *zNull;                /* Rendering of NULL */
  char *(*xRender)(void*,sqlite3_value*);           /* Render a value */
  int (*xWrite)(void*,const char*,sqlite3_int64);   /* Write output */
  void *pRenderArg;           /* First argument to the xRender callback */
  void *pWriteArg;            /* First argument to the xWrite callback */
  char **pzOutput;            /* Storage location for output string */
  /* Additional fields may be added in the future */
};

/*
** Interfaces
*/
int sqlite3_format_query_result(
  sqlite3_stmt *pStmt,             /* SQL statement to run */
  const sqlite3_qrf_spec *pSpec,   /* Result format specification */
  char **pzErr                     /* OUT: Write error message here */
);

/*
** Range of values for sqlite3_qrf_spec.aWidth[] entries and for
** sqlite3_qrf_spec.mxColWidth and .nScreenWidth
*/
#define QRF_MAX_WIDTH    10000
#define QRF_MIN_WIDTH    0

/*
** Output styles:
*/
#define QRF_STYLE_Auto      0 /* Choose a style automatically */
#define QRF_STYLE_Box       1 /* Unicode box-drawing characters */
#define QRF_STYLE_Column    2 /* One record per line in neat columns */
#define QRF_STYLE_Count     3 /* Output only a count of the rows of output */
#define QRF_STYLE_Csv       4 /* Comma-separated-value */
#define QRF_STYLE_Eqp       5 /* Format EXPLAIN QUERY PLAN output */
#define QRF_STYLE_Explain   6 /* EXPLAIN output */
#define QRF_STYLE_Html      7 /* Generate an XHTML table */
#define QRF_STYLE_Insert    8 /* Generate SQL "insert" statements */
#define QRF_STYLE_Json      9 /* Output is a list of JSON objects */
#define QRF_STYLE_JObject  10 /* Independent JSON objects for each row */
#define QRF_STYLE_Line     11 /* One column per line. */
#define QRF_STYLE_List     12 /* One record per line with a separator */
#define QRF_STYLE_Markdown 13 /* Markdown formatting */
#define QRF_STYLE_Off      14 /* No query output shown */
#define QRF_STYLE_Quote    15 /* SQL-quoted, comma-separated */
#define QRF_STYLE_Stats    16 /* EQP-like output but with performance stats */
#define QRF_STYLE_StatsEst 17 /* EQP-like output with planner estimates */
#define QRF_STYLE_StatsVm  18 /* EXPLAIN-like output with performance stats */
#define QRF_STYLE_Table    19 /* MySQL-style table formatting */

/*
** Quoting styles for text.
** Allowed values for sqlite3_qrf_spec.eText
*/
#define QRF_TEXT_Auto    0 /* Choose text encoding automatically */
#define QRF_TEXT_Plain   1 /* Literal text */
#define QRF_TEXT_Sql     2 /* Quote as an SQL literal */
#define QRF_TEXT_Csv     3 /* CSV-style quoting */
#define QRF_TEXT_Html    4 /* HTML-style quoting */
#define QRF_TEXT_Tcl     5 /* C/Tcl quoting */
#define QRF_TEXT_Json    6 /* JSON quoting */
#define QRF_TEXT_Relaxed 7 /* Relaxed SQL quoting */

/*
** Quoting styles for BLOBs
** Allowed values for sqlite3_qrf_spec.eBlob
*/
#define QRF_BLOB_Auto    0 /* Determine BLOB quoting using eText */
#define QRF_BLOB_Text    1 /* Display content exactly as it is */
#define QRF_BLOB_Sql     2 /* Quote as an SQL literal */
#define QRF_BLOB_Hex     3 /* Hexadecimal representation */
#define QRF_BLOB_Tcl     4 /* "\000" notation */
#define QRF_BLOB_Json    5 /* A JSON string */
#define QRF_BLOB_Size    6 /* Display the blob size only */

/*
** Control-character escape modes.
** Allowed values for sqlite3_qrf_spec.eEsc
*/
#define QRF_ESC_Auto    0 /* Choose the ctrl-char escape automatically */
#define QRF_ESC_Off     1 /* Do not escape control characters */
#define QRF_ESC_Ascii   2 /* Unix-style escapes.  Ex: U+0007 shows ^G */
#define QRF_ESC_Symbol  3 /* Unicode escapes. Ex: U+0007 shows U+2407 */

/*
** Allowed values for "boolean" fields, such as "bColumnNames", "bWordWrap",
** and "bTextJsonb".  There is an extra "auto" variants so these are actually
** tri-state settings, not booleans.
*/
#define QRF_SW_Auto     0 /* Let QRF choose the best value */
#define QRF_SW_Off      1 /* This setting is forced off */
#define QRF_SW_On       2 /* This setting is forced on */
#define QRF_Auto        0 /* Alternate spelling for QRF_*_Auto */
#define QRF_No          1 /* Alternate spelling for QRF_SW_Off */
#define QRF_Yes         2 /* Alternate spelling for QRF_SW_On */

/*
** Possible alignment values alignment settings
**
**                             Horizontal   Vertial
**                             ----------   --------  */
#define QRF_ALIGN_Auto    0 /*   auto        auto     */
#define QRF_ALIGN_Left    1 /*   left        auto     */
#define QRF_ALIGN_Center  2 /*   center      auto     */
#define QRF_ALIGN_Right   3 /*   right       auto     */
#define QRF_ALIGN_Top     4 /*   auto        top      */
#define QRF_ALIGN_NW      5 /*   left        top      */
#define QRF_ALIGN_N       6 /*   center      top      */
#define QRF_ALIGN_NE      7 /*   right       top      */
#define QRF_ALIGN_Middle  8 /*   auto        middle   */
#define QRF_ALIGN_W       9 /*   left        middle   */
#define QRF_ALIGN_C      10 /*   center      middle   */
#define QRF_ALIGN_E      11 /*   right       middle   */
#define QRF_ALIGN_Bottom 12 /*   auto        bottom   */
#define QRF_ALIGN_SW     13 /*   left        bottom   */
#define QRF_ALIGN_S      14 /*   center      bottom   */
#define QRF_ALIGN_SE     15 /*   right       bottom   */
#define QRF_ALIGN_HMASK   3 /* Horizontal alignment mask */
#define QRF_ALIGN_VMASK  12 /* Vertical alignment mask */

/*
** Auxiliary routines contined within this module that might be useful
** in other contexts, and which are therefore exported.
*/
/*
** Return an estimate of the width, in columns, for the single Unicode
** character c.  For normal characters, the answer is always 1.  But the
** estimate might be 0 or 2 for zero-width and double-width characters.
**
** Different devices display unicode using different widths.  So
** it is impossible to know that true display width with 100% accuracy.
** Inaccuracies in the width estimates might cause columns to be misaligned.
** Unfortunately, there is nothing we can do about that.
*/
int sqlite3_qrf_wcwidth(int c);

/*
** Return an estimate of the number of display columns used by the
** string in the argument.  The width of individual characters is
** determined as for sqlite3_qrf_wcwidth().  VT100 escape code sequences
** are assigned a width of zero.
*/
size_t sqlite3_qrf_wcswidth(const char*);


#ifdef __cplusplus
}
#endif
#endif /* !defined(SQLITE_QRF_H) */
Changes to extsrc/shell.c.

more than 10,000 changes

Changes to extsrc/sqlite3.c.

more than 10,000 changes

Changes to extsrc/sqlite3.h.
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
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







-
+












-
-
-
+
+
+
+
+
+



















-
-
-
+
+
+







** The SQLITE_VERSION_NUMBER for any given release of SQLite will also
** be larger than the release from which it is derived.  Either Y will
** be held constant and Z will be incremented or else Y will be incremented
** and Z will be reset to zero.
**
** Since [version 3.6.18] ([dateof:3.6.18]),
** SQLite source code has been stored in the
** <a href="http://www.fossil-scm.org/">Fossil configuration management
** <a href="http://fossil-scm.org/">Fossil configuration management
** system</a>.  ^The SQLITE_SOURCE_ID macro evaluates to
** a string which identifies a particular check-in of SQLite
** within its configuration management system.  ^The SQLITE_SOURCE_ID
** string contains the date and time of the check-in (UTC) and a SHA1
** or SHA3-256 hash of the entire source tree.  If the source code has
** been edited in any way since it was last checked in, then the last
** four hexadecimal digits of the hash may be modified.
**
** See also: [sqlite3_libversion()],
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
#define SQLITE_VERSION        "3.47.0"
#define SQLITE_VERSION_NUMBER 3047000
#define SQLITE_SOURCE_ID      "2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f28411c85dda156f82636e"
#define SQLITE_VERSION        "3.52.0"
#define SQLITE_VERSION_NUMBER 3052000
#define SQLITE_SOURCE_ID      "2026-02-04 20:51:27 c476d956d0bd3065cf894de6f9d393b999ff7d2268a35f01a6d88804789ab58f"
#define SQLITE_SCM_BRANCH     "trunk"
#define SQLITE_SCM_TAGS       ""
#define SQLITE_SCM_DATETIME   "2026-02-04T20:51:27.822Z"

/*
** CAPI3REF: Run-Time Library Version Numbers
** KEYWORDS: sqlite3_version sqlite3_sourceid
**
** These interfaces provide the same information as the [SQLITE_VERSION],
** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros
** but are associated with the library instead of the header file.  ^(Cautious
** programmers might include assert() statements in their application to
** verify that values returned by these interfaces match the macros in
** the header, and thus ensure that the application is
** compiled with matching library and header files.
**
** <blockquote><pre>
** assert( sqlite3_libversion_number()==SQLITE_VERSION_NUMBER );
** assert( strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,80)==0 );
** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 );
** </pre></blockquote>)^
**
** ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION]
** macro.  ^The sqlite3_libversion() function returns a pointer to the
** to the sqlite3_version[] string constant.  The sqlite3_libversion()
** ^The sqlite3_version[] string constant contains the text of the
** [SQLITE_VERSION] macro.  ^The sqlite3_libversion() function returns a
** pointer to the sqlite3_version[] string constant.  The sqlite3_libversion()
** function is provided for use in DLLs since DLL users usually do not have
** direct access to string constants within the DLL.  ^The
** sqlite3_libversion_number() function returns an integer equal to
** [SQLITE_VERSION_NUMBER].  ^(The sqlite3_sourceid() function returns
** a pointer to a string constant whose value is the same as the
** [SQLITE_SOURCE_ID] C preprocessor macro.  Except if SQLite is built
** using an edited copy of [the amalgamation], then the last four characters
366
367
368
369
370
371
372
373

374
375
376
377
378
379
380
369
370
371
372
373
374
375

376
377
378
379
380
381
382
383







-
+







**
** The sqlite3_exec() interface is a convenience wrapper around
** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()],
** that allows an application to run multiple statements of SQL
** without having to use a lot of C code.
**
** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded,
** semicolon-separate SQL statements passed into its 2nd argument,
** semicolon-separated SQL statements passed into its 2nd argument,
** in the context of the [database connection] passed in as its 1st
** argument.  ^If the callback function of the 3rd argument to
** sqlite3_exec() is not NULL, then it is invoked for each result row
** coming out of the evaluated SQL statements.  ^The 4th argument to
** sqlite3_exec() is relayed through to the 1st argument of each
** callback invocation.  ^If the callback pointer to sqlite3_exec()
** is NULL, then no callback is ever invoked and result rows are
399
400
401
402
403
404
405
406

407
408
409
410
411
412
413
402
403
404
405
406
407
408

409
410
411
412
413
414
415
416







-
+







** ^The 2nd argument to the sqlite3_exec() callback function is the
** number of columns in the result.  ^The 3rd argument to the sqlite3_exec()
** callback is an array of pointers to strings obtained as if from
** [sqlite3_column_text()], one for each column.  ^If an element of a
** result row is NULL then the corresponding string pointer for the
** sqlite3_exec() callback is a NULL pointer.  ^The 4th argument to the
** sqlite3_exec() callback is an array of pointers to strings where each
** entry represents the name of corresponding result column as obtained
** entry represents the name of a corresponding result column as obtained
** from [sqlite3_column_name()].
**
** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer
** to an empty string, or a pointer that contains only whitespace and/or
** SQL comments, then no SQL statements are evaluated and the database
** is not changed.
**
493
494
495
496
497
498
499



500
501
502
503
504
505
506
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512







+
+
+







** [sqlite3_extended_result_codes()] API.  Or, the extended code for
** the most recent error can be obtained using
** [sqlite3_extended_errcode()].
*/
#define SQLITE_ERROR_MISSING_COLLSEQ   (SQLITE_ERROR | (1<<8))
#define SQLITE_ERROR_RETRY             (SQLITE_ERROR | (2<<8))
#define SQLITE_ERROR_SNAPSHOT          (SQLITE_ERROR | (3<<8))
#define SQLITE_ERROR_RESERVESIZE       (SQLITE_ERROR | (4<<8))
#define SQLITE_ERROR_KEY               (SQLITE_ERROR | (5<<8))
#define SQLITE_ERROR_UNABLE            (SQLITE_ERROR | (6<<8))
#define SQLITE_IOERR_READ              (SQLITE_IOERR | (1<<8))
#define SQLITE_IOERR_SHORT_READ        (SQLITE_IOERR | (2<<8))
#define SQLITE_IOERR_WRITE             (SQLITE_IOERR | (3<<8))
#define SQLITE_IOERR_FSYNC             (SQLITE_IOERR | (4<<8))
#define SQLITE_IOERR_DIR_FSYNC         (SQLITE_IOERR | (5<<8))
#define SQLITE_IOERR_TRUNCATE          (SQLITE_IOERR | (6<<8))
#define SQLITE_IOERR_FSTAT             (SQLITE_IOERR | (7<<8))
527
528
529
530
531
532
533


534
535
536
537
538
539
540
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548







+
+







#define SQLITE_IOERR_AUTH              (SQLITE_IOERR | (28<<8))
#define SQLITE_IOERR_BEGIN_ATOMIC      (SQLITE_IOERR | (29<<8))
#define SQLITE_IOERR_COMMIT_ATOMIC     (SQLITE_IOERR | (30<<8))
#define SQLITE_IOERR_ROLLBACK_ATOMIC   (SQLITE_IOERR | (31<<8))
#define SQLITE_IOERR_DATA              (SQLITE_IOERR | (32<<8))
#define SQLITE_IOERR_CORRUPTFS         (SQLITE_IOERR | (33<<8))
#define SQLITE_IOERR_IN_PAGE           (SQLITE_IOERR | (34<<8))
#define SQLITE_IOERR_BADKEY            (SQLITE_IOERR | (35<<8))
#define SQLITE_IOERR_CODEC             (SQLITE_IOERR | (36<<8))
#define SQLITE_LOCKED_SHAREDCACHE      (SQLITE_LOCKED |  (1<<8))
#define SQLITE_LOCKED_VTAB             (SQLITE_LOCKED |  (2<<8))
#define SQLITE_BUSY_RECOVERY           (SQLITE_BUSY   |  (1<<8))
#define SQLITE_BUSY_SNAPSHOT           (SQLITE_BUSY   |  (2<<8))
#define SQLITE_BUSY_TIMEOUT            (SQLITE_BUSY   |  (3<<8))
#define SQLITE_CANTOPEN_NOTEMPDIR      (SQLITE_CANTOPEN | (1<<8))
#define SQLITE_CANTOPEN_ISDIR          (SQLITE_CANTOPEN | (2<<8))
585
586
587
588
589
590
591
592

593
594
595
596
597
598
599
593
594
595
596
597
598
599

600
601
602
603
604
605
606
607







-
+







** though future versions of SQLite might change so that an error is
** raised if any of the disallowed bits are passed into sqlite3_open_v2().
** Applications should not depend on the historical behavior.
**
** Note in particular that passing the SQLITE_OPEN_EXCLUSIVE flag into
** [sqlite3_open_v2()] does *not* cause the underlying database file
** to be opened using O_EXCL.  Passing SQLITE_OPEN_EXCLUSIVE into
** [sqlite3_open_v2()] has historically be a no-op and might become an
** [sqlite3_open_v2()] has historically been a no-op and might become an
** error in future versions of SQLite.
*/
#define SQLITE_OPEN_READONLY         0x00000001  /* Ok for sqlite3_open_v2() */
#define SQLITE_OPEN_READWRITE        0x00000002  /* Ok for sqlite3_open_v2() */
#define SQLITE_OPEN_CREATE           0x00000004  /* Ok for sqlite3_open_v2() */
#define SQLITE_OPEN_DELETEONCLOSE    0x00000008  /* VFS only */
#define SQLITE_OPEN_EXCLUSIVE        0x00000010  /* VFS only */
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
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







+
+
+
+
+
+
+
















+







-
+







** read-only media and cannot be changed even by processes with
** elevated privileges.
**
** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying
** filesystem supports doing multiple write operations atomically when those
** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and
** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE].
**
** The SQLITE_IOCAP_SUBPAGE_READ property means that it is ok to read
** from the database file in amounts that are not a multiple of the
** page size and that do not begin at a page boundary.  Without this
** property, SQLite is careful to only do full-page reads and write
** on aligned pages, with the one exception that it will do a sub-page
** read of the first page to access the database header.
*/
#define SQLITE_IOCAP_ATOMIC                 0x00000001
#define SQLITE_IOCAP_ATOMIC512              0x00000002
#define SQLITE_IOCAP_ATOMIC1K               0x00000004
#define SQLITE_IOCAP_ATOMIC2K               0x00000008
#define SQLITE_IOCAP_ATOMIC4K               0x00000010
#define SQLITE_IOCAP_ATOMIC8K               0x00000020
#define SQLITE_IOCAP_ATOMIC16K              0x00000040
#define SQLITE_IOCAP_ATOMIC32K              0x00000080
#define SQLITE_IOCAP_ATOMIC64K              0x00000100
#define SQLITE_IOCAP_SAFE_APPEND            0x00000200
#define SQLITE_IOCAP_SEQUENTIAL             0x00000400
#define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN  0x00000800
#define SQLITE_IOCAP_POWERSAFE_OVERWRITE    0x00001000
#define SQLITE_IOCAP_IMMUTABLE              0x00002000
#define SQLITE_IOCAP_BATCH_ATOMIC           0x00004000
#define SQLITE_IOCAP_SUBPAGE_READ           0x00008000

/*
** CAPI3REF: File Locking Levels
**
** SQLite uses one of these integer values as the second
** argument to calls it makes to the xLock() and xUnlock() methods
** of an [sqlite3_io_methods] object.  These values are ordered from
** lest restrictive to most restrictive.
** least restrictive to most restrictive.
**
** The argument to xLock() is always SHARED or higher.  The argument to
** xUnlock is either SHARED or NONE.
*/
#define SQLITE_LOCK_NONE          0       /* xUnlock() only */
#define SQLITE_LOCK_SHARED        1       /* xLock() or xUnlock() */
#define SQLITE_LOCK_RESERVED      2       /* xLock() only */
810
811
812
813
814
815
816

817
818
819
820
821
822
823
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840







+







** <li> [SQLITE_IOCAP_ATOMIC64K]
** <li> [SQLITE_IOCAP_SAFE_APPEND]
** <li> [SQLITE_IOCAP_SEQUENTIAL]
** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN]
** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE]
** <li> [SQLITE_IOCAP_IMMUTABLE]
** <li> [SQLITE_IOCAP_BATCH_ATOMIC]
** <li> [SQLITE_IOCAP_SUBPAGE_READ]
** </ul>
**
** The SQLITE_IOCAP_ATOMIC property means that all writes of
** any size are atomic.  The SQLITE_IOCAP_ATOMICnnn values
** mean that writes of blocks that are nnn bytes in size and
** are aligned to an address which is an integer multiple of
** nnn are atomic.  The SQLITE_IOCAP_SAFE_APPEND value means
911
912
913
914
915
916
917
918

919
920
921
922
923
924
925
928
929
930
931
932
933
934

935
936
937
938
939
940
941
942







-
+







** <li>[[SQLITE_FCNTL_JOURNAL_POINTER]]
** The [SQLITE_FCNTL_JOURNAL_POINTER] opcode is used to obtain a pointer
** to the [sqlite3_file] object associated with the journal file (either
** the [rollback journal] or the [write-ahead log]) for a particular database
** connection.  See also [SQLITE_FCNTL_FILE_POINTER].
**
** <li>[[SQLITE_FCNTL_SYNC_OMITTED]]
** No longer in use.
** The SQLITE_FCNTL_SYNC_OMITTED file-control is no longer used.
**
** <li>[[SQLITE_FCNTL_SYNC]]
** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and
** sent to the VFS immediately before the xSync method is invoked on a
** database file descriptor. Or, if the xSync method is not invoked
** because the user has configured SQLite with
** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place
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
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







-
+













-
+







** ^The [SQLITE_FCNTL_OVERWRITE] opcode is invoked by SQLite after opening
** a write transaction to indicate that, unless it is rolled back for some
** reason, the entire database file will be overwritten by the current
** transaction. This is used by VACUUM operations.
**
** <li>[[SQLITE_FCNTL_VFSNAME]]
** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of
** all [VFSes] in the VFS stack.  The names are of all VFS shims and the
** all [VFSes] in the VFS stack.  The names of all VFS shims and the
** final bottom-level VFS are written into memory obtained from
** [sqlite3_malloc()] and the result is stored in the char* variable
** that the fourth parameter of [sqlite3_file_control()] points to.
** The caller is responsible for freeing the memory when done.  As with
** all file-control actions, there is no guarantee that this will actually
** do anything.  Callers should initialize the char* variable to a NULL
** pointer in case this file-control is not implemented.  This file-control
** is intended for diagnostic use only.
**
** <li>[[SQLITE_FCNTL_VFS_POINTER]]
** ^The [SQLITE_FCNTL_VFS_POINTER] opcode finds a pointer to the top-level
** [VFSes] currently in use.  ^(The argument X in
** sqlite3_file_control(db,SQLITE_FCNTL_VFS_POINTER,X) must be
** of type "[sqlite3_vfs] **".  This opcodes will set *X
** of type "[sqlite3_vfs] **".  This opcode will set *X
** to a pointer to the top-level VFS.)^
** ^When there are multiple VFS shims in the stack, this opcode finds the
** upper-most shim only.
**
** <li>[[SQLITE_FCNTL_PRAGMA]]
** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA]
** file control is sent to the open [sqlite3_file] object corresponding
1087
1088
1089
1090
1091
1092
1093





1094
1095
1096
1097
1098
1099
1100
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122







+
+
+
+
+







**
** <li>[[SQLITE_FCNTL_WIN32_SET_HANDLE]]
** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging.  This
** opcode causes the xFileControl method to swap the file handle with the one
** pointed to by the pArg argument.  This capability is used during testing
** and only needs to be supported when SQLITE_TEST is defined.
**
** <li>[[SQLITE_FCNTL_NULL_IO]]
** The [SQLITE_FCNTL_NULL_IO] opcode sets the low-level file descriptor
** or file handle for the [sqlite3_file] object such that it will no longer
** read or write to the database file.
**
** <li>[[SQLITE_FCNTL_WAL_BLOCK]]
** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might
** be advantageous to block on the next WAL lock if the lock is not immediately
** available.  The WAL subsystem issues this signal during rare
** circumstances in order to fix a problem with priority inversion.
** Applications should <em>not</em> use this file-control.
**
1144
1145
1146
1147
1148
1149
1150






1151
1152
1153
1154
1155
1156
1157
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185







+
+
+
+
+
+







** <li>[[SQLITE_FCNTL_LOCK_TIMEOUT]]
** The [SQLITE_FCNTL_LOCK_TIMEOUT] opcode is used to configure a VFS
** to block for up to M milliseconds before failing when attempting to
** obtain a file lock using the xLock or xShmLock methods of the VFS.
** The parameter is a pointer to a 32-bit signed integer that contains
** the value that M is to be set to. Before returning, the 32-bit signed
** integer is overwritten with the previous value of M.
**
** <li>[[SQLITE_FCNTL_BLOCK_ON_CONNECT]]
** The [SQLITE_FCNTL_BLOCK_ON_CONNECT] opcode is used to configure the
** VFS to block when taking a SHARED lock to connect to a wal mode database.
** This is used to implement the functionality associated with
** SQLITE_SETLK_BLOCK_ON_CONNECT.
**
** <li>[[SQLITE_FCNTL_DATA_VERSION]]
** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to
** a database file.  The argument is a pointer to a 32-bit unsigned integer.
** The "data version" for the pager is written into the pointer.  The
** "data version" changes whenever any change occurs to the corresponding
** database file, either through SQL statements on the same database
1179
1180
1181
1182
1183
1184
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
1210
1207
1208
1209
1210
1211
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







-
+

















+
+
+
+
+
+
+
+
+







** in wal mode after the client has finished copying pages from the wal
** file to the database file, but before the *-shm file is updated to
** record the fact that the pages have been checkpointed.
**
** <li>[[SQLITE_FCNTL_EXTERNAL_READER]]
** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect
** whether or not there is a database client in another process with a wal-mode
** transaction open on the database or not. It is only available on unix.The
** transaction open on the database or not. It is only available on unix. The
** (void*) argument passed with this file-control should be a pointer to a
** value of type (int). The integer value is set to 1 if the database is a wal
** mode database and there exists at least one client in another process that
** currently has an SQL transaction open on the database. It is set to 0 if
** the database is not a wal-mode db, or if there is no such connection in any
** other process. This opcode cannot be used to detect transactions opened
** by clients within the current process, only within other processes.
**
** <li>[[SQLITE_FCNTL_CKSM_FILE]]
** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use internally by the
** [checksum VFS shim] only.
**
** <li>[[SQLITE_FCNTL_RESET_CACHE]]
** If there is currently no transaction open on the database, and the
** database is not a temp db, then the [SQLITE_FCNTL_RESET_CACHE] file-control
** purges the contents of the in-memory page cache. If there is an open
** transaction, or if the db is a temp-db, this opcode is a no-op, not an error.
**
** <li>[[SQLITE_FCNTL_FILESTAT]]
** The [SQLITE_FCNTL_FILESTAT] opcode returns low-level diagnostic information
** about the [sqlite3_file] objects used access the database and journal files
** for the given schema.  The fourth parameter to [sqlite3_file_control()]
** should be an initialized [sqlite3_str] pointer.  JSON text describing
** various aspects of the sqlite3_file object is appended to the sqlite3_str.
** The SQLITE_FCNTL_FILESTAT opcode is usually a no-op, unless compile-time
** options are used to enable it.
** </ul>
*/
#define SQLITE_FCNTL_LOCKSTATE               1
#define SQLITE_FCNTL_GET_LOCKPROXYFILE       2
#define SQLITE_FCNTL_SET_LOCKPROXYFILE       3
#define SQLITE_FCNTL_LAST_ERRNO              4
#define SQLITE_FCNTL_SIZE_HINT               5
1240
1241
1242
1243
1244
1245
1246



1247
1248
1249
1250
1251
1252
1253
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293







+
+
+







#define SQLITE_FCNTL_SIZE_LIMIT             36
#define SQLITE_FCNTL_CKPT_DONE              37
#define SQLITE_FCNTL_RESERVE_BYTES          38
#define SQLITE_FCNTL_CKPT_START             39
#define SQLITE_FCNTL_EXTERNAL_READER        40
#define SQLITE_FCNTL_CKSM_FILE              41
#define SQLITE_FCNTL_RESET_CACHE            42
#define SQLITE_FCNTL_NULL_IO                43
#define SQLITE_FCNTL_BLOCK_ON_CONNECT       44
#define SQLITE_FCNTL_FILESTAT               45

/* deprecated names */
#define SQLITE_GET_LOCKPROXYFILE      SQLITE_FCNTL_GET_LOCKPROXYFILE
#define SQLITE_SET_LOCKPROXYFILE      SQLITE_FCNTL_SET_LOCKPROXYFILE
#define SQLITE_LAST_ERRNO             SQLITE_FCNTL_LAST_ERRNO


1446
1447
1448
1449
1450
1451
1452
1453

1454
1455
1456
1457
1458
1459
1460
1486
1487
1488
1489
1490
1491
1492

1493
1494
1495
1496
1497
1498
1499
1500







-
+







** Day Number multiplied by 86400000 (the number of milliseconds in
** a 24-hour day).
** ^SQLite will use the xCurrentTimeInt64() method to get the current
** date and time if that method is available (if iVersion is 2 or
** greater and the function pointer is not NULL) and will fall back
** to xCurrentTime() if xCurrentTimeInt64() is unavailable.
**
** ^The xSetSystemCall(), xGetSystemCall(), and xNestSystemCall() interfaces
** ^The xSetSystemCall(), xGetSystemCall(), and xNextSystemCall() interfaces
** are not used by the SQLite core.  These optional interfaces are provided
** by some VFSes to facilitate testing of the VFS code. By overriding
** system calls with functions under its control, a test program can
** simulate faults and error conditions that would otherwise be difficult
** or impossible to induce.  The set of system calls that can be overridden
** varies from one VFS to another, and from one version of the same VFS to the
** next.  Applications that use these interfaces must be prepared for any
1602
1603
1604
1605
1606
1607
1608
1609

1610
1611
1612
1613
1614
1615
1616
1642
1643
1644
1645
1646
1647
1648

1649
1650
1651
1652
1653
1654
1655
1656







-
+







** the library (perhaps it is unable to allocate a needed resource such
** as a mutex) it returns an [error code] other than [SQLITE_OK].
**
** ^The sqlite3_initialize() routine is called internally by many other
** SQLite interfaces so that an application usually does not need to
** invoke sqlite3_initialize() directly.  For example, [sqlite3_open()]
** calls sqlite3_initialize() so the SQLite library will be automatically
** initialized when [sqlite3_open()] is called if it has not be initialized
** initialized when [sqlite3_open()] is called if it has not been initialized
** already.  ^However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT]
** compile-time option, then the automatic calls to sqlite3_initialize()
** are omitted and the application must call sqlite3_initialize() directly
** prior to using any other SQLite interface.  For maximum portability,
** it is recommended that applications always invoke sqlite3_initialize()
** directly prior to using any other SQLite interface.  Future releases
** of SQLite may require this.  In other words, the behavior exhibited
1859
1860
1861
1862
1863
1864
1865
1866

1867
1868
1869
1870

1871
1872
1873
1874

1875
1876
1877
1878
1879
1880

1881
1882
1883
1884
1885
1886
1887
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







-
+



-
+



-
+





-
+







**
** [[SQLITE_CONFIG_GETMALLOC]] <dt>SQLITE_CONFIG_GETMALLOC</dt>
** <dd> ^(The SQLITE_CONFIG_GETMALLOC option takes a single argument which
** is a pointer to an instance of the [sqlite3_mem_methods] structure.
** The [sqlite3_mem_methods]
** structure is filled with the currently defined memory allocation routines.)^
** This option can be used to overload the default memory allocation
** routines with a wrapper that simulations memory allocation failure or
** routines with a wrapper that simulates memory allocation failure or
** tracks memory usage, for example. </dd>
**
** [[SQLITE_CONFIG_SMALL_MALLOC]] <dt>SQLITE_CONFIG_SMALL_MALLOC</dt>
** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of
** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes a single argument of
** type int, interpreted as a boolean, which if true provides a hint to
** SQLite that it should avoid large memory allocations if possible.
** SQLite will run faster if it is free to make large memory allocations,
** but some application might prefer to run slower in exchange for
** but some applications might prefer to run slower in exchange for
** guarantees about memory fragmentation that are possible if large
** allocations are avoided.  This hint is normally off.
** </dd>
**
** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt>
** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int,
** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes a single argument of type int,
** interpreted as a boolean, which enables or disables the collection of
** memory allocation statistics. ^(When memory allocation statistics are
** disabled, the following SQLite interfaces become non-operational:
**   <ul>
**   <li> [sqlite3_hard_heap_limit64()]
**   <li> [sqlite3_memory_used()]
**   <li> [sqlite3_memory_highwater()]
1918
1919
1920
1921
1922
1923
1924
1925

1926
1927
1928
1929
1930
1931
1932
1958
1959
1960
1961
1962
1963
1964

1965
1966
1967
1968
1969
1970
1971
1972







-
+







** ^When pMem is not NULL, SQLite will strive to use the memory provided
** to satisfy page cache needs, falling back to [sqlite3_malloc()] if
** a page cache line is larger than sz bytes or if all of the pMem buffer
** is exhausted.
** ^If pMem is NULL and N is non-zero, then each database connection
** does an initial bulk allocation for page cache memory
** from [sqlite3_malloc()] sufficient for N cache lines if N is positive or
** of -1024*N bytes if N is negative, . ^If additional
** of -1024*N bytes if N is negative. ^If additional
** page cache memory is needed beyond what is provided by the initial
** allocation, then SQLite goes to [sqlite3_malloc()] separately for each
** additional cache line. </dd>
**
** [[SQLITE_CONFIG_HEAP]] <dt>SQLITE_CONFIG_HEAP</dt>
** <dd> ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer
** that SQLite will use for all of its dynamic memory allocation needs
1947
1948
1949
1950
1951
1952
1953
1954

1955
1956
1957
1958
1959
1960
1961
1987
1988
1989
1990
1991
1992
1993

1994
1995
1996
1997
1998
1999
2000
2001







-
+







** The minimum allocation size is capped at 2**12. Reasonable values
** for the minimum allocation size are 2**5 through 2**8.</dd>
**
** [[SQLITE_CONFIG_MUTEX]] <dt>SQLITE_CONFIG_MUTEX</dt>
** <dd> ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a
** pointer to an instance of the [sqlite3_mutex_methods] structure.
** The argument specifies alternative low-level mutex routines to be used
** in place the mutex routines built into SQLite.)^  ^SQLite makes a copy of
** in place of the mutex routines built into SQLite.)^  ^SQLite makes a copy of
** the content of the [sqlite3_mutex_methods] structure before the call to
** [sqlite3_config()] returns. ^If SQLite is compiled with
** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
** the entire mutexing subsystem is omitted from the build and hence calls to
** [sqlite3_config()] with the SQLITE_CONFIG_MUTEX configuration option will
** return [SQLITE_ERROR].</dd>
**
1970
1971
1972
1973
1974
1975
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
2007
2008
2009
2010

2011
2012
2013
2014
2015
2016
2017
2010
2011
2012
2013
2014
2015
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







-
+

-
-
-
-
-
+
+
+
+
+
+
+
+









-
+
















-
+







** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then
** the entire mutexing subsystem is omitted from the build and hence calls to
** [sqlite3_config()] with the SQLITE_CONFIG_GETMUTEX configuration option will
** return [SQLITE_ERROR].</dd>
**
** [[SQLITE_CONFIG_LOOKASIDE]] <dt>SQLITE_CONFIG_LOOKASIDE</dt>
** <dd> ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine
** the default size of lookaside memory on each [database connection].
** the default size of [lookaside memory] on each [database connection].
** The first argument is the
** size of each lookaside buffer slot and the second is the number of
** slots allocated to each database connection.)^  ^(SQLITE_CONFIG_LOOKASIDE
** sets the <i>default</i> lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE]
** option to [sqlite3_db_config()] can be used to change the lookaside
** configuration on individual connections.)^ </dd>
** size of each lookaside buffer slot ("sz") and the second is the number of
** slots allocated to each database connection ("cnt").)^
** ^(SQLITE_CONFIG_LOOKASIDE sets the <i>default</i> lookaside size.
** The [SQLITE_DBCONFIG_LOOKASIDE] option to [sqlite3_db_config()] can
** be used to change the lookaside configuration on individual connections.)^
** The [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to change the
** default lookaside configuration at compile-time.
** </dd>
**
** [[SQLITE_CONFIG_PCACHE2]] <dt>SQLITE_CONFIG_PCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is
** a pointer to an [sqlite3_pcache_methods2] object.  This object specifies
** the interface to a custom page cache implementation.)^
** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.</dd>
**
** [[SQLITE_CONFIG_GETPCACHE2]] <dt>SQLITE_CONFIG_GETPCACHE2</dt>
** <dd> ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which
** is a pointer to an [sqlite3_pcache_methods2] object.  SQLite copies of
** is a pointer to an [sqlite3_pcache_methods2] object.  SQLite copies off
** the current page cache implementation into that object.)^ </dd>
**
** [[SQLITE_CONFIG_LOG]] <dt>SQLITE_CONFIG_LOG</dt>
** <dd> The SQLITE_CONFIG_LOG option is used to configure the SQLite
** global [error log].
** (^The SQLITE_CONFIG_LOG option takes two arguments: a pointer to a
** function with a call signature of void(*)(void*,int,const char*),
** and a pointer to void. ^If the function pointer is not NULL, it is
** invoked by [sqlite3_log()] to process each logging event.  ^If the
** function pointer is NULL, the [sqlite3_log()] interface becomes a no-op.
** ^The void pointer that is the second argument to SQLITE_CONFIG_LOG is
** passed through as the first parameter to the application-defined logger
** function whenever that function is invoked.  ^The second parameter to
** the logger function is a copy of the first parameter to the corresponding
** [sqlite3_log()] call and is intended to be a [result code] or an
** [extended result code].  ^The third parameter passed to the logger is
** log message after formatting via [sqlite3_snprintf()].
** a log message after formatting via [sqlite3_snprintf()].
** The SQLite logging interface is not reentrant; the logger function
** supplied by the application must not invoke any SQLite interface.
** In a multi-threaded application, the application-defined logger
** function must be threadsafe. </dd>
**
** [[SQLITE_CONFIG_URI]] <dt>SQLITE_CONFIG_URI
** <dd>^(The SQLITE_CONFIG_URI option takes a single argument of type int.
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
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







+
+
-
+
+
+
+
+
+
+











-
-
-
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+


-
-
+


-
+
+
+
+
+
+
+
+
+
+
+




-
+
+







#define SQLITE_CONFIG_MEMDB_MAXSIZE       29  /* sqlite3_int64 */
#define SQLITE_CONFIG_ROWID_IN_VIEW       30  /* int* */

/*
** CAPI3REF: Database Connection Configuration Options
**
** These constants are the available integer configuration options that
** can be passed as the second parameter to the [sqlite3_db_config()] interface.
**
** can be passed as the second argument to the [sqlite3_db_config()] interface.
** The [sqlite3_db_config()] interface is a var-args function.  It takes a
** variable number of parameters, though always at least two.  The number of
** parameters passed into sqlite3_db_config() depends on which of these
** constants is given as the second parameter.  This documentation page
** refers to parameters beyond the second as "arguments".  Thus, when this
** page says "the N-th argument" it means "the N-th parameter past the
** configuration option" or "the (N+2)-th parameter to sqlite3_db_config()".
**
** New configuration options may be added in future releases of SQLite.
** Existing configuration options might be discontinued.  Applications
** should check the return code from [sqlite3_db_config()] to make sure that
** the call worked.  ^The [sqlite3_db_config()] interface will return a
** non-zero [error code] if a discontinued or unsupported configuration option
** is invoked.
**
** <dl>
** [[SQLITE_DBCONFIG_LOOKASIDE]]
** <dt>SQLITE_DBCONFIG_LOOKASIDE</dt>
** <dd> ^This option takes three additional arguments that determine the
** [lookaside memory allocator] configuration for the [database connection].
** ^The first argument (the third parameter to [sqlite3_db_config()] is a
** <dd> The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the
** configuration of the [lookaside memory allocator] within a database
** connection.
** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are <i>not</i>
** in the [DBCONFIG arguments|usual format].
** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two,
** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE
** should have a total of five parameters.
** <ol>
** <li><p>The first argument ("buf") is a
** pointer to a memory buffer to use for lookaside memory.
** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb
** may be NULL in which case SQLite will allocate the
** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the
** size of each lookaside buffer slot.  ^The third argument is the number of
** slots.  The size of the buffer in the first argument must be greater than
** or equal to the product of the second and third arguments.  The buffer
** must be aligned to an 8-byte boundary.  ^If the second argument to
** The first argument may be NULL in which case SQLite will allocate the
** lookaside buffer itself using [sqlite3_malloc()].
** <li><P>The second argument ("sz") is the
** size of each lookaside buffer slot.  Lookaside is disabled if "sz"
** is less than 8.  The "sz" argument should be a multiple of 8 less than
** 65536.  If "sz" does not meet this constraint, it is reduced in size until
** it does.
** <li><p>The third argument ("cnt") is the number of slots. Lookaside is disabled
** if "cnt"is less than 1.  The "cnt" value will be reduced, if necessary, so
** that the product of "sz" and "cnt" does not exceed 2,147,418,112.  The "cnt"
** parameter is usually chosen so that the product of "sz" and "cnt" is less
** than 1,000,000.
** </ol>
** <p>If the "buf" argument is not NULL, then it must
** point to a memory buffer with a size that is greater than
** or equal to the product of "sz" and "cnt".
** The buffer must be aligned to an 8-byte boundary.
** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally
** rounded down to the next smaller multiple of 8.  ^(The lookaside memory
** The lookaside memory
** configuration for a database connection can only be changed when that
** connection is not currently using lookaside memory, or in other words
** when the "current value" returned by
** [sqlite3_db_status](D,[SQLITE_DBSTATUS_LOOKASIDE_USED],...) is zero.
** when the value returned by [SQLITE_DBSTATUS_LOOKASIDE_USED] is zero.
** Any attempt to change the lookaside memory configuration when lookaside
** memory is in use leaves the configuration unchanged and returns
** [SQLITE_BUSY].)^</dd>
** [SQLITE_BUSY].
** If the "buf" argument is NULL and an attempt
** to allocate memory based on "sz" and "cnt" fails, then
** lookaside is silently disabled.
** <p>
** The [SQLITE_CONFIG_LOOKASIDE] configuration option can be used to set the
** default lookaside configuration at initialization.  The
** [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to set the default lookaside
** configuration at compile-time.  Typical values for lookaside are 1200 for
** "sz" and 40 to 100 for "cnt".
** </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_FKEY]]
** <dt>SQLITE_DBCONFIG_ENABLE_FKEY</dt>
** <dd> ^This option is used to enable or disable the enforcement of
** [foreign key constraints].  There should be two additional arguments.
** [foreign key constraints].  This is the same setting that is
** enabled or disabled by the [PRAGMA foreign_keys] statement.
** The first argument is an integer which is 0 to disable FK enforcement,
** positive to enable FK enforcement or negative to leave FK enforcement
** unchanged.  The second parameter is a pointer to an integer into which
** is written 0 or 1 to indicate whether FK enforcement is off or on
** following this call.  The second parameter may be a NULL pointer, in
** which case the FK enforcement setting is not reported back. </dd>
**
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
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







-
+





-
+















-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+







-
+



-
-
+
+








+
+
+
+
-
-
-
-
-
+
+
+
+
+




-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+







** is written 0 or 1 to indicate whether triggers are disabled or enabled
** following this call.  The second parameter may be a NULL pointer, in
** which case the trigger setting is not reported back.
**
** <p>Originally this option disabled all triggers.  ^(However, since
** SQLite version 3.35.0, TEMP triggers are still allowed even if
** this option is off.  So, in other words, this option now only disables
** triggers in the main database schema or in the schemas of ATTACH-ed
** triggers in the main database schema or in the schemas of [ATTACH]-ed
** databases.)^ </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_VIEW]]
** <dt>SQLITE_DBCONFIG_ENABLE_VIEW</dt>
** <dd> ^This option is used to enable or disable [CREATE VIEW | views].
** There should be two additional arguments.
** There must be two additional arguments.
** The first argument is an integer which is 0 to disable views,
** positive to enable views or negative to leave the setting unchanged.
** The second parameter is a pointer to an integer into which
** is written 0 or 1 to indicate whether views are disabled or enabled
** following this call.  The second parameter may be a NULL pointer, in
** which case the view setting is not reported back.
**
** <p>Originally this option disabled all views.  ^(However, since
** SQLite version 3.35.0, TEMP views are still allowed even if
** this option is off.  So, in other words, this option now only disables
** views in the main database schema or in the schemas of ATTACH-ed
** databases.)^ </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]]
** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt>
** <dd> ^This option is used to enable or disable the
** [fts3_tokenizer()] function which is part of the
** [FTS3] full-text search engine extension.
** There should be two additional arguments.
** The first argument is an integer which is 0 to disable fts3_tokenizer() or
** positive to enable fts3_tokenizer() or negative to leave the setting
** <dd> ^This option is used to enable or disable using the
** [fts3_tokenizer()] function - part of the [FTS3] full-text search engine
** extension - without using bound parameters as the parameters. Doing so
** is disabled by default. There must be two additional arguments. The first
** argument is an integer. If it is passed 0, then using fts3_tokenizer()
** without bound parameters is disabled. If it is passed a positive value,
** then calling fts3_tokenizer without bound parameters is enabled. If it
** unchanged.
** The second parameter is a pointer to an integer into which
** is written 0 or 1 to indicate whether fts3_tokenizer is disabled or enabled
** following this call.  The second parameter may be a NULL pointer, in
** which case the new setting is not reported back. </dd>
** is passed a negative value, this setting is not modified - this can be
** used to query for the current setting. The second parameter is a pointer
** to an integer into which is written 0 or 1 to indicate the current value
** of this setting (after it is modified, if applicable).  The second
** parameter may be a NULL pointer, in which case the value of the setting
** is not reported back. Refer to [FTS3] documentation for further details.
** </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]]
** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt>
** <dd> ^This option is used to enable or disable the [sqlite3_load_extension()]
** interface independently of the [load_extension()] SQL function.
** The [sqlite3_enable_load_extension()] API enables or disables both the
** C-API [sqlite3_load_extension()] and the SQL function [load_extension()].
** There should be two additional arguments.
** There must be two additional arguments.
** When the first argument to this interface is 1, then only the C-API is
** enabled and the SQL function remains disabled.  If the first argument to
** this interface is 0, then both the C-API and the SQL function are disabled.
** If the first argument is -1, then no changes are made to state of either the
** C-API or the SQL function.
** If the first argument is -1, then no changes are made to the state of either
** the C-API or the SQL function.
** The second parameter is a pointer to an integer into which
** is written 0 or 1 to indicate whether [sqlite3_load_extension()] interface
** is disabled or enabled following this call.  The second parameter may
** be a NULL pointer, in which case the new setting is not reported back.
** </dd>
**
** [[SQLITE_DBCONFIG_MAINDBNAME]] <dt>SQLITE_DBCONFIG_MAINDBNAME</dt>
** <dd> ^This option is used to change the name of the "main" database
** schema.  This option does not follow the
** [DBCONFIG arguments|usual SQLITE_DBCONFIG argument format].
** This option takes exactly one additional argument so that the
** [sqlite3_db_config()] call has a total of three parameters.  The
** schema.  ^The sole argument is a pointer to a constant UTF8 string
** which will become the new schema name in place of "main".  ^SQLite
** does not make a copy of the new main schema name string, so the application
** must ensure that the argument passed into this DBCONFIG option is unchanged
** until after the database connection closes.
** extra argument must be a pointer to a constant UTF8 string which
** will become the new schema name in place of "main".  ^SQLite does
** not make a copy of the new main schema name string, so the application
** must ensure that the argument passed into SQLITE_DBCONFIG MAINDBNAME
** is unchanged until after the database connection closes.
** </dd>
**
** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]]
** <dt>SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE</dt>
** <dd> Usually, when a database in wal mode is closed or detached from a
** database handle, SQLite checks if this will mean that there are now no
** connections at all to the database. If so, it performs a checkpoint
** operation before closing the connection. This option may be used to
** override this behavior. The first parameter passed to this operation
** is an integer - positive to disable checkpoints-on-close, or zero (the
** default) to enable them, and negative to leave the setting unchanged.
** The second parameter is a pointer to an integer
** <dd> Usually, when a database in [WAL mode] is closed or detached from a
** database handle, SQLite checks if if there are other connections to the
** same database, and if there are no other database connection (if the
** connection being closed is the last open connection to the database),
** then SQLite performs a [checkpoint] before closing the connection and
** deletes the WAL file.  The SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE option can
** be used to override that behavior. The first argument passed to this
** operation (the third parameter to [sqlite3_db_config()]) is an integer
** which is positive to disable checkpoints-on-close, or zero (the default)
** to enable them, and negative to leave the setting unchanged.
** The second argument (the fourth parameter) is a pointer to an integer
** into which is written 0 or 1 to indicate whether checkpoints-on-close
** have been disabled - 0 if they are not disabled, 1 if they are.
** </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_QPSG]] <dt>SQLITE_DBCONFIG_ENABLE_QPSG</dt>
** <dd>^(The SQLITE_DBCONFIG_ENABLE_QPSG option activates or deactivates
** the [query planner stability guarantee] (QPSG).  When the QPSG is active,
2406
2407
2408
2409
2410
2411
2412
2413

2414
2415
2416
2417
2418
2419
2420
2493
2494
2495
2496
2497
2498
2499

2500
2501
2502
2503
2504
2505
2506
2507







-
+







** integer into which is written 0 or 1 to indicate whether the writable_schema
** is enabled or disabled following this call.
** </dd>
**
** [[SQLITE_DBCONFIG_LEGACY_ALTER_TABLE]]
** <dt>SQLITE_DBCONFIG_LEGACY_ALTER_TABLE</dt>
** <dd>The SQLITE_DBCONFIG_LEGACY_ALTER_TABLE option activates or deactivates
** the legacy behavior of the [ALTER TABLE RENAME] command such it
** the legacy behavior of the [ALTER TABLE RENAME] command such that it
** behaves as it did prior to [version 3.24.0] (2018-06-04).  See the
** "Compatibility Notice" on the [ALTER TABLE RENAME documentation] for
** additional information. This feature can also be turned on and off
** using the [PRAGMA legacy_alter_table] statement.
** </dd>
**
** [[SQLITE_DBCONFIG_DQS_DML]]
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
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489










2490
2491
2492
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
2542
2543
2544
2545
2546
2547
2548

2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
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
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
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
2676
2677







-
+




















-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+












-
+








+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







** can also be controlled using the [PRAGMA trusted_schema] statement.
** </dd>
**
** [[SQLITE_DBCONFIG_LEGACY_FILE_FORMAT]]
** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</dt>
** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates
** the legacy file format flag.  When activated, this flag causes all newly
** created database file to have a schema format version number (the 4-byte
** created database files to have a schema format version number (the 4-byte
** integer found at offset 44 into the database header) of 1.  This in turn
** means that the resulting database file will be readable and writable by
** any SQLite version back to 3.0.0 ([dateof:3.0.0]).  Without this setting,
** newly created databases are generally not understandable by SQLite versions
** prior to 3.3.0 ([dateof:3.3.0]).  As these words are written, there
** is now scarcely any need to generate database files that are compatible
** all the way back to version 3.0.0, and so this setting is of little
** practical use, but is provided so that SQLite can continue to claim the
** ability to generate new database files that are compatible with  version
** 3.0.0.
** <p>Note that when the SQLITE_DBCONFIG_LEGACY_FILE_FORMAT setting is on,
** the [VACUUM] command will fail with an obscure error when attempting to
** process a table with generated columns and a descending index.  This is
** not considered a bug since SQLite versions 3.3.0 and earlier do not support
** either generated columns or descending indexes.
** </dd>
**
** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]]
** <dt>SQLITE_DBCONFIG_STMT_SCANSTATUS</dt>
** <dd>The SQLITE_DBCONFIG_STMT_SCANSTATUS option is only useful in
** SQLITE_ENABLE_STMT_SCANSTATUS builds. In this case, it sets or clears
** a flag that enables collection of the sqlite3_stmt_scanstatus_v2()
** statistics. For statistics to be collected, the flag must be set on
** the database handle both when the SQL statement is prepared and when it
** is stepped. The flag is set (collection of statistics is enabled)
** by default.  This option takes two arguments: an integer and a pointer to
** an integer..  The first argument is 1, 0, or -1 to enable, disable, or
** [SQLITE_ENABLE_STMT_SCANSTATUS] builds. In this case, it sets or clears
** a flag that enables collection of run-time performance statistics
** used by [sqlite3_stmt_scanstatus_v2()] and the [nexec and ncycle]
** columns of the [bytecode virtual table].
** For statistics to be collected, the flag must be set on
** the database handle both when the SQL statement is
** [sqlite3_prepare|prepared] and when it is [sqlite3_step|stepped].
** The flag is set (collection of statistics is enabled) by default.
** <p>This option takes two arguments: an integer and a pointer to
** an integer.  The first argument is 1, 0, or -1 to enable, disable, or
** leave unchanged the statement scanstatus option.  If the second argument
** is not NULL, then the value of the statement scanstatus setting after
** processing the first argument is written into the integer that the second
** argument points to.
** </dd>
**
** [[SQLITE_DBCONFIG_REVERSE_SCANORDER]]
** <dt>SQLITE_DBCONFIG_REVERSE_SCANORDER</dt>
** <dd>The SQLITE_DBCONFIG_REVERSE_SCANORDER option changes the default order
** in which tables and indexes are scanned so that the scans start at the end
** and work toward the beginning rather than starting at the beginning and
** working toward the end. Setting SQLITE_DBCONFIG_REVERSE_SCANORDER is the
** same as setting [PRAGMA reverse_unordered_selects].  This option takes
** same as setting [PRAGMA reverse_unordered_selects]. <p>This option takes
** two arguments which are an integer and a pointer to an integer.  The first
** argument is 1, 0, or -1 to enable, disable, or leave unchanged the
** reverse scan order flag, respectively.  If the second argument is not NULL,
** then 0 or 1 is written into the integer that the second argument points to
** depending on if the reverse scan order flag is set after processing the
** first argument.
** </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE]]
** <dt>SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE</dt>
** <dd>The SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE option enables or disables
** the ability of the [ATTACH DATABASE] SQL command to create a new database
** file if the database filed named in the ATTACH command does not already
** exist.  This ability of ATTACH to create a new database is enabled by
** default.  Applications can disable or reenable the ability for ATTACH to
** create new database files using this DBCONFIG option.<p>
** This option takes two arguments which are an integer and a pointer
** to an integer.  The first argument is 1, 0, or -1 to enable, disable, or
** leave unchanged the attach-create flag, respectively.  If the second
** argument is not NULL, then 0 or 1 is written into the integer that the
** second argument points to depending on if the attach-create flag is set
** after processing the first argument.
** </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE]]
** <dt>SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE</dt>
** <dd>The SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE option enables or disables the
** ability of the [ATTACH DATABASE] SQL command to open a database for writing.
** This capability is enabled by default.  Applications can disable or
** reenable this capability using the current DBCONFIG option.  If
** this capability is disabled, the [ATTACH] command will still work,
** but the database will be opened read-only.  If this option is disabled,
** then the ability to create a new database using [ATTACH] is also disabled,
** regardless of the value of the [SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE]
** option.<p>
** This option takes two arguments which are an integer and a pointer
** to an integer.  The first argument is 1, 0, or -1 to enable, disable, or
** leave unchanged the ability to ATTACH another database for writing,
** respectively.  If the second argument is not NULL, then 0 or 1 is written
** into the integer to which the second argument points, depending on whether
** the ability to ATTACH a read/write database is enabled or disabled
** after processing the first argument.
** </dd>
**
** [[SQLITE_DBCONFIG_ENABLE_COMMENTS]]
** <dt>SQLITE_DBCONFIG_ENABLE_COMMENTS</dt>
** <dd>The SQLITE_DBCONFIG_ENABLE_COMMENTS option enables or disables the
** ability to include comments in SQL text.  Comments are enabled by default.
** An application can disable or reenable comments in SQL text using this
** DBCONFIG option.<p>
** This option takes two arguments which are an integer and a pointer
** to an integer.  The first argument is 1, 0, or -1 to enable, disable, or
** leave unchanged the ability to use comments in SQL text,
** respectively.  If the second argument is not NULL, then 0 or 1 is written
** into the integer that the second argument points to depending on if
** comments are allowed in SQL text after processing the first argument.
** </dd>
**
** </dl>
**
** [[DBCONFIG arguments]] <h3>Arguments To SQLITE_DBCONFIG Options</h3>
**
** <p>Most of the SQLITE_DBCONFIG options take two arguments, so that the
** overall call to [sqlite3_db_config()] has a total of four parameters.
** The first argument (the third parameter to sqlite3_db_config()) is an integer.
** The second argument is a pointer to an integer.  If the first argument is 1,
** then the option becomes enabled.  If the first integer argument is 0, then the
** option is disabled.  If the first argument is -1, then the option setting
** is unchanged.  The second argument, the pointer to an integer, may be NULL.
** If the second argument is not NULL, then a value of 0 or 1 is written into
** the integer to which the second argument points, depending on whether the
** setting is disabled or enabled after applying any changes specified by
** the first argument.
**
** <p>While most SQLITE_DBCONFIG options use the argument format
** described in the previous paragraph, the [SQLITE_DBCONFIG_MAINDBNAME]
** and [SQLITE_DBCONFIG_LOOKASIDE] options are different.  See the
** documentation of those exceptional options for details.
*/
#define SQLITE_DBCONFIG_MAINDBNAME            1000 /* const char* */
#define SQLITE_DBCONFIG_LOOKASIDE             1001 /* void* int int */
#define SQLITE_DBCONFIG_ENABLE_FKEY           1002 /* int int* */
#define SQLITE_DBCONFIG_ENABLE_TRIGGER        1003 /* int int* */
#define SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004 /* int int* */
#define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005 /* int int* */
2526
2527
2528
2529
2530
2531
2532



2533

2534
2535
2536
2537
2538
2539
2540
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694

2695
2696
2697
2698
2699
2700
2701
2702







+
+
+
-
+







#define SQLITE_DBCONFIG_DQS_DML               1013 /* int int* */
#define SQLITE_DBCONFIG_DQS_DDL               1014 /* int int* */
#define SQLITE_DBCONFIG_ENABLE_VIEW           1015 /* int int* */
#define SQLITE_DBCONFIG_LEGACY_FILE_FORMAT    1016 /* int int* */
#define SQLITE_DBCONFIG_TRUSTED_SCHEMA        1017 /* int int* */
#define SQLITE_DBCONFIG_STMT_SCANSTATUS       1018 /* int int* */
#define SQLITE_DBCONFIG_REVERSE_SCANORDER     1019 /* int int* */
#define SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE  1020 /* int int* */
#define SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE   1021 /* int int* */
#define SQLITE_DBCONFIG_ENABLE_COMMENTS       1022 /* int int* */
#define SQLITE_DBCONFIG_MAX                   1019 /* Largest DBCONFIG */
#define SQLITE_DBCONFIG_MAX                   1022 /* Largest DBCONFIG */

/*
** CAPI3REF: Enable Or Disable Extended Result Codes
** METHOD: sqlite3
**
** ^The sqlite3_extended_result_codes() routine enables or disables the
** [extended result codes] feature of SQLite. ^The extended result
2618
2619
2620
2621
2622
2623
2624
2625

2626
2627
2628




2629
2630
2631
2632
2633
2634
2635
2780
2781
2782
2783
2784
2785
2786

2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801







-
+



+
+
+
+







** CAPI3REF: Count The Number Of Rows Modified
** METHOD: sqlite3
**
** ^These functions return the number of rows modified, inserted or
** deleted by the most recently completed INSERT, UPDATE or DELETE
** statement on the database connection specified by the only parameter.
** The two functions are identical except for the type of the return value
** and that if the number of rows modified by the most recent INSERT, UPDATE
** and that if the number of rows modified by the most recent INSERT, UPDATE,
** or DELETE is greater than the maximum value supported by type "int", then
** the return value of sqlite3_changes() is undefined. ^Executing any other
** type of SQL statement does not modify the value returned by these functions.
** For the purposes of this interface, a CREATE TABLE AS SELECT statement
** does not count as an INSERT, UPDATE or DELETE statement and hence the rows
** added to the new table by the CREATE TABLE AS SELECT statement are not
** counted.
**
** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are
** considered - auxiliary changes caused by [CREATE TRIGGER | triggers],
** [foreign key actions] or [REPLACE] constraint resolution are not counted.
**
** Changes to a view that are intercepted by
** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value
2774
2775
2776
2777
2778
2779
2780
2781

2782
2783
2784
2785
2786
2787
2788
2940
2941
2942
2943
2944
2945
2946

2947
2948
2949
2950
2951
2952
2953
2954







-
+







** independent tokens (they are part of the token in which they are
** embedded) and thus do not count as a statement terminator.  ^Whitespace
** and comments that follow the final semicolon are ignored.
**
** ^These routines return 0 if the statement is incomplete.  ^If a
** memory allocation fails, then SQLITE_NOMEM is returned.
**
** ^These routines do not parse the SQL statements thus
** ^These routines do not parse the SQL statements and thus
** will not detect syntactically incorrect SQL.
**
** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior
** to invoking sqlite3_complete16() then sqlite3_initialize() is invoked
** automatically by sqlite3_complete16().  If that initialization fails,
** then the return value from sqlite3_complete16() will be non-zero
** regardless of whether or not the input SQL is complete.)^
2876
2877
2878
2879
2880
2881
2882






































2883
2884
2885
2886
2887
2888
2889
2890

2891
2892
2893
2894
2895
2896
2897
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093

3094
3095
3096
3097
3098
3099
3100
3101







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







-
+







** was defined  (using [sqlite3_busy_handler()]) prior to calling
** this routine, that other busy handler is cleared.)^
**
** See also:  [PRAGMA busy_timeout]
*/
SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms);

/*
** CAPI3REF: Set the Setlk Timeout
** METHOD: sqlite3
**
** This routine is only useful in SQLITE_ENABLE_SETLK_TIMEOUT builds. If
** the VFS supports blocking locks, it sets the timeout in ms used by
** eligible locks taken on wal mode databases by the specified database
** handle. In non-SQLITE_ENABLE_SETLK_TIMEOUT builds, or if the VFS does
** not support blocking locks, this function is a no-op.
**
** Passing 0 to this function disables blocking locks altogether. Passing
** -1 to this function requests that the VFS blocks for a long time -
** indefinitely if possible. The results of passing any other negative value
** are undefined.
**
** Internally, each SQLite database handle stores two timeout values - the
** busy-timeout (used for rollback mode databases, or if the VFS does not
** support blocking locks) and the setlk-timeout (used for blocking locks
** on wal-mode databases). The sqlite3_busy_timeout() method sets both
** values, this function sets only the setlk-timeout value. Therefore,
** to configure separate busy-timeout and setlk-timeout values for a single
** database handle, call sqlite3_busy_timeout() followed by this function.
**
** Whenever the number of connections to a wal mode database falls from
** 1 to 0, the last connection takes an exclusive lock on the database,
** then checkpoints and deletes the wal file. While it is doing this, any
** new connection that tries to read from the database fails with an
** SQLITE_BUSY error. Or, if the SQLITE_SETLK_BLOCK_ON_CONNECT flag is
** passed to this API, the new connection blocks until the exclusive lock
** has been released.
*/
SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags);

/*
** CAPI3REF: Flags for sqlite3_setlk_timeout()
*/
#define SQLITE_SETLK_BLOCK_ON_CONNECT 0x01

/*
** CAPI3REF: Convenience Routines For Running Queries
** METHOD: sqlite3
**
** This is a legacy interface that is preserved for backwards compatibility.
** Use of this interface is not recommended.
**
** Definition: A <b>result table</b> is memory data structure created by the
** Definition: A <b>result table</b> is a memory data structure created by the
** [sqlite3_get_table()] interface.  A result table records the
** complete query results from one or more queries.
**
** The table conceptually has a number of rows and columns.  But
** these numbers are not part of the result table itself.  These
** numbers are obtained separately.  Let N be the number of rows
** and M be the number of columns.
3026
3027
3028
3029
3030
3031
3032
3033

3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052


3053
3054
3055
3056
3057

3058
3059
3060
3061
3062
3063
3064
3230
3231
3232
3233
3234
3235
3236

3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254


3255
3256
3257
3258
3259
3260

3261
3262
3263
3264
3265
3266
3267
3268







-
+

















-
-
+
+




-
+







** ^The sqlite3_malloc64(N) routine works just like
** sqlite3_malloc(N) except that N is an unsigned 64-bit integer instead
** of a signed 32-bit integer.
**
** ^Calling sqlite3_free() with a pointer previously returned
** by sqlite3_malloc() or sqlite3_realloc() releases that memory so
** that it might be reused.  ^The sqlite3_free() routine is
** a no-op if is called with a NULL pointer.  Passing a NULL pointer
** a no-op if it is called with a NULL pointer.  Passing a NULL pointer
** to sqlite3_free() is harmless.  After being freed, memory
** should neither be read nor written.  Even reading previously freed
** memory might result in a segmentation fault or other severe error.
** Memory corruption, a segmentation fault, or other severe error
** might result if sqlite3_free() is called with a non-NULL pointer that
** was not obtained from sqlite3_malloc() or sqlite3_realloc().
**
** ^The sqlite3_realloc(X,N) interface attempts to resize a
** prior memory allocation X to be at least N bytes.
** ^If the X parameter to sqlite3_realloc(X,N)
** is a NULL pointer then its behavior is identical to calling
** sqlite3_malloc(N).
** ^If the N parameter to sqlite3_realloc(X,N) is zero or
** negative then the behavior is exactly the same as calling
** sqlite3_free(X).
** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation
** of at least N bytes in size or NULL if insufficient memory is available.
** ^If M is the size of the prior allocation, then min(N,M) bytes
** of the prior allocation are copied into the beginning of buffer returned
** ^If M is the size of the prior allocation, then min(N,M) bytes of the
** prior allocation are copied into the beginning of the buffer returned
** by sqlite3_realloc(X,N) and the prior allocation is freed.
** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the
** prior allocation is not freed.
**
** ^The sqlite3_realloc64(X,N) interfaces works the same as
** ^The sqlite3_realloc64(X,N) interface works the same as
** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead
** of a 32-bit signed integer.
**
** ^If X is a memory allocation previously obtained from sqlite3_malloc(),
** sqlite3_malloc64(), sqlite3_realloc(), or sqlite3_realloc64(), then
** sqlite3_msize(X) returns the size of that memory allocation in bytes.
** ^The value returned by sqlite3_msize(X) might be larger than the number
3100
3101
3102
3103
3104
3105
3106
3107

3108
3109
3110
3111
3112
3113
3114
3304
3305
3306
3307
3308
3309
3310

3311
3312
3313
3314
3315
3316
3317
3318







-
+







** ^The [sqlite3_memory_used()] routine returns the number of bytes
** of memory currently outstanding (malloced but not freed).
** ^The [sqlite3_memory_highwater()] routine returns the maximum
** value of [sqlite3_memory_used()] since the high-water mark
** was last reset.  ^The values returned by [sqlite3_memory_used()] and
** [sqlite3_memory_highwater()] include any overhead
** added by SQLite in its implementation of [sqlite3_malloc()],
** but not overhead added by the any underlying system library
** but not overhead added by any underlying system library
** routines that [sqlite3_malloc()] may call.
**
** ^The memory high-water mark is reset to the current value of
** [sqlite3_memory_used()] if and only if the parameter to
** [sqlite3_memory_highwater()] is true.  ^The value returned
** by [sqlite3_memory_highwater(1)] is the high-water mark
** prior to the reset.
3552
3553
3554
3555
3556
3557
3558
3559

3560
3561
3562
3563
3564
3565
3566
3567

3568
3569
3570
3571
3572
3573
3574
3756
3757
3758
3759
3760
3761
3762

3763
3764
3765
3766
3767
3768
3769
3770

3771
3772
3773
3774
3775
3776
3777
3778







-
+







-
+







** <dd>The new database connection will use the "serialized"
** [threading mode].)^  This means the multiple threads can safely
** attempt to use the same database connection at the same time.
** (Mutexes will block any actual concurrency, but in this mode
** there is no harm in trying.)
**
** ^(<dt>[SQLITE_OPEN_SHAREDCACHE]</dt>
** <dd>The database is opened [shared cache] enabled, overriding
** <dd>The database is opened with [shared cache] enabled, overriding
** the default shared cache setting provided by
** [sqlite3_enable_shared_cache()].)^
** The [use of shared cache mode is discouraged] and hence shared cache
** capabilities may be omitted from many builds of SQLite.  In such cases,
** this option is a no-op.
**
** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt>
** <dd>The database is opened [shared cache] disabled, overriding
** <dd>The database is opened with [shared cache] disabled, overriding
** the default shared cache setting provided by
** [sqlite3_enable_shared_cache()].)^
**
** [[OPEN_EXRESCODE]] ^(<dt>[SQLITE_OPEN_EXRESCODE]</dt>
** <dd>The database connection comes up in "extended result code mode".
** In other words, the database behaves as if
** [sqlite3_extended_result_codes(db,1)] were called on the database
3895
3896
3897
3898
3899
3900
3901
3902

3903
3904
3905
3906
3907
3908
3909
4099
4100
4101
4102
4103
4104
4105

4106
4107
4108
4109
4110
4111
4112
4113







-
+







** CAPI3REF: Create and Destroy VFS Filenames
**
** These interfaces are provided for use by [VFS shim] implementations and
** are not useful outside of that context.
**
** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of
** database filename D with corresponding journal file J and WAL file W and
** with N URI parameters key/values pairs in the array P.  The result from
** an array P of N URI Key/Value pairs.  The result from
** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that
** is safe to pass to routines like:
** <ul>
** <li> [sqlite3_uri_parameter()],
** <li> [sqlite3_uri_boolean()],
** <li> [sqlite3_uri_int64()],
** <li> [sqlite3_uri_key()],
3966
3967
3968
3969
3970
3971
3972

3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985

3986
3987
3988
3989
3990
3991
3992
3993

3994
3995
3996
3997
3998
3999
4000
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189

4190
4191
4192
4193
4194
4195
4196
4197

4198
4199
4200
4201
4202
4203
4204
4205







+












-
+







-
+







**
** <ul>
** <li> sqlite3_errcode()
** <li> sqlite3_extended_errcode()
** <li> sqlite3_errmsg()
** <li> sqlite3_errmsg16()
** <li> sqlite3_error_offset()
** <li> sqlite3_db_handle()
** </ul>
**
** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language
** text that describes the error, as either UTF-8 or UTF-16 respectively,
** or NULL if no error message is available.
** (See how SQLite handles [invalid UTF] for exceptions to this rule.)
** ^(Memory to hold the error message string is managed internally.
** The application does not need to worry about freeing the result.
** However, the error string might be overwritten or deallocated by
** subsequent calls to other SQLite interface functions.)^
**
** ^The sqlite3_errstr(E) interface returns the English-language text
** that describes the [result code] E, as UTF-8, or NULL if E is not an
** that describes the [result code] E, as UTF-8, or NULL if E is not a
** result code for which a text error message is available.
** ^(Memory to hold the error message string is managed internally
** and must not be freed by the application)^.
**
** ^If the most recent error references a specific token in the input
** SQL, the sqlite3_error_offset() interface returns the byte offset
** of the start of that token.  ^The byte offset returned by
** sqlite3_error_offset() assumes that the input SQL is UTF8.
** sqlite3_error_offset() assumes that the input SQL is UTF-8.
** ^If the most recent error does not reference a specific token in the input
** SQL, then the sqlite3_error_offset() function returns -1.
**
** When the serialized [threading mode] is in use, it might be the
** case that a second error occurs on a separate thread in between
** the time of the first error and the call to these interfaces.
** When that happens, the second error will be reported since these
4011
4012
4013
4014
4015
4016
4017




























4018
4019
4020
4021
4022
4023
4024
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







SQLITE_API int sqlite3_errcode(sqlite3 *db);
SQLITE_API int sqlite3_extended_errcode(sqlite3 *db);
SQLITE_API const char *sqlite3_errmsg(sqlite3*);
SQLITE_API const void *sqlite3_errmsg16(sqlite3*);
SQLITE_API const char *sqlite3_errstr(int);
SQLITE_API int sqlite3_error_offset(sqlite3 *db);

/*
** CAPI3REF: Set Error Code And Message
** METHOD: sqlite3
**
** Set the error code of the database handle passed as the first argument
** to errcode, and the error message to a copy of nul-terminated string
** zErrMsg. If zErrMsg is passed NULL, then the error message is set to
** the default message associated with the supplied error code.  Subsequent
** calls to [sqlite3_errcode()] and [sqlite3_errmsg()] and similar will
** return the values set by this routine in place of what was previously
** set by SQLite itself.
**
** This function returns SQLITE_OK if the error code and error message are
** successfully set, SQLITE_NOMEM if an OOM occurs, and SQLITE_MISUSE if
** the database handle is NULL or invalid.
**
** The error code and message set by this routine remains in effect until
** they are changed, either by another call to this routine or until they are
** changed to by SQLite itself to reflect the result of some subsquent
** API call.
**
** This function is intended for use by SQLite extensions or wrappers.  The
** idea is that an extension or wrapper can use this routine to set error
** messages and error codes and thus behave more like a core SQLite
** feature from the point of view of an application.
*/
SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zErrMsg);

/*
** CAPI3REF: Prepared Statement Object
** KEYWORDS: {prepared statement} {prepared statements}
**
** An instance of this object represents a single SQL statement that
** has been compiled into binary form and is ready to be evaluated.
**
4085
4086
4087
4088
4089
4090
4091
4092
4093


4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108




4109
4110
4111
4112
4113
4114
4115
4318
4319
4320
4321
4322
4323
4324


4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352







-
-
+
+















+
+
+
+








/*
** CAPI3REF: Run-Time Limit Categories
** KEYWORDS: {limit category} {*limit categories}
**
** These constants define various performance limits
** that can be lowered at run-time using [sqlite3_limit()].
** The synopsis of the meanings of the various limits is shown below.
** Additional information is available at [limits | Limits in SQLite].
** A concise description of these limits follows, and additional information
** is available at [limits | Limits in SQLite].
**
** <dl>
** [[SQLITE_LIMIT_LENGTH]] ^(<dt>SQLITE_LIMIT_LENGTH</dt>
** <dd>The maximum size of any string or BLOB or table row, in bytes.<dd>)^
**
** [[SQLITE_LIMIT_SQL_LENGTH]] ^(<dt>SQLITE_LIMIT_SQL_LENGTH</dt>
** <dd>The maximum length of an SQL statement, in bytes.</dd>)^
**
** [[SQLITE_LIMIT_COLUMN]] ^(<dt>SQLITE_LIMIT_COLUMN</dt>
** <dd>The maximum number of columns in a table definition or in the
** result set of a [SELECT] or the maximum number of columns in an index
** or in an ORDER BY or GROUP BY clause.</dd>)^
**
** [[SQLITE_LIMIT_EXPR_DEPTH]] ^(<dt>SQLITE_LIMIT_EXPR_DEPTH</dt>
** <dd>The maximum depth of the parse tree on any expression.</dd>)^
**
** [[SQLITE_LIMIT_PARSER_DEPTH]] ^(<dt>SQLITE_LIMIT_PARSER_DEPTH</dt>
** <dd>The maximum depth of the LALR(1) parser stack used to analyze
** input SQL statements.</dd>)^
**
** [[SQLITE_LIMIT_COMPOUND_SELECT]] ^(<dt>SQLITE_LIMIT_COMPOUND_SELECT</dt>
** <dd>The maximum number of terms in a compound SELECT statement.</dd>)^
**
** [[SQLITE_LIMIT_VDBE_OP]] ^(<dt>SQLITE_LIMIT_VDBE_OP</dt>
** <dd>The maximum number of instructions in a virtual machine program
** used to implement an SQL statement.  If [sqlite3_prepare_v2()] or
4147
4148
4149
4150
4151
4152
4153

4154
4155
4156
4157
4158

4159
4160
4161
4162
4163
4164
4165
4384
4385
4386
4387
4388
4389
4390
4391
4392
4393
4394
4395

4396
4397
4398
4399
4400
4401
4402
4403







+




-
+







#define SQLITE_LIMIT_VDBE_OP                   5
#define SQLITE_LIMIT_FUNCTION_ARG              6
#define SQLITE_LIMIT_ATTACHED                  7
#define SQLITE_LIMIT_LIKE_PATTERN_LENGTH       8
#define SQLITE_LIMIT_VARIABLE_NUMBER           9
#define SQLITE_LIMIT_TRIGGER_DEPTH            10
#define SQLITE_LIMIT_WORKER_THREADS           11
#define SQLITE_LIMIT_PARSER_DEPTH             12

/*
** CAPI3REF: Prepare Flags
**
** These constants define various flags that can be passed into
** These constants define various flags that can be passed into the
** "prepFlags" parameter of the [sqlite3_prepare_v3()] and
** [sqlite3_prepare16_v3()] interfaces.
**
** New flags may be added in future releases of SQLite.
**
** <dl>
** [[SQLITE_PREPARE_PERSISTENT]] ^(<dt>SQLITE_PREPARE_PERSISTENT</dt>
4181
4182
4183
4184
4185
4186
4187

























4188
4189
4190
4191
4192


4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207



4208
4209
4210
4211
4212
4213
4214
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470


4471
4472
4473
4474
4475
4476
4477
4478
4479
4480







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





+
+













-
-
+
+
+







** prepared statements, regardless of whether or not they use this
** flag.
**
** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt>
** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler
** to return an error (error code SQLITE_ERROR) if the statement uses
** any virtual tables.
**
** [[SQLITE_PREPARE_DONT_LOG]] <dt>SQLITE_PREPARE_DONT_LOG</dt>
** <dd>The SQLITE_PREPARE_DONT_LOG flag prevents SQL compiler
** errors from being sent to the error log defined by
** [SQLITE_CONFIG_LOG].  This can be used, for example, to do test
** compiles to see if some SQL syntax is well-formed, without generating
** messages on the global error log when it is not.  If the test compile
** fails, the sqlite3_prepare_v3() call returns the same error indications
** with or without this flag; it just omits the call to [sqlite3_log()] that
** logs the error.
**
** [[SQLITE_PREPARE_FROM_DDL]] <dt>SQLITE_PREPARE_FROM_DDL</dt>
** <dd>The SQLITE_PREPARE_FROM_DDL flag causes the SQL compiler to behave as if
** the SQL statement is part of a database schema. This makes a difference
** when the [SQLITE_DBCONFIG_TRUSTED_SCHEMA] option is set to off.
** When this option is used and SQLITE_DBCONFIG_TRUSTED_SCHEMA is off,
** SQL functions may not be called unless they are tagged with
** [SQLITE_INNOCUOUS] and virtual tables may not be used unless tagged
** with [SQLITE_VTAB_INNOCUOUS].  Use the SQLITE_PREPARE_FROM_DDL option
** when preparing SQL that is derived from parts of the database
** schema. In particular, virtual table implementations that
** run SQL statements based on the arguments to their CREATE VIRTUAL
** TABLE statement should use [sqlite3_prepare_v3()] and set the
** SQLITE_PREPARE_FROM_DLL flag to prevent bypass of the
** [SQLITE_DBCONFIG_TRUSTED_SCHEMA] security checks.
** </dl>
*/
#define SQLITE_PREPARE_PERSISTENT              0x01
#define SQLITE_PREPARE_NORMALIZE               0x02
#define SQLITE_PREPARE_NO_VTAB                 0x04
#define SQLITE_PREPARE_DONT_LOG                0x10
#define SQLITE_PREPARE_FROM_DDL                0x20

/*
** CAPI3REF: Compiling An SQL Statement
** KEYWORDS: {SQL statement compiler}
** METHOD: sqlite3
** CONSTRUCTOR: sqlite3_stmt
**
** To execute an SQL statement, it must first be compiled into a byte-code
** program using one of these routines.  Or, in other words, these routines
** are constructors for the [prepared statement] object.
**
** The preferred routine to use is [sqlite3_prepare_v2()].  The
** [sqlite3_prepare()] interface is legacy and should be avoided.
** [sqlite3_prepare_v3()] has an extra "prepFlags" option that is used
** for special purposes.
** [sqlite3_prepare_v3()] has an extra
** [SQLITE_PREPARE_FROM_DDL|"prepFlags" option] that is some times
** needed for special purpose or to pass along security restrictions.
**
** The use of the UTF-8 interfaces is preferred, as SQLite currently
** does all parsing using UTF-8.  The UTF-16 interfaces are provided
** as a convenience.  The UTF-16 interfaces work by converting the
** input text into UTF-8, then invoking the corresponding UTF-8 interface.
**
** The first argument, "db", is a [database connection] obtained from a
4227
4228
4229
4230
4231
4232
4233
4234

4235
4236
4237
4238
4239
4240
4241
4493
4494
4495
4496
4497
4498
4499

4500
4501
4502
4503
4504
4505
4506
4507







-
+







** up to the first zero terminator or until the nByte bytes have been read,
** whichever comes first.  ^If nByte is zero, then no prepared
** statement is generated.
** If the caller knows that the supplied string is nul-terminated, then
** there is a small performance advantage to passing an nByte parameter that
** is the number of bytes in the input string <i>including</i>
** the nul-terminator.
** Note that nByte measure the length of the input in bytes, not
** Note that nByte measures the length of the input in bytes, not
** characters, even for the UTF-16 interfaces.
**
** ^If pzTail is not NULL then *pzTail is made to point to the first byte
** past the end of the first SQL statement in zSql.  These routines only
** compile the first statement in zSql, so *pzTail is left pointing to
** what remains uncompiled.
**
4361
4362
4363
4364
4365
4366
4367
4368

4369
4370
4371
4372
4373
4374
4375
4627
4628
4629
4630
4631
4632
4633

4634
4635
4636
4637
4638
4639
4640
4641







-
+







** text "SELECT $abc,:xyz" and if parameter $abc is bound to integer 2345
** and parameter :xyz is unbound, then sqlite3_sql() will return
** the original string, "SELECT $abc,:xyz" but sqlite3_expanded_sql()
** will return "SELECT 2345,NULL".)^
**
** ^The sqlite3_expanded_sql() interface returns NULL if insufficient memory
** is available to hold the result, or if the result would exceed the
** the maximum string length determined by the [SQLITE_LIMIT_LENGTH].
** maximum string length determined by the [SQLITE_LIMIT_LENGTH].
**
** ^The [SQLITE_TRACE_SIZE_LIMIT] compile-time option limits the size of
** bound parameter expansions.  ^The [SQLITE_OMIT_TRACE] compile-time
** option causes sqlite3_expanded_sql() to always return NULL.
**
** ^The strings returned by sqlite3_sql(P) and sqlite3_normalized_sql(P)
** are managed by SQLite and are automatically freed when the prepared
4549
4550
4551
4552
4553
4554
4555
4556

4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572

4573
4574
4575
4576
4577
4578
4579
4815
4816
4817
4818
4819
4820
4821

4822
4823
4824
4825
4826
4827
4828
4829
4830
4831
4832
4833
4834
4835
4836
4837

4838
4839
4840
4841
4842
4843
4844
4845







-
+















-
+







typedef struct sqlite3_value sqlite3_value;

/*
** CAPI3REF: SQL Function Context Object
**
** The context in which an SQL function executes is stored in an
** sqlite3_context object.  ^A pointer to an sqlite3_context object
** is always first parameter to [application-defined SQL functions].
** is always the first parameter to [application-defined SQL functions].
** The application-defined SQL function implementation will pass this
** pointer through into calls to [sqlite3_result_int | sqlite3_result()],
** [sqlite3_aggregate_context()], [sqlite3_user_data()],
** [sqlite3_context_db_handle()], [sqlite3_get_auxdata()],
** and/or [sqlite3_set_auxdata()].
*/
typedef struct sqlite3_context sqlite3_context;

/*
** CAPI3REF: Binding Values To Prepared Statements
** KEYWORDS: {host parameter} {host parameters} {host parameter name}
** KEYWORDS: {SQL parameter} {SQL parameters} {parameter binding}
** METHOD: sqlite3_stmt
**
** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants,
** literals may be replaced by a [parameter] that matches one of following
** literals may be replaced by a [parameter] that matches one of the following
** templates:
**
** <ul>
** <li>  ?
** <li>  ?NNN
** <li>  :VVV
** <li>  @VVV
4605
4606
4607
4608
4609
4610
4611
4612
4613


4614
4615
4616
4617

4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637

4638
4639
4640
4641
4642
4643
4644
4871
4872
4873
4874
4875
4876
4877


4878
4879
4880
4881
4882

4883
4884
4885
4886
4887
4888
4889
4890
4891
4892
4893
4894
4895
4896
4897
4898
4899
4900
4901
4902

4903
4904
4905
4906
4907
4908
4909
4910







-
-
+
+



-
+



















-
+







** is ignored and the end result is the same as sqlite3_bind_null().
** ^If the third parameter to sqlite3_bind_text() is not NULL, then
** it should be a pointer to well-formed UTF8 text.
** ^If the third parameter to sqlite3_bind_text16() is not NULL, then
** it should be a pointer to well-formed UTF16 text.
** ^If the third parameter to sqlite3_bind_text64() is not NULL, then
** it should be a pointer to a well-formed unicode string that is
** either UTF8 if the sixth parameter is SQLITE_UTF8, or UTF16
** otherwise.
** either UTF8 if the sixth parameter is SQLITE_UTF8 or SQLITE_UTF8_ZT,
** or UTF16 otherwise.
**
** [[byte-order determination rules]] ^The byte-order of
** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF)
** found in first character, which is removed, or in the absence of a BOM
** found in the first character, which is removed, or in the absence of a BOM
** the byte order is the native byte order of the host
** machine for sqlite3_bind_text16() or the byte order specified in
** the 6th parameter for sqlite3_bind_text64().)^
** ^If UTF16 input text contains invalid unicode
** characters, then SQLite might change those invalid characters
** into the unicode replacement character: U+FFFD.
**
** ^(In those routines that have a fourth argument, its value is the
** number of bytes in the parameter.  To be clear: the value is the
** number of <u>bytes</u> in the value, not the number of characters.)^
** ^If the fourth parameter to sqlite3_bind_text() or sqlite3_bind_text16()
** is negative, then the length of the string is
** the number of bytes up to the first zero terminator.
** If the fourth parameter to sqlite3_bind_blob() is negative, then
** the behavior is undefined.
** If a non-negative fourth parameter is provided to sqlite3_bind_text()
** or sqlite3_bind_text16() or sqlite3_bind_text64() then
** that parameter must be the byte offset
** where the NUL terminator would occur assuming the string were NUL
** terminated.  If any NUL characters occurs at byte offsets less than
** terminated.  If any NUL characters occur at byte offsets less than
** the value of the fourth parameter then the resulting string value will
** contain embedded NULs.  The result of expressions involving strings
** with embedded NULs is undefined.
**
** ^The fifth argument to the BLOB and string binding interfaces controls
** or indicates the lifetime of the object referenced by the third parameter.
** These three options exist:
4652
4653
4654
4655
4656
4657
4658
4659
4660
4661
4662









4663
4664
4665
4666
4667
4668
4669
4670
4671
4672
4673
4674
4675
4676
4677
4678
4679


4680
4681
4682



4683
4684
4685
4686
4687
4688
4689
4918
4919
4920
4921
4922
4923
4924




4925
4926
4927
4928
4929
4930
4931
4932
4933
4934
4935
4936
4937
4938
4939
4940
4941
4942
4943
4944
4945
4946
4947
4948
4949
4950
4951
4952



4953
4954
4955
4956
4957
4958
4959
4960
4961
4962







-
-
-
-
+
+
+
+
+
+
+
+
+

















+
+
-
-
-
+
+
+







** either the prepared statement is finalized or the same SQL parameter is
** bound to something else, whichever occurs sooner.
** ^ (3) The constant, [SQLITE_TRANSIENT], may be passed to indicate that the
** object is to be copied prior to the return from sqlite3_bind_*(). ^The
** object and pointer to it must remain valid until then. ^SQLite will then
** manage the lifetime of its private copy.
**
** ^The sixth argument to sqlite3_bind_text64() must be one of
** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]
** to specify the encoding of the text in the third parameter.  If
** the sixth argument to sqlite3_bind_text64() is not one of the
** ^The sixth argument (the E argument)
** to sqlite3_bind_text64(S,K,Z,N,D,E) must be one of
** [SQLITE_UTF8], [SQLITE_UTF8_ZT], [SQLITE_UTF16], [SQLITE_UTF16BE],
** or [SQLITE_UTF16LE] to specify the encoding of the text in the
** third parameter, Z.  The special value [SQLITE_UTF8_ZT] means that the
** string argument is both UTF-8 encoded and is zero-terminated.  In other
** words, SQLITE_UTF8_ZT means that the Z array is allocated to hold at
** least N+1 bytes and that the Z&#91;N&#93; byte is zero.  If
** the E argument to sqlite3_bind_text64(S,K,Z,N,D,E) is not one of the
** allowed values shown above, or if the text encoding is different
** from the encoding specified by the sixth parameter, then the behavior
** is undefined.
**
** ^The sqlite3_bind_zeroblob() routine binds a BLOB of length N that
** is filled with zeroes.  ^A zeroblob uses a fixed amount of memory
** (just an integer to hold its size) while it is being processed.
** Zeroblobs are intended to serve as placeholders for BLOBs whose
** content is later written using
** [sqlite3_blob_open | incremental BLOB I/O] routines.
** ^A negative value for the zeroblob results in a zero-length BLOB.
**
** ^The sqlite3_bind_pointer(S,I,P,T,D) routine causes the I-th parameter in
** [prepared statement] S to have an SQL value of NULL, but to also be
** associated with the pointer P of type T.  ^D is either a NULL pointer or
** a pointer to a destructor function for P. ^SQLite will invoke the
** destructor D with a single argument of P when it is finished using
** P, even if the call to sqlite3_bind_pointer() fails.  Due to a
** historical design quirk, results are undefined if D is
** P.  The T parameter should be a static string, preferably a string
** literal. The sqlite3_bind_pointer() routine is part of the
** [pointer passing interface] added for SQLite 3.20.0.
** SQLITE_TRANSIENT. The T parameter should be a static string,
** preferably a string literal. The sqlite3_bind_pointer() routine is
** part of the [pointer passing interface] added for SQLite 3.20.0.
**
** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer
** for the [prepared statement] or with a prepared statement for which
** [sqlite3_step()] has been called more recently than [sqlite3_reset()],
** then the call will return [SQLITE_MISUSE].  If any sqlite3_bind_()
** routine is passed a [prepared statement] that has been finalized, the
** result is undefined and probably harmful.
4842
4843
4844
4845
4846
4847
4848
4849

4850
4851
4852
4853
4854
4855
4856
5115
5116
5117
5118
5119
5120
5121

5122
5123
5124
5125
5126
5127
5128
5129







-
+







SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N);

/*
** CAPI3REF: Source Of Data In A Query Result
** METHOD: sqlite3_stmt
**
** ^These routines provide a means to determine the database, table, and
** table column that is the origin of a particular result column in
** table column that is the origin of a particular result column in a
** [SELECT] statement.
** ^The name of the database or table or column can be returned as
** either a UTF-8 or UTF-16 string.  ^The _database_ routines return
** the database name, the _table_ routines return the table name, and
** the origin_ routines return the column name.
** ^The returned string is valid until the [prepared statement] is destroyed
** using [sqlite3_finalize()] or until the statement is automatically
4980
4981
4982
4983
4984
4985
4986
4987

4988
4989
4990
4991
4992
4993
4994
5253
5254
5255
5256
5257
5258
5259

5260
5261
5262
5263
5264
5265
5266
5267







-
+







** more threads at the same moment in time.
**
** For all versions of SQLite up to and including 3.6.23.1, a call to
** [sqlite3_reset()] was required after sqlite3_step() returned anything
** other than [SQLITE_ROW] before any subsequent invocation of
** sqlite3_step().  Failure to reset the prepared statement using
** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from
** sqlite3_step().  But after [version 3.6.23.1] ([dateof:3.6.23.1],
** sqlite3_step().  But after [version 3.6.23.1] ([dateof:3.6.23.1]),
** sqlite3_step() began
** calling [sqlite3_reset()] automatically in this circumstance rather
** than returning [SQLITE_MISUSE].  This is not considered a compatibility
** break because any application that ever receives an SQLITE_MISUSE error
** is broken by definition.  The [SQLITE_OMIT_AUTORESET] compile-time option
** can be used to restore the legacy behavior.
**
5286
5287
5288
5289
5290
5291
5292
5293

5294
5295
5296
5297
5298
5299
5300
5559
5560
5561
5562
5563
5564
5565

5566
5567
5568
5569
5570
5571
5572
5573







-
+








/*
** CAPI3REF: Destroy A Prepared Statement Object
** DESTRUCTOR: sqlite3_stmt
**
** ^The sqlite3_finalize() function is called to delete a [prepared statement].
** ^If the most recent evaluation of the statement encountered no errors
** or if the statement is never been evaluated, then sqlite3_finalize() returns
** or if the statement has never been evaluated, then sqlite3_finalize() returns
** SQLITE_OK.  ^If the most recent evaluation of statement S failed, then
** sqlite3_finalize(S) returns the appropriate [error code] or
** [extended error code].
**
** ^The sqlite3_finalize(S) routine can be called at any point during
** the life cycle of [prepared statement] S:
** before statement S is ever evaluated, after
5411
5412
5413
5414
5415
5416
5417
5418
5419


5420
5421
5422
5423
5424
5425
5426
5684
5685
5686
5687
5688
5689
5690


5691
5692
5693
5694
5695
5696
5697
5698
5699







-
-
+
+







** ^The fourth parameter may also optionally include the [SQLITE_DIRECTONLY]
** flag, which if present prevents the function from being invoked from
** within VIEWs, TRIGGERs, CHECK constraints, generated column expressions,
** index expressions, or the WHERE clause of partial indexes.
**
** For best security, the [SQLITE_DIRECTONLY] flag is recommended for
** all application-defined SQL functions that do not need to be
** used inside of triggers, view, CHECK constraints, or other elements of
** the database schema.  This flags is especially recommended for SQL
** used inside of triggers, views, CHECK constraints, or other elements of
** the database schema.  This flag is especially recommended for SQL
** functions that have side effects or reveal internal application state.
** Without this flag, an attacker might be able to modify the schema of
** a database file to include invocations of the function with parameters
** chosen by the attacker, which the application will then execute when
** the database file is opened and read.
**
** ^(The fifth parameter is an arbitrary pointer.  The implementation of the
5443
5444
5445
5446
5447
5448
5449
5450

5451
5452
5453
5454
5455
5456
5457
5716
5717
5718
5719
5720
5721
5722

5723
5724
5725
5726
5727
5728
5729
5730







-
+







** which case a regular aggregate function is created, or must both be
** non-NULL, in which case the new function may be used as either an aggregate
** or aggregate window function. More details regarding the implementation
** of aggregate window functions are
** [user-defined window functions|available here].
**
** ^(If the final parameter to sqlite3_create_function_v2() or
** sqlite3_create_window_function() is not NULL, then it is destructor for
** sqlite3_create_window_function() is not NULL, then it is the destructor for
** the application data pointer. The destructor is invoked when the function
** is deleted, either by being overloaded or when the database connection
** closes.)^ ^The destructor is also invoked if the call to
** sqlite3_create_function_v2() fails.  ^When the destructor callback is
** invoked, it is passed a single argument which is a copy of the application
** data pointer which was the fifth parameter to sqlite3_create_function_v2().
**
5518
5519
5520
5521
5522
5523
5524
5525

5526













































5527
5528
5529
5530
5531
5532
5533

5534
5535
5536
5537
5538
5539
5540
5791
5792
5793
5794
5795
5796
5797

5798
5799
5800
5801
5802
5803
5804
5805
5806
5807
5808
5809
5810
5811
5812
5813
5814
5815
5816
5817
5818
5819
5820
5821
5822
5823
5824
5825
5826
5827
5828
5829
5830
5831
5832
5833
5834
5835
5836
5837
5838
5839
5840
5841
5842
5843
5844
5845
5846
5847
5848
5849
5850
5851
5852
5853
5854
5855
5856
5857
5858
5859







-
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







+







  void (*xInverse)(sqlite3_context*,int,sqlite3_value**),
  void(*xDestroy)(void*)
);

/*
** CAPI3REF: Text Encodings
**
** These constant define integer codes that represent the various
** These constants define integer codes that represent the various
** text encodings supported by SQLite.
**
** <dl>
** [[SQLITE_UTF8]] <dt>SQLITE_UTF8</dt><dd>Text is encoding as UTF-8</dd>
**
** [[SQLITE_UTF16LE]] <dt>SQLITE_UTF16LE</dt><dd>Text is encoding as UTF-16
** with each code point being expressed "little endian" - the least significant
** byte first.  This is the usual encoding, for example on Windows.</dd>
**
** [[SQLITE_UTF16BE]] <dt>SQLITE_UTF16BE</dt><dd>Text is encoding as UTF-16
** with each code point being expressed "big endian" - the most significant
** byte first.  This encoding is less common, but is still sometimes seen,
** specially on older systems.
**
** [[SQLITE_UTF16]] <dt>SQLITE_UTF16</dt><dd>Text is encoding as UTF-16
** with each code point being expressed either little endian or as big
** endian, according to the native endianness of the host computer.
**
** [[SQLITE_ANY]] <dt>SQLITE_ANY</dt><dd>This encoding value may only be used
** to declare the preferred text for [application-defined SQL functions]
** created using [sqlite3_create_function()] and similar.  If the preferred
** encoding (the 4th parameter to sqlite3_create_function() - the eTextRep
** parameter) is SQLITE_ANY, that indicates that the function does not have
** a preference regarding the text encoding of its parameters and can take
** any text encoding that the SQLite core find convenient to supply.  This
** option is deprecated.  Please do not use it in new applications.
**
** [[SQLITE_UTF16_ALIGNED]] <dt>SQLITE_UTF16_ALIGNED</dt><dd>This encoding
** value may be used as the 3rd parameter (the eTextRep parameter) to
** [sqlite3_create_collation()] and similar.  This encoding value means
** that the application-defined collating sequence created expects its
** input strings to be in UTF16 in native byte order, and that the start
** of the strings must be aligned to a 2-byte boundary.
**
** [[SQLITE_UTF8_ZT]] <dt>SQLITE_UTF8_ZT</dt><dd>This option can only be
** used to specify the text encoding to strings input to [sqlite3_result_text64()]
** and [sqlite3_bind_text64()].  It means that the input string (call it "z")
** is UTF-8 encoded and that it is zero-terminated.  If the length parameter
** (call it "n") is non-negative, this encoding option means that the caller
** guarantees that z array contains at least n+1 bytes and that the z&#91;n&#93;
** byte has a value of zero.
** This option gives the same output as SQLITE_UTF8, but can be more efficient
** by avoiding the need to make a copy of the input string, in some cases.
** However, if z is allocated to hold fewer than n+1 bytes or if the
** z&#91;n&#93; byte is not zero, undefined behavior may result.
** </dl>
*/
#define SQLITE_UTF8           1    /* IMP: R-37514-35566 */
#define SQLITE_UTF16LE        2    /* IMP: R-03371-37637 */
#define SQLITE_UTF16BE        3    /* IMP: R-51971-34154 */
#define SQLITE_UTF16          4    /* Use native byte order */
#define SQLITE_ANY            5    /* Deprecated */
#define SQLITE_UTF16_ALIGNED  8    /* sqlite3_create_collation only */
#define SQLITE_UTF8_ZT       16    /* Zero-terminated UTF8 */

/*
** CAPI3REF: Function Flags
**
** These constants may be ORed together with the
** [SQLITE_UTF8 | preferred text encoding] as the fourth argument
** to [sqlite3_create_function()], [sqlite3_create_function16()], or
5610
5611
5612
5613
5614
5615
5616
5617

5618
5619
5620
5621
5622
5623
5624
5929
5930
5931
5932
5933
5934
5935

5936
5937
5938
5939
5940
5941
5942
5943







-
+







**
** [[SQLITE_RESULT_SUBTYPE]] <dt>SQLITE_RESULT_SUBTYPE</dt><dd>
** The SQLITE_RESULT_SUBTYPE flag indicates to SQLite that a function might call
** [sqlite3_result_subtype()] to cause a sub-type to be associated with its
** result.
** Every function that invokes [sqlite3_result_subtype()] should have this
** property.  If it does not, then the call to [sqlite3_result_subtype()]
** might become a no-op if the function is used as term in an
** might become a no-op if the function is used as a term in an
** [expression index].  On the other hand, SQL functions that never invoke
** [sqlite3_result_subtype()] should avoid setting this property, as the
** purpose of this property is to disable certain optimizations that are
** incompatible with subtypes.
**
** [[SQLITE_SELFORDER1]] <dt>SQLITE_SELFORDER1</dt><dd>
** The SQLITE_SELFORDER1 flag indicates that the function is an aggregate
5737
5738
5739
5740
5741
5742
5743
5744

5745
5746
5747
5748
5749
5750
5751
6056
6057
6058
6059
6060
6061
6062

6063
6064
6065
6066
6067
6068
6069
6070







-
+







** then the conversion is performed.  Otherwise no conversion occurs.
** The [SQLITE_INTEGER | datatype] after conversion is returned.)^
**
** ^Within the [xUpdate] method of a [virtual table], the
** sqlite3_value_nochange(X) interface returns true if and only if
** the column corresponding to X is unchanged by the UPDATE operation
** that the xUpdate method call was invoked to implement and if
** and the prior [xColumn] method call that was invoked to extracted
** the prior [xColumn] method call that was invoked to extract
** the value for that column returned without setting a result (probably
** because it queried [sqlite3_vtab_nochange()] and found that the column
** was unchanging).  ^Within an [xUpdate] method, any value for which
** sqlite3_value_nochange(X) is true will in all other respects appear
** to be a NULL value.  If sqlite3_value_nochange(X) is invoked anywhere other
** than within an [xUpdate] method call for an UPDATE statement, then
** the return value is arbitrary and meaningless.
5843
5844
5845
5846
5847
5848
5849
5850

5851
5852
5853
5854
5855
5856
5857
6162
6163
6164
6165
6166
6167
6168

6169
6170
6171
6172
6173
6174
6175
6176







-
+







SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*);

/*
** CAPI3REF: Copy And Free SQL Values
** METHOD: sqlite3_value
**
** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value]
** object D and returns a pointer to that copy.  ^The [sqlite3_value] returned
** object V and returns a pointer to that copy.  ^The [sqlite3_value] returned
** is a [protected sqlite3_value] object even if the input is not.
** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a
** memory allocation fails. ^If V is a [pointer value], then the result
** of sqlite3_value_dup(V) is a NULL value.
**
** ^The sqlite3_value_free(V) interface frees an [sqlite3_value] object
** previously obtained from [sqlite3_value_dup()].  ^If V is a NULL pointer
5881
5882
5883
5884
5885
5886
5887
5888

5889
5890
5891
5892
5893
5894
5895
6200
6201
6202
6203
6204
6205
6206

6207
6208
6209
6210
6211
6212
6213
6214







-
+







** first time from within xFinal().)^
**
** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer
** when first called if N is less than or equal to zero or if a memory
** allocation error occurs.
**
** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is
** determined by the N parameter on first successful call.  Changing the
** determined by the N parameter on the first successful call.  Changing the
** value of N in any subsequent call to sqlite3_aggregate_context() within
** the same aggregate function instance will not resize the memory
** allocation.)^  Within the xFinal callback, it is customary to set
** N=0 in calls to sqlite3_aggregate_context(C,N) so that no
** pointless memory allocations occur.
**
** ^SQLite automatically frees the memory allocated by
6010
6011
6012
6013
6014
6015
6016

6017
6018
6019
6020
6021
6022
6023
6329
6330
6331
6332
6333
6334
6335
6336
6337
6338
6339
6340
6341
6342
6343







+







** with a [database connection].
** A call to sqlite3_set_clientdata(D,N,P,X) causes the pointer P
** to be attached to [database connection] D using name N.  Subsequent
** calls to sqlite3_get_clientdata(D,N) will return a copy of pointer P
** or a NULL pointer if there were no prior calls to
** sqlite3_set_clientdata() with the same values of D and N.
** Names are compared using strcmp() and are thus case sensitive.
** It returns 0 on success and SQLITE_NOMEM on allocation failure.
**
** If P and X are both non-NULL, then the destructor X is invoked with
** argument P on the first of the following occurrences:
** <ul>
** <li> An out-of-memory error occurs during the call to
**      sqlite3_set_clientdata() which attempts to register pointer P.
** <li> A subsequent call to sqlite3_set_clientdata(D,N,P,X) is made
6031
6032
6033
6034
6035
6036
6037
6038
6039
6040
6041








6042
6043
6044
6045
6046
6047
6048
6049
6050

6051
6052
6053
6054
6055
6056
6057
6351
6352
6353
6354
6355
6356
6357




6358
6359
6360
6361
6362
6363
6364
6365
6366
6367
6368
6369
6370
6371
6372
6373

6374
6375
6376
6377
6378
6379
6380
6381







-
-
-
-
+
+
+
+
+
+
+
+








-
+







** SQLite does not do anything with client data other than invoke
** destructors on the client data at the appropriate time.  The intended
** use for client data is to provide a mechanism for wrapper libraries
** to store additional information about an SQLite database connection.
**
** There is no limit (other than available memory) on the number of different
** client data pointers (with different names) that can be attached to a
** single database connection.  However, the implementation is optimized
** for the case of having only one or two different client data names.
** Applications and wrapper libraries are discouraged from using more than
** one client data name each.
** single database connection.  However, the current implementation stores
** the content on a linked list.  Insert and retrieval performance will
** be proportional to the number of entries.  The design use case, and
** the use case for which the implementation is optimized, is
** that an application will store only small number of client data names,
** typically just one or two.  This interface is not intended to be a
** generalized key/value store for thousands or millions of keys.  It
** will work for that, but performance might be disappointing.
**
** There is no way to enumerate the client data pointers
** associated with a database connection.  The N parameter can be thought
** of as a secret key such that only code that knows the secret key is able
** to access the associated data.
**
** Security Warning:  These interfaces should not be exposed in scripting
** languages or in other circumstances where it might be possible for an
** an attacker to invoke them.  Any agent that can invoke these interfaces
** attacker to invoke them.  Any agent that can invoke these interfaces
** can probably also take control of the process.
**
** Database connection client data is only available for SQLite
** version 3.44.0 ([dateof:3.44.0]) and later.
**
** See also: [sqlite3_set_auxdata()] and [sqlite3_get_auxdata()].
*/
6142
6143
6144
6145
6146
6147
6148
6149

6150
6151
6152






6153
6154
6155
6156
6157
6158
6159
6160
6161
6162
6163
6164

6165
6166
6167
6168
6169
6170
6171
6466
6467
6468
6469
6470
6471
6472

6473
6474


6475
6476
6477
6478
6479
6480
6481
6482
6483
6484
6485
6486
6487
6488
6489
6490
6491

6492
6493
6494
6495
6496
6497
6498
6499







-
+

-
-
+
+
+
+
+
+











-
+







** of the application-defined function to be NULL.
**
** ^The sqlite3_result_text(), sqlite3_result_text16(),
** sqlite3_result_text16le(), and sqlite3_result_text16be() interfaces
** set the return value of the application-defined function to be
** a text string which is represented as UTF-8, UTF-16 native byte order,
** UTF-16 little endian, or UTF-16 big endian, respectively.
** ^The sqlite3_result_text64() interface sets the return value of an
** ^The sqlite3_result_text64(C,Z,N,D,E) interface sets the return value of an
** application-defined function to be a text string in an encoding
** specified by the fifth (and last) parameter, which must be one
** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE].
** specified the E parameter, which must be one
** of [SQLITE_UTF8], [SQLITE_UTF8_ZT], [SQLITE_UTF16], [SQLITE_UTF16BE],
** or [SQLITE_UTF16LE].  ^The special value [SQLITE_UTF8_ZT] means that
** the result text is both UTF-8 and zero-terminated.  In other words,
** SQLITE_UTF8_ZT means that the Z array holds at least N+1 byes and that
** the Z&#91;N&#93; is zero.
** ^SQLite takes the text result from the application from
** the 2nd parameter of the sqlite3_result_text* interfaces.
** ^If the 3rd parameter to any of the sqlite3_result_text* interfaces
** other than sqlite3_result_text64() is negative, then SQLite computes
** the string length itself by searching the 2nd parameter for the first
** zero character.
** ^If the 3rd parameter to the sqlite3_result_text* interfaces
** is non-negative, then as many bytes (not characters) of the text
** pointed to by the 2nd parameter are taken as the application-defined
** function result.  If the 3rd parameter is non-negative, then it
** must be the byte offset into the string where the NUL terminator would
** appear if the string where NUL terminated.  If any NUL characters occur
** appear if the string were NUL terminated.  If any NUL characters occur
** in the string at a byte offset that is less than the value of the 3rd
** parameter, then the resulting string will contain embedded NULs and the
** result of expressions operating on strings with embedded NULs is undefined.
** ^If the 4th parameter to the sqlite3_result_text* interfaces
** or sqlite3_result_blob is a non-NULL pointer, then SQLite calls that
** function as the destructor on the text or BLOB result when it has
** finished using that result.
6215
6216
6217
6218
6219
6220
6221
6222

6223
6224
6225
6226
6227
6228
6229
6230
6231
6232
6233
6234
6235
6236
6237
6238
6239

6240
6241
6242
6243
6244
6245
6246
6543
6544
6545
6546
6547
6548
6549

6550
6551
6552
6553
6554
6555
6556
6557
6558
6559
6560
6561
6562
6563
6564
6565
6566

6567
6568
6569
6570
6571
6572
6573
6574







-
+
















-
+







** [application-defined SQL function] using [sqlite3_value_pointer()].
** ^If the D parameter is not NULL, then it is a pointer to a destructor
** for the P parameter.  ^SQLite invokes D with P as its only argument
** when SQLite is finished with P.  The T parameter should be a static
** string and preferably a string literal. The sqlite3_result_pointer()
** routine is part of the [pointer passing interface] added for SQLite 3.20.0.
**
** If these routines are called from within the different thread
** If these routines are called from within a different thread
** than the one containing the application-defined function that received
** the [sqlite3_context] pointer, the results are undefined.
*/
SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*));
SQLITE_API void sqlite3_result_blob64(sqlite3_context*,const void*,
                           sqlite3_uint64,void(*)(void*));
SQLITE_API void sqlite3_result_double(sqlite3_context*, double);
SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int);
SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int);
SQLITE_API void sqlite3_result_error_toobig(sqlite3_context*);
SQLITE_API void sqlite3_result_error_nomem(sqlite3_context*);
SQLITE_API void sqlite3_result_error_code(sqlite3_context*, int);
SQLITE_API void sqlite3_result_int(sqlite3_context*, int);
SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64);
SQLITE_API void sqlite3_result_null(sqlite3_context*);
SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*));
SQLITE_API void sqlite3_result_text64(sqlite3_context*, const char*,sqlite3_uint64,
SQLITE_API void sqlite3_result_text64(sqlite3_context*, const char *z, sqlite3_uint64 n,
                           void(*)(void*), unsigned char encoding);
SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*));
SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*));
SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*));
SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*);
SQLITE_API void sqlite3_result_pointer(sqlite3_context*, void*,const char*,void(*)(void*));
SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n);
6621
6622
6623
6624
6625
6626
6627
6628

6629
6630
6631
6632
6633
6634
6635
6949
6950
6951
6952
6953
6954
6955

6956
6957
6958
6959
6960
6961
6962
6963







-
+







SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*);

/*
** CAPI3REF: Return The Schema Name For A Database Connection
** METHOD: sqlite3
**
** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name
** for the N-th database on database connection D, or a NULL pointer of N is
** for the N-th database on database connection D, or a NULL pointer if N is
** out of range.  An N value of 0 means the main database file.  An N of 1 is
** the "temp" schema.  Larger values of N correspond to various ATTACH-ed
** databases.
**
** Space to hold the string that is returned by sqlite3_db_name() is managed
** by SQLite itself.  The string might be deallocated by any operation that
** changes the schema, including [ATTACH] or [DETACH] or calls to
6716
6717
6718
6719
6720
6721
6722
6723

6724
6725
6726
6727
6728
6729
6730
6731
6732

6733
6734
6735
6736
6737
6738
6739
7044
7045
7046
7047
7048
7049
7050

7051
7052
7053
7054
7055
7056
7057
7058
7059

7060
7061
7062
7063
7064
7065
7066
7067







-
+








-
+







** <dd>The SQLITE_TXN_NONE state means that no transaction is currently
** pending.</dd>
**
** [[SQLITE_TXN_READ]] <dt>SQLITE_TXN_READ</dt>
** <dd>The SQLITE_TXN_READ state means that the database is currently
** in a read transaction.  Content has been read from the database file
** but nothing in the database file has changed.  The transaction state
** will advanced to SQLITE_TXN_WRITE if any changes occur and there are
** will be advanced to SQLITE_TXN_WRITE if any changes occur and there are
** no other conflicting concurrent write transactions.  The transaction
** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or
** [COMMIT].</dd>
**
** [[SQLITE_TXN_WRITE]] <dt>SQLITE_TXN_WRITE</dt>
** <dd>The SQLITE_TXN_WRITE state means that the database is currently
** in a write transaction.  Content has been written to the database file
** but has not yet committed.  The transaction state will change to
** to SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd>
** SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].</dd>
*/
#define SQLITE_TXN_NONE  0
#define SQLITE_TXN_READ  1
#define SQLITE_TXN_WRITE 2

/*
** CAPI3REF: Find the next prepared statement
6876
6877
6878
6879
6880
6881
6882


6883
6884
6885
6886
6887
6888
6889
7204
7205
7206
7207
7208
7209
7210
7211
7212
7213
7214
7215
7216
7217
7218
7219







+
+







** to be invoked whenever a row is updated, inserted or deleted in
** a [rowid table].
** ^Any callback set by a previous call to this function
** for the same database connection is overridden.
**
** ^The second argument is a pointer to the function to invoke when a
** row is updated, inserted or deleted in a rowid table.
** ^The update hook is disabled by invoking sqlite3_update_hook()
** with a NULL pointer as the second parameter.
** ^The first argument to the callback is a copy of the third argument
** to sqlite3_update_hook().
** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE],
** or [SQLITE_UPDATE], depending on the operation that caused the callback
** to be invoked.
** ^The third and fourth arguments to the callback contain pointers to the
** database and table name containing the affected row.
7004
7005
7006
7007
7008
7009
7010
7011

7012
7013
7014
7015
7016
7017
7018
7334
7335
7336
7337
7338
7339
7340

7341
7342
7343
7344
7345
7346
7347
7348







-
+







*/
SQLITE_API int sqlite3_db_release_memory(sqlite3*);

/*
** CAPI3REF: Impose A Limit On Heap Size
**
** These interfaces impose limits on the amount of heap memory that will be
** by all database connections within a single process.
** used by all database connections within a single process.
**
** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the
** soft limit on the amount of heap memory that may be allocated by SQLite.
** ^SQLite strives to keep heap memory utilization below the soft heap
** limit by reducing the number of pages held in the page cache
** as heap memory usages approaches the limit.
** ^The soft heap limit is "soft" because even though SQLite strives to stay
7062
7063
7064
7065
7066
7067
7068
7069

7070
7071
7072
7073
7074
7075
7076
7392
7393
7394
7395
7396
7397
7398

7399
7400
7401
7402
7403
7404
7405
7406







-
+







**      [sqlite3_config]([SQLITE_CONFIG_PCACHE2],...).
** <li> The page cache allocates from its own memory pool supplied
**      by [sqlite3_config]([SQLITE_CONFIG_PAGECACHE],...) rather than
**      from the heap.
** </ul>)^
**
** The circumstances under which SQLite will enforce the heap limits may
** changes in future releases of SQLite.
** change in future releases of SQLite.
*/
SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N);
SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N);

/*
** CAPI3REF: Deprecated Soft Heap Limit Interface
** DEPRECATED
7169
7170
7171
7172
7173
7174
7175
7176

7177
7178
7179
7180
7181
7182
7183
7184
7185
7186
7187




7188
7189
7190
7191
7192
7193
7194
7499
7500
7501
7502
7503
7504
7505

7506
7507
7508
7509
7510
7511
7512
7513




7514
7515
7516
7517
7518
7519
7520
7521
7522
7523
7524







-
+







-
-
-
-
+
+
+
+







** METHOD: sqlite3
**
** ^This interface loads an SQLite extension library from the named file.
**
** ^The sqlite3_load_extension() interface attempts to load an
** [SQLite extension] library contained in the file zFile.  If
** the file cannot be loaded directly, attempts are made to load
** with various operating-system specific extensions added.
** with various operating-system specific filename extensions added.
** So for example, if "samplelib" cannot be loaded, then names like
** "samplelib.so" or "samplelib.dylib" or "samplelib.dll" might
** be tried also.
**
** ^The entry point is zProc.
** ^(zProc may be 0, in which case SQLite will try to come up with an
** entry point name on its own.  It first tries "sqlite3_extension_init".
** If that does not work, it constructs a name "sqlite3_X_init" where the
** X is consists of the lower-case equivalent of all ASCII alphabetic
** characters in the filename from the last "/" to the first following
** "." and omitting any initial "lib".)^
** If that does not work, it tries names of the form "sqlite3_X_init"
** where X consists of the lower-case equivalent of all ASCII alphabetic
** characters or all ASCII alphanumeric characters in the filename from
** the last "/" to the first following "." and omitting any initial "lib".)^
** ^The sqlite3_load_extension() interface returns
** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong.
** ^If an error occurs and pzErrMsg is not 0, then the
** [sqlite3_load_extension()] interface shall attempt to
** fill *pzErrMsg with error message text stored in memory
** obtained from [sqlite3_malloc()]. The calling function
** should free this memory by calling [sqlite3_free()].
7249
7250
7251
7252
7253
7254
7255
7256

7257
7258
7259
7260
7261
7262
7263
7579
7580
7581
7582
7583
7584
7585

7586
7587
7588
7589
7590
7591
7592
7593







-
+







** each new [database connection] that is created.  The idea here is that
** xEntryPoint() is the entry point for a statically linked [SQLite extension]
** that is to be automatically loaded into all new database connections.
**
** ^(Even though the function prototype shows that xEntryPoint() takes
** no arguments and returns void, SQLite invokes xEntryPoint() with three
** arguments and expects an integer result as if the signature of the
** entry point where as follows:
** entry point were as follows:
**
** <blockquote><pre>
** &nbsp;  int xEntryPoint(
** &nbsp;    sqlite3 *db,
** &nbsp;    const char **pzErrMsg,
** &nbsp;    const struct sqlite3_api_routines *pThunk
** &nbsp;  );
7413
7414
7415
7416
7417
7418
7419
7420

7421
7422
7423
7424
7425
7426
7427
7743
7744
7745
7746
7747
7748
7749

7750
7751
7752
7753
7754
7755
7756
7757







-
+







** about what parameters to pass to xFilter.  ^If argvIndex>0 then
** the right-hand side of the corresponding aConstraint[] is evaluated
** and becomes the argvIndex-th entry in argv.  ^(If aConstraintUsage[].omit
** is true, then the constraint is assumed to be fully handled by the
** virtual table and might not be checked again by the byte code.)^ ^(The
** aConstraintUsage[].omit flag is an optimization hint. When the omit flag
** is left in its default setting of false, the constraint will always be
** checked separately in byte code.  If the omit flag is change to true, then
** checked separately in byte code.  If the omit flag is changed to true, then
** the constraint may or may not be checked in byte code.  In other words,
** when the omit flag is true there is no guarantee that the constraint will
** not be checked again using byte code.)^
**
** ^The idxNum and idxStr values are recorded and passed into the
** [xFilter] method.
** ^[sqlite3_free()] is used to free idxStr if and only if
7439
7440
7441
7442
7443
7444
7445
7446

7447
7448
7449
7450
7451
7452
7453
7769
7770
7771
7772
7773
7774
7775

7776
7777
7778
7779
7780
7781
7782
7783







-
+







**
** ^The estimatedRows value is an estimate of the number of rows that
** will be returned by the strategy.
**
** The xBestIndex method may optionally populate the idxFlags field with a
** mask of SQLITE_INDEX_SCAN_* flags. One such flag is
** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN]
** output to show the idxNum has hex instead of as decimal.  Another flag is
** output to show the idxNum as hex instead of as decimal.  Another flag is
** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will
** return at most one row.
**
** Additionally, if xBestIndex sets the SQLITE_INDEX_SCAN_UNIQUE flag, then
** SQLite also assumes that if a call to the xUpdate() method is made as
** part of the same statement to delete or update a virtual table row and the
** implementation returns SQLITE_CONSTRAINT, then there is no need to rollback
7580
7581
7582
7583
7584
7585
7586
7587

7588
7589
7590
7591
7592
7593
7594
7910
7911
7912
7913
7914
7915
7916

7917
7918
7919
7920
7921
7922
7923
7924







-
+







**
** ^The module name is registered on the [database connection] specified
** by the first parameter.  ^The name of the module is given by the
** second parameter.  ^The third parameter is a pointer to
** the implementation of the [virtual table module].   ^The fourth
** parameter is an arbitrary client data pointer that is passed through
** into the [xCreate] and [xConnect] methods of the virtual table module
** when a new virtual table is be being created or reinitialized.
** when a new virtual table is being created or reinitialized.
**
** ^The sqlite3_create_module_v2() interface has a fifth parameter which
** is a pointer to a destructor for the pClientData.  ^SQLite will
** invoke the destructor function (if it is not NULL) when SQLite
** no longer needs the pClientData pointer.  ^The destructor will also
** be invoked if the call to sqlite3_create_module_v2() fails.
** ^The sqlite3_create_module()
7745
7746
7747
7748
7749
7750
7751
7752

7753
7754
7755
7756
7757
7758
7759
8075
8076
8077
8078
8079
8080
8081

8082
8083
8084
8085
8086
8087
8088
8089







-
+







** and write access. ^If the flags parameter is zero, the BLOB is opened for
** read-only access.
**
** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored
** in *ppBlob. Otherwise an [error code] is returned and, unless the error
** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
** the API is not misused, it is always safe to call [sqlite3_blob_close()]
** on *ppBlob after this function it returns.
** on *ppBlob after this function returns.
**
** This function fails with SQLITE_ERROR if any of the following are true:
** <ul>
**   <li> ^(Database zDb does not exist)^,
**   <li> ^(Table zTable does not exist within database zDb)^,
**   <li> ^(Table zTable is a WITHOUT ROWID table)^,
**   <li> ^(Column zColumn does not exist)^,
7865
7866
7867
7868
7869
7870
7871
7872

7873
7874
7875
7876
7877
7878
7879
8195
8196
8197
8198
8199
8200
8201

8202
8203
8204
8205
8206
8207
8208
8209







-
+








/*
** CAPI3REF: Return The Size Of An Open BLOB
** METHOD: sqlite3_blob
**
** ^Returns the size in bytes of the BLOB accessible via the
** successfully opened [BLOB handle] in its only argument.  ^The
** incremental blob I/O routines can only read or overwriting existing
** incremental blob I/O routines can only read or overwrite existing
** blob content; they cannot change the size of a blob.
**
** This routine only works on a [BLOB handle] which has been created
** by a prior successful call to [sqlite3_blob_open()] and which has not
** been closed by [sqlite3_blob_close()].  Passing any other pointer in
** to this routine results in undefined and probably undesirable behavior.
*/
8015
8016
8017
8018
8019
8020
8021
8022

8023
8024
8025
8026
8027
8028
8029
8345
8346
8347
8348
8349
8350
8351

8352
8353
8354
8355
8356
8357
8358
8359







-
+







** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function
** before calling sqlite3_initialize() or any other public sqlite3_
** function that calls sqlite3_initialize().
**
** ^The sqlite3_mutex_alloc() routine allocates a new
** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc()
** routine returns NULL if it is unable to allocate the requested
** mutex.  The argument to sqlite3_mutex_alloc() must one of these
** mutex.  The argument to sqlite3_mutex_alloc() must be one of these
** integer constants:
**
** <ul>
** <li>  SQLITE_MUTEX_FAST
** <li>  SQLITE_MUTEX_RECURSIVE
** <li>  SQLITE_MUTEX_STATIC_MAIN
** <li>  SQLITE_MUTEX_STATIC_MEM
8248
8249
8250
8251
8252
8253
8254
8255

8256
8257
8258
8259
8260
8261
8262
8578
8579
8580
8581
8582
8583
8584

8585
8586
8587
8588
8589
8590
8591
8592







-
+







#define SQLITE_MUTEX_STATIC_MASTER    2


/*
** CAPI3REF: Retrieve the mutex for a database connection
** METHOD: sqlite3
**
** ^This interface returns a pointer the [sqlite3_mutex] object that
** ^This interface returns a pointer to the [sqlite3_mutex] object that
** serializes access to the [database connection] given in the argument
** when the [threading mode] is Serialized.
** ^If the [threading mode] is Single-thread or Multi-thread then this
** routine returns a NULL pointer.
*/
SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*);

8371
8372
8373
8374
8375
8376
8377
8378

8379
8380
8381
8382
8383
8384
8385
8701
8702
8703
8704
8705
8706
8707

8708
8709
8710
8711
8712
8713
8714
8715







-
+







#define SQLITE_TESTCTRL_USELONGDOUBLE           34  /* NOT USED */
#define SQLITE_TESTCTRL_LAST                    34  /* Largest TESTCTRL */

/*
** CAPI3REF: SQL Keyword Checking
**
** These routines provide access to the set of SQL language keywords
** recognized by SQLite.  Applications can uses these routines to determine
** recognized by SQLite.  Applications can use these routines to determine
** whether or not a specific identifier needs to be escaped (for example,
** by enclosing in double-quotes) so as not to confuse the parser.
**
** The sqlite3_keyword_count() interface returns the number of distinct
** keywords understood by SQLite.
**
** The sqlite3_keyword_name(N,Z,L) interface finds the 0-based N-th keyword and
8473
8474
8475
8476
8477
8478
8479
8480

8481




8482
8483

8484
8485
8486
8487
8488
8489
8490


8491
8492
8493
8494
8495
8496
8497
8803
8804
8805
8806
8807
8808
8809

8810
8811
8812
8813
8814
8815
8816
8817
8818
8819
8820
8821
8822
8823


8824
8825
8826
8827
8828
8829
8830
8831
8832







-
+

+
+
+
+


+





-
-
+
+







**
** ^The [sqlite3_str_finish(X)] interface destroys the sqlite3_str object X
** and returns a pointer to a memory buffer obtained from [sqlite3_malloc64()]
** that contains the constructed string.  The calling application should
** pass the returned value to [sqlite3_free()] to avoid a memory leak.
** ^The [sqlite3_str_finish(X)] interface may return a NULL pointer if any
** errors were encountered during construction of the string.  ^The
** [sqlite3_str_finish(X)] interface will also return a NULL pointer if the
** [sqlite3_str_finish(X)] interface might also return a NULL pointer if the
** string in [sqlite3_str] object X is zero bytes long.
**
** ^The [sqlite3_str_free(X)] interface destroys both the sqlite3_str object
** X and the string content it contains.  Calling sqlite3_str_free(X) is
** the equivalent of calling [sqlite3_free](sqlite3_str_finish(X)).
*/
SQLITE_API char *sqlite3_str_finish(sqlite3_str*);
SQLITE_API void sqlite3_str_free(sqlite3_str*);

/*
** CAPI3REF: Add Content To A Dynamic String
** METHOD: sqlite3_str
**
** These interfaces add content to an sqlite3_str object previously obtained
** from [sqlite3_str_new()].
** These interfaces add or remove content to an sqlite3_str object
** previously obtained from [sqlite3_str_new()].
**
** ^The [sqlite3_str_appendf(X,F,...)] and
** [sqlite3_str_vappendf(X,F,V)] interfaces uses the [built-in printf]
** functionality of SQLite to append formatted text onto the end of
** [sqlite3_str] object X.
**
** ^The [sqlite3_str_append(X,S,N)] method appends exactly N bytes from string S
8505
8506
8507
8508
8509
8510
8511




8512
8513
8514
8515
8516
8517
8518
8519
8520
8521
8522

8523
8524
8525
8526
8527
8528
8529
8840
8841
8842
8843
8844
8845
8846
8847
8848
8849
8850
8851
8852
8853
8854
8855
8856
8857
8858
8859
8860
8861
8862
8863
8864
8865
8866
8867
8868
8869







+
+
+
+











+







**
** ^The [sqlite3_str_appendchar(X,N,C)] method appends N copies of the
** single-byte character C onto the end of [sqlite3_str] object X.
** ^This method can be used, for example, to add whitespace indentation.
**
** ^The [sqlite3_str_reset(X)] method resets the string under construction
** inside [sqlite3_str] object X back to zero bytes in length.
**
** ^The [sqlite3_str_truncate(X,N)] method changes the length of the string
** under construction to be N bytes are less.  This routine is a no-op if
** N is negative or if the string is already N bytes or smaller in size.
**
** These methods do not return a result code.  ^If an error occurs, that fact
** is recorded in the [sqlite3_str] object and can be recovered by a
** subsequent call to [sqlite3_str_errcode(X)].
*/
SQLITE_API void sqlite3_str_appendf(sqlite3_str*, const char *zFormat, ...);
SQLITE_API void sqlite3_str_vappendf(sqlite3_str*, const char *zFormat, va_list);
SQLITE_API void sqlite3_str_append(sqlite3_str*, const char *zIn, int N);
SQLITE_API void sqlite3_str_appendall(sqlite3_str*, const char *zIn);
SQLITE_API void sqlite3_str_appendchar(sqlite3_str*, int N, char C);
SQLITE_API void sqlite3_str_reset(sqlite3_str*);
SQLITE_API void sqlite3_str_truncate(sqlite3_str*,int N);

/*
** CAPI3REF: Status Of A Dynamic String
** METHOD: sqlite3_str
**
** These interfaces return the current status of an [sqlite3_str] object.
**
8539
8540
8541
8542
8543
8544
8545
8546

8547
8548
8549
8550
8551
8552
8553
8879
8880
8881
8882
8883
8884
8885

8886
8887
8888
8889
8890
8891
8892
8893







-
+







** ^The length returned by [sqlite3_str_length(X)] does not include the
** zero-termination byte.
**
** ^The [sqlite3_str_value(X)] method returns a pointer to the current
** content of the dynamic string under construction in X.  The value
** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X
** and might be freed or altered by any subsequent method on the same
** [sqlite3_str] object.  Applications must not used the pointer returned
** [sqlite3_str] object.  Applications must not use the pointer returned by
** [sqlite3_str_value(X)] after any subsequent method call on the same
** object.  ^Applications may change the content of the string returned
** by [sqlite3_str_value(X)] as long as they do not write into any bytes
** outside the range of 0 to [sqlite3_str_length(X)] and do not read or
** write any byte after any subsequent sqlite3_str method call.
*/
SQLITE_API int sqlite3_str_errcode(sqlite3_str*);
8625
8626
8627
8628
8629
8630
8631
8632

8633
8634
8635
8636
8637
8638
8639
8965
8966
8967
8968
8969
8970
8971

8972
8973
8974
8975
8976
8977
8978
8979







-
+







**
** [[SQLITE_STATUS_PAGECACHE_OVERFLOW]]
** ^(<dt>SQLITE_STATUS_PAGECACHE_OVERFLOW</dt>
** <dd>This parameter returns the number of bytes of page cache
** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE]
** buffer and where forced to overflow to [sqlite3_malloc()].  The
** returned value includes allocations that overflowed because they
** where too large (they were larger than the "sz" parameter to
** were too large (they were larger than the "sz" parameter to
** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because
** no space was left in the page cache.</dd>)^
**
** [[SQLITE_STATUS_PAGECACHE_SIZE]] ^(<dt>SQLITE_STATUS_PAGECACHE_SIZE</dt>
** <dd>This parameter records the largest memory allocation request
** handed to the [pagecache memory allocator].  Only the value returned in the
** *pHighwater parameter to [sqlite3_status()] is of interest.
8683
8684
8685
8686
8687
8688
8689








8690
8691
8692
8693

8694
8695
8696
8697
8698
8699
8700
9023
9024
9025
9026
9027
9028
9029
9030
9031
9032
9033
9034
9035
9036
9037
9038
9039
9040
9041
9042
9043
9044
9045
9046
9047
9048
9049







+
+
+
+
+
+
+
+




+







** ^The current value of the requested parameter is written into *pCur
** and the highest instantaneous value is written into *pHiwtr.  ^If
** the resetFlg is true, then the highest instantaneous value is
** reset back down to the current value.
**
** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a
** non-zero [error code] on failure.
**
** ^The sqlite3_db_status64(D,O,C,H,R) routine works exactly the same
** way as sqlite3_db_status(D,O,C,H,R) routine except that the C and H
** parameters are pointer to 64-bit integers (type: sqlite3_int64) instead
** of pointers to 32-bit integers, which allows larger status values
** to be returned.  If a status value exceeds 2,147,483,647 then
** sqlite3_db_status() will truncate the value whereas sqlite3_db_status64()
** will return the full value.
**
** See also: [sqlite3_status()] and [sqlite3_stmt_status()].
*/
SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg);
SQLITE_API int sqlite3_db_status64(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int);

/*
** CAPI3REF: Status Parameters for database connections
** KEYWORDS: {SQLITE_DBSTATUS options}
**
** These constants are the available integer "verbs" that can be passed as
** the second argument to the [sqlite3_db_status()] interface.
8709
8710
8711
8712
8713
8714
8715
8716

8717
8718
8719
8720

8721
8722
8723
8724

8725
8726
8727
8728

8729
8730
8731
8732

8733
8734
8735
8736
8737

8738
8739
8740
8741
8742
8743
8744
8745
8746

8747
8748
8749

8750
8751
8752
8753
8754
8755
8756
8757
8758

8759
8760
8761
8762
8763
8764
8765
9058
9059
9060
9061
9062
9063
9064

9065
9066
9067
9068

9069
9070
9071
9072

9073
9074
9075
9076

9077
9078
9079
9080

9081
9082
9083
9084
9085
9086
9087
9088
9089
9090
9091
9092
9093
9094
9095

9096
9097
9098

9099
9100
9101
9102
9103
9104
9105
9106
9107
9108
9109
9110
9111
9112
9113
9114
9115
9116







-
+



-
+



-
+



-
+



-
+





+








-
+


-
+









+







** [[SQLITE_DBSTATUS_LOOKASIDE_USED]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_USED</dt>
** <dd>This parameter returns the number of lookaside memory slots currently
** checked out.</dd>)^
**
** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_HIT</dt>
** <dd>This parameter returns the number of malloc attempts that were
** satisfied using lookaside memory. Only the high-water value is meaningful;
** the current value is always zero.)^
** the current value is always zero.</dd>)^
**
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]]
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE</dt>
** <dd>This parameter returns the number malloc attempts that might have
** <dd>This parameter returns the number of malloc attempts that might have
** been satisfied using lookaside memory but failed due to the amount of
** memory requested being larger than the lookaside slot size.
** Only the high-water value is meaningful;
** the current value is always zero.)^
** the current value is always zero.</dd>)^
**
** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]]
** ^(<dt>SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL</dt>
** <dd>This parameter returns the number malloc attempts that might have
** <dd>This parameter returns the number of malloc attempts that might have
** been satisfied using lookaside memory but failed due to all lookaside
** memory already being in use.
** Only the high-water value is meaningful;
** the current value is always zero.)^
** the current value is always zero.</dd>)^
**
** [[SQLITE_DBSTATUS_CACHE_USED]] ^(<dt>SQLITE_DBSTATUS_CACHE_USED</dt>
** <dd>This parameter returns the approximate number of bytes of heap
** memory used by all pager caches associated with the database connection.)^
** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0.
** </dd>
**
** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]]
** ^(<dt>SQLITE_DBSTATUS_CACHE_USED_SHARED</dt>
** <dd>This parameter is similar to DBSTATUS_CACHE_USED, except that if a
** pager cache is shared between two or more connections the bytes of heap
** memory used by that pager cache is divided evenly between the attached
** connections.)^  In other words, if none of the pager caches associated
** with the database connection are shared, this request returns the same
** value as DBSTATUS_CACHE_USED. Or, if one or more or the pager caches are
** value as DBSTATUS_CACHE_USED. Or, if one or more of the pager caches are
** shared, the value returned by this call will be smaller than that returned
** by DBSTATUS_CACHE_USED. ^The highwater mark associated with
** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.
** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.</dd>
**
** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(<dt>SQLITE_DBSTATUS_SCHEMA_USED</dt>
** <dd>This parameter returns the approximate number of bytes of heap
** memory used to store the schema for all databases associated
** with the connection - main, temp, and any [ATTACH]-ed databases.)^
** ^The full amount of memory used by the schemas is reported, even if the
** schema memory is shared with other database connections due to
** [shared cache mode] being enabled.
** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0.
** </dd>
**
** [[SQLITE_DBSTATUS_STMT_USED]] ^(<dt>SQLITE_DBSTATUS_STMT_USED</dt>
** <dd>This parameter returns the approximate number of bytes of heap
** and lookaside memory used by all prepared statements associated with
** the database connection.)^
** ^The highwater mark associated with SQLITE_DBSTATUS_STMT_USED is always 0.
** </dd>
8781
8782
8783
8784
8785
8786
8787




8788
8789
8790
8791
8792
8793
8794
8795

8796
8797
8798
8799
8800
8801
8802












8803
8804
8805
8806
8807
8808
8809
8810
8811
8812
8813
8814
8815
8816
8817
8818

8819

8820
8821
8822
8823
8824
8825
8826
9132
9133
9134
9135
9136
9137
9138
9139
9140
9141
9142
9143
9144
9145
9146
9147
9148
9149

9150
9151
9152
9153
9154
9155
9156
9157
9158
9159
9160
9161
9162
9163
9164
9165
9166
9167
9168
9169
9170
9171
9172
9173
9174
9175
9176
9177
9178
9179
9180
9181
9182
9183
9184
9185
9186

9187
9188
9189
9190
9191
9192
9193
9194







+
+
+
+







-
+







+
+
+
+
+
+
+
+
+
+
+
+
















+
-
+







** been written to disk. Specifically, the number of pages written to the
** wal file in wal mode databases, or the number of pages written to the
** database file in rollback mode databases. Any pages written as part of
** transaction rollback or database recovery operations are not included.
** If an IO or other error occurs while writing a page to disk, the effect
** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The
** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0.
** <p>
** ^(There is overlap between the quantities measured by this parameter
** (SQLITE_DBSTATUS_CACHE_WRITE) and SQLITE_DBSTATUS_TEMPBUF_SPILL.
** Resetting one will reduce the other.)^
** </dd>
**
** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt>
** <dd>This parameter returns the number of dirty cache entries that have
** been written to disk in the middle of a transaction due to the page
** cache overflowing. Transactions are more efficient if they are written
** to disk all at once. When pages spill mid-transaction, that introduces
** additional overhead. This parameter can be used help identify
** additional overhead. This parameter can be used to help identify
** inefficiencies that can be resolved by increasing the cache size.
** </dd>
**
** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(<dt>SQLITE_DBSTATUS_DEFERRED_FKS</dt>
** <dd>This parameter returns zero for the current value if and only if
** all foreign key constraints (deferred or immediate) have been
** resolved.)^  ^The highwater mark is always 0.
**
** [[SQLITE_DBSTATUS_TEMPBUF_SPILL] ^(<dt>SQLITE_DBSTATUS_TEMPBUF_SPILL</dt>
** <dd>^(This parameter returns the number of bytes written to temporary
** files on disk that could have been kept in memory had sufficient memory
** been available.  This value includes writes to intermediate tables that
** are part of complex queries, external sorts that spill to disk, and
** writes to TEMP tables.)^
** ^The highwater mark is always 0.
** <p>
** ^(There is overlap between the quantities measured by this parameter
** (SQLITE_DBSTATUS_TEMPBUF_SPILL) and SQLITE_DBSTATUS_CACHE_WRITE.
** Resetting one will reduce the other.)^
** </dd>
** </dl>
*/
#define SQLITE_DBSTATUS_LOOKASIDE_USED       0
#define SQLITE_DBSTATUS_CACHE_USED           1
#define SQLITE_DBSTATUS_SCHEMA_USED          2
#define SQLITE_DBSTATUS_STMT_USED            3
#define SQLITE_DBSTATUS_LOOKASIDE_HIT        4
#define SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE  5
#define SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL  6
#define SQLITE_DBSTATUS_CACHE_HIT            7
#define SQLITE_DBSTATUS_CACHE_MISS           8
#define SQLITE_DBSTATUS_CACHE_WRITE          9
#define SQLITE_DBSTATUS_DEFERRED_FKS        10
#define SQLITE_DBSTATUS_CACHE_USED_SHARED   11
#define SQLITE_DBSTATUS_CACHE_SPILL         12
#define SQLITE_DBSTATUS_TEMPBUF_SPILL       13
#define SQLITE_DBSTATUS_MAX                 12   /* Largest defined DBSTATUS */
#define SQLITE_DBSTATUS_MAX                 13   /* Largest defined DBSTATUS */


/*
** CAPI3REF: Prepared Statement Status
** METHOD: sqlite3_stmt
**
** ^(Each prepared statement maintains various
8859
8860
8861
8862
8863
8864
8865
8866

8867
8868
8869
8870
8871
8872

8873
8874
8875
8876
8877
8878
8879
8880
8881

8882
8883
8884
8885
8886

8887
8888
8889
8890
8891
8892
8893

8894
8895
8896
8897
8898
8899
8900
8901
8902
8903

8904
8905
8906
8907
8908
8909
8910
9227
9228
9229
9230
9231
9232
9233

9234
9235
9236
9237
9238
9239

9240
9241
9242
9243
9244
9245
9246
9247
9248

9249
9250
9251
9252
9253

9254
9255
9256
9257
9258
9259
9260

9261
9262
9263
9264
9265
9266
9267
9268
9269
9270

9271
9272
9273
9274
9275
9276
9277
9278







-
+





-
+








-
+




-
+






-
+









-
+







** a table as part of a full table scan.  Large numbers for this counter
** may indicate opportunities for performance improvement through
** careful use of indices.</dd>
**
** [[SQLITE_STMTSTATUS_SORT]] <dt>SQLITE_STMTSTATUS_SORT</dt>
** <dd>^This is the number of sort operations that have occurred.
** A non-zero value in this counter may indicate an opportunity to
** improvement performance through careful use of indices.</dd>
** improve performance through careful use of indices.</dd>
**
** [[SQLITE_STMTSTATUS_AUTOINDEX]] <dt>SQLITE_STMTSTATUS_AUTOINDEX</dt>
** <dd>^This is the number of rows inserted into transient indices that
** were created automatically in order to help joins run faster.
** A non-zero value in this counter may indicate an opportunity to
** improvement performance by adding permanent indices that do not
** improve performance by adding permanent indices that do not
** need to be reinitialized each time the statement is run.</dd>
**
** [[SQLITE_STMTSTATUS_VM_STEP]] <dt>SQLITE_STMTSTATUS_VM_STEP</dt>
** <dd>^This is the number of virtual machine operations executed
** by the prepared statement if that number is less than or equal
** to 2147483647.  The number of virtual machine operations can be
** used as a proxy for the total work done by the prepared statement.
** If the number of virtual machine operations exceeds 2147483647
** then the value returned by this statement status code is undefined.
** then the value returned by this statement status code is undefined.</dd>
**
** [[SQLITE_STMTSTATUS_REPREPARE]] <dt>SQLITE_STMTSTATUS_REPREPARE</dt>
** <dd>^This is the number of times that the prepare statement has been
** automatically regenerated due to schema changes or changes to
** [bound parameters] that might affect the query plan.
** [bound parameters] that might affect the query plan.</dd>
**
** [[SQLITE_STMTSTATUS_RUN]] <dt>SQLITE_STMTSTATUS_RUN</dt>
** <dd>^This is the number of times that the prepared statement has
** been run.  A single "run" for the purposes of this counter is one
** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()].
** The counter is incremented on the first [sqlite3_step()] call of each
** cycle.
** cycle.</dd>
**
** [[SQLITE_STMTSTATUS_FILTER_MISS]]
** [[SQLITE_STMTSTATUS_FILTER HIT]]
** <dt>SQLITE_STMTSTATUS_FILTER_HIT<br>
** SQLITE_STMTSTATUS_FILTER_MISS</dt>
** <dd>^SQLITE_STMTSTATUS_FILTER_HIT is the number of times that a join
** step was bypassed because a Bloom filter returned not-found.  The
** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of
** times that the Bloom filter returned a find, and thus the join step
** had to be processed as normal.
** had to be processed as normal.</dd>
**
** [[SQLITE_STMTSTATUS_MEMUSED]] <dt>SQLITE_STMTSTATUS_MEMUSED</dt>
** <dd>^This is the approximate number of bytes of heap memory
** used to store the prepared statement.  ^This value is not actually
** a counter, and so the resetFlg parameter to sqlite3_stmt_status()
** is ignored when the opcode is SQLITE_STMTSTATUS_MEMUSED.
** </dd>
9001
9002
9003
9004
9005
9006
9007
9008

9009
9010

9011
9012
9013
9014
9015
9016
9017
9018

9019
9020
9021
9022
9023

9024
9025
9026
9027
9028

9029
9030
9031
9032
9033
9034
9035
9369
9370
9371
9372
9373
9374
9375

9376
9377

9378
9379
9380
9381
9382
9383
9384
9385

9386
9387
9388
9389
9390

9391
9392
9393
9394
9395

9396
9397
9398
9399
9400
9401
9402
9403







-
+

-
+







-
+




-
+




-
+







** call to xShutdown().
**
** [[the xCreate() page cache methods]]
** ^SQLite invokes the xCreate() method to construct a new cache instance.
** SQLite will typically create one cache instance for each open database file,
** though this is not guaranteed. ^The
** first parameter, szPage, is the size in bytes of the pages that must
** be allocated by the cache.  ^szPage will always a power of two.  ^The
** be allocated by the cache.  ^szPage will always be a power of two.  ^The
** second parameter szExtra is a number of bytes of extra storage
** associated with each page cache entry.  ^The szExtra parameter will
** associated with each page cache entry.  ^The szExtra parameter will be
** a number less than 250.  SQLite will use the
** extra szExtra bytes on each page to store metadata about the underlying
** database page on disk.  The value passed into szExtra depends
** on the SQLite version, the target platform, and how SQLite was compiled.
** ^The third argument to xCreate(), bPurgeable, is true if the cache being
** created will be used to cache database pages of a file stored on disk, or
** false if it is used for an in-memory database. The cache implementation
** does not have to do anything special based with the value of bPurgeable;
** does not have to do anything special based upon the value of bPurgeable;
** it is purely advisory.  ^On a cache where bPurgeable is false, SQLite will
** never invoke xUnpin() except to deliberately delete a page.
** ^In other words, calls to xUnpin() on a cache with bPurgeable set to
** false will always have the "discard" flag set to true.
** ^Hence, a cache created with bPurgeable false will
** ^Hence, a cache created with bPurgeable set to false will
** never contain any unpinned pages.
**
** [[the xCachesize() page cache method]]
** ^(The xCachesize() method may be called at any time by SQLite to set the
** suggested maximum cache-size (number of pages stored by) the cache
** suggested maximum cache-size (number of pages stored) for the cache
** instance passed as the first argument. This is the value configured using
** the SQLite "[PRAGMA cache_size]" command.)^  As with the bPurgeable
** parameter, the implementation is not required to do anything with this
** value; it is advisory only.
**
** [[the xPagecount() page cache methods]]
** The xPagecount() method must return the number of pages currently
9048
9049
9050
9051
9052
9053
9054
9055

9056
9057
9058
9059
9060

9061
9062
9063
9064
9065
9066
9067
9068
9069
9070
9071
9072
9073
9074
9075
9076
9077

9078
9079
9080
9081
9082
9083
9084
9085
9086
9087
9088
9089
9090
9091
9092
9093
9094
9095

9096
9097
9098
9099
9100
9101
9102
9416
9417
9418
9419
9420
9421
9422

9423
9424
9425
9426
9427

9428
9429
9430
9431
9432
9433
9434
9435
9436
9437
9438
9439
9440
9441
9442
9443
9444

9445
9446
9447
9448
9449
9450
9451
9452
9453
9454
9455
9456
9457
9458
9459
9460
9461
9462

9463
9464
9465
9466
9467
9468
9469
9470







-
+




-
+
















-
+

















-
+







** is 1.  After it has been retrieved using xFetch, the page is considered
** to be "pinned".
**
** If the requested page is already in the page cache, then the page cache
** implementation must return a pointer to the page buffer with its content
** intact.  If the requested page is not already in the cache, then the
** cache implementation should use the value of the createFlag
** parameter to help it determined what action to take:
** parameter to help it determine what action to take:
**
** <table border=1 width=85% align=center>
** <tr><th> createFlag <th> Behavior when page is not already in cache
** <tr><td> 0 <td> Do not allocate a new page.  Return NULL.
** <tr><td> 1 <td> Allocate a new page if it easy and convenient to do so.
** <tr><td> 1 <td> Allocate a new page if it is easy and convenient to do so.
**                 Otherwise return NULL.
** <tr><td> 2 <td> Make every effort to allocate a new page.  Only return
**                 NULL if allocating a new page is effectively impossible.
** </table>
**
** ^(SQLite will normally invoke xFetch() with a createFlag of 0 or 1.  SQLite
** will only use a createFlag of 2 after a prior call with a createFlag of 1
** failed.)^  In between the xFetch() calls, SQLite may
** attempt to unpin one or more cache pages by spilling the content of
** pinned pages to disk and synching the operating system disk cache.
**
** [[the xUnpin() page cache method]]
** ^xUnpin() is called by SQLite with a pointer to a currently pinned page
** as its second argument.  If the third parameter, discard, is non-zero,
** then the page must be evicted from the cache.
** ^If the discard parameter is
** zero, then the page may be discarded or retained at the discretion of
** zero, then the page may be discarded or retained at the discretion of the
** page cache implementation. ^The page cache implementation
** may choose to evict unpinned pages at any time.
**
** The cache must not perform any reference counting. A single
** call to xUnpin() unpins the page regardless of the number of prior calls
** to xFetch().
**
** [[the xRekey() page cache methods]]
** The xRekey() method is used to change the key value associated with the
** page passed as the second argument. If the cache
** previously contains an entry associated with newKey, it must be
** discarded. ^Any prior cache entry associated with newKey is guaranteed not
** to be pinned.
**
** When SQLite calls the xTruncate() method, the cache must discard all
** existing cache entries with page numbers (keys) greater than or equal
** to the value of the iLimit parameter passed to xTruncate(). If any
** of these pages are pinned, they are implicitly unpinned, meaning that
** of these pages are pinned, they become implicitly unpinned, meaning that
** they can be safely discarded.
**
** [[the xDestroy() page cache method]]
** ^The xDestroy() method is used to delete a cache allocated by xCreate().
** All resources associated with the specified cache should be freed. ^After
** calling the xDestroy() method, SQLite considers the [sqlite3_pcache*]
** handle invalid, and will not use it with any other sqlite3_pcache_methods2
9268
9269
9270
9271
9272
9273
9274
9275

9276
9277
9278
9279
9280
9281
9282
9283
9284
9285
9286
9287
9288
9289
9290
9291
9292

9293
9294
9295
9296
9297
9298
9299
9636
9637
9638
9639
9640
9641
9642

9643
9644
9645
9646
9647
9648
9649
9650
9651
9652
9653
9654
9655
9656
9657
9658
9659

9660
9661
9662
9663
9664
9665
9666
9667







-
+
















-
+







** lasts for the duration of the sqlite3_backup_step() call.
** ^Because the source database is not locked between calls to
** sqlite3_backup_step(), the source database may be modified mid-way
** through the backup process.  ^If the source database is modified by an
** external process or via a database connection other than the one being
** used by the backup operation, then the backup will be automatically
** restarted by the next call to sqlite3_backup_step(). ^If the source
** database is modified by the using the same database connection as is used
** database is modified by using the same database connection as is used
** by the backup operation, then the backup database is automatically
** updated at the same time.
**
** [[sqlite3_backup_finish()]] <b>sqlite3_backup_finish()</b>
**
** When sqlite3_backup_step() has returned [SQLITE_DONE], or when the
** application wishes to abandon the backup operation, the application
** should destroy the [sqlite3_backup] by passing it to sqlite3_backup_finish().
** ^The sqlite3_backup_finish() interfaces releases all
** resources associated with the [sqlite3_backup] object.
** ^If sqlite3_backup_step() has not yet returned [SQLITE_DONE], then any
** active write-transaction on the destination database is rolled back.
** The [sqlite3_backup] object is invalid
** and may not be used following a call to sqlite3_backup_finish().
**
** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no
** sqlite3_backup_step() errors occurred, regardless or whether or not
** sqlite3_backup_step() errors occurred, regardless of whether or not
** sqlite3_backup_step() completed.
** ^If an out-of-memory condition or IO error occurred during any prior
** sqlite3_backup_step() call on the same [sqlite3_backup] object, then
** sqlite3_backup_finish() returns the corresponding [error code].
**
** ^A return of [SQLITE_BUSY] or [SQLITE_LOCKED] from sqlite3_backup_step()
** is not a permanent error and does not affect the return value of
9387
9388
9389
9390
9391
9392
9393
9394

9395
9396
9397
9398
9399
9400
9401
9402
9403
9404
9405
9406
9407
9408
9409
9410
9411
9412
9413
9414

9415
9416
9417
9418
9419
9420
9421
9755
9756
9757
9758
9759
9760
9761

9762
9763
9764
9765
9766
9767
9768
9769
9770
9771
9772
9773
9774
9775
9776
9777
9778
9779
9780
9781

9782
9783
9784
9785
9786
9787
9788
9789







-
+



















-
+







** ^When a connection (known as the blocked connection) fails to obtain a
** shared-cache lock and SQLITE_LOCKED is returned to the caller, the
** identity of the database connection (the blocking connection) that
** has locked the required resource is stored internally. ^After an
** application receives an SQLITE_LOCKED error, it may call the
** sqlite3_unlock_notify() method with the blocked connection handle as
** the first argument to register for a callback that will be invoked
** when the blocking connections current transaction is concluded. ^The
** when the blocking connection's current transaction is concluded. ^The
** callback is invoked from within the [sqlite3_step] or [sqlite3_close]
** call that concludes the blocking connection's transaction.
**
** ^(If sqlite3_unlock_notify() is called in a multi-threaded application,
** there is a chance that the blocking connection will have already
** concluded its transaction by the time sqlite3_unlock_notify() is invoked.
** If this happens, then the specified callback is invoked immediately,
** from within the call to sqlite3_unlock_notify().)^
**
** ^If the blocked connection is attempting to obtain a write-lock on a
** shared-cache table, and more than one other connection currently holds
** a read-lock on the same table, then SQLite arbitrarily selects one of
** the other connections to use as the blocking connection.
**
** ^(There may be at most one unlock-notify callback registered by a
** blocked connection. If sqlite3_unlock_notify() is called when the
** blocked connection already has a registered unlock-notify callback,
** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is
** called with a NULL pointer as its second argument, then any existing
** unlock-notify callback is canceled. ^The blocked connections
** unlock-notify callback is canceled. ^The blocked connection's
** unlock-notify callback may also be canceled by closing the blocked
** connection using [sqlite3_close()].
**
** The unlock-notify callback is not reentrant. If an application invokes
** any sqlite3_xxx API functions from within an unlock-notify callback, a
** crash or deadlock may be the result.
**
9577
9578
9579
9580
9581
9582
9583
9584

9585
9586
9587
9588
9589
9590
9591
9592
9593
9594
9595
9596
9597
9598




















9599
9600
9601
9602
9603
9604
9605
9606
9607
9608
9609
9610
9611
9612
9613
9614
9615

9616
9617
9618
9619
9620
9621
9622
9623
9624
9625
9626
9627
9628
9629
9630
9631
9632
9633




9634
9635
9636
9637
9638
9639
9640
9945
9946
9947
9948
9949
9950
9951

9952
9953
9954
9955
9956
9957
9958
9959







9960
9961
9962
9963
9964
9965
9966
9967
9968
9969
9970
9971
9972
9973
9974
9975
9976
9977
9978
9979
9980
9981
9982
9983
9984
9985
9986
9987
9988
9989
9990
9991
9992
9993
9994
9995

9996
9997
9998
9999
10000
10001
10002
10003
10004
10005
10006
10007
10008
10009
10010
10011



10012
10013
10014
10015
10016
10017
10018
10019
10020
10021
10022







-
+







-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
















-
+















-
-
-
+
+
+
+







** is a copy of the third parameter passed to sqlite3_wal_hook() when
** registering the callback. ^The second is a copy of the database handle.
** ^The third parameter is the name of the database that was written to -
** either "main" or the name of an [ATTACH]-ed database. ^The fourth parameter
** is the number of pages currently in the write-ahead log file,
** including those that were just committed.
**
** The callback function should normally return [SQLITE_OK].  ^If an error
** ^The callback function should normally return [SQLITE_OK].  ^If an error
** code is returned, that error will propagate back up through the
** SQLite code base to cause the statement that provoked the callback
** to report an error, though the commit will have still occurred. If the
** callback returns [SQLITE_ROW] or [SQLITE_DONE], or if it returns a value
** that does not correspond to any valid SQLite error code, the results
** are undefined.
**
** A single database handle may have at most a single write-ahead log callback
** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any
** previously registered write-ahead log callback. ^The return value is
** a copy of the third parameter from the previous call, if any, or 0.
** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the
** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will
** overwrite any prior [sqlite3_wal_hook()] settings.
** ^A single database handle may have at most a single write-ahead log
** callback registered at one time. ^Calling [sqlite3_wal_hook()]
** replaces the default behavior or previously registered write-ahead
** log callback.
**
** ^The return value is a copy of the third parameter from the
** previous call, if any, or 0.
**
** ^The [sqlite3_wal_autocheckpoint()] interface and the
** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and
** will overwrite any prior [sqlite3_wal_hook()] settings.
**
** ^If a write-ahead log callback is set using this function then
** [sqlite3_wal_checkpoint_v2()] or [PRAGMA wal_checkpoint]
** should be invoked periodically to keep the write-ahead log file
** from growing without bound.
**
** ^Passing a NULL pointer for the callback disables automatic
** checkpointing entirely. To re-enable the default behavior, call
** sqlite3_wal_autocheckpoint(db,1000) or use [PRAGMA wal_checkpoint].
*/
SQLITE_API void *sqlite3_wal_hook(
  sqlite3*,
  int(*)(void *,sqlite3*,const char*,int),
  void*
);

/*
** CAPI3REF: Configure an auto-checkpoint
** METHOD: sqlite3
**
** ^The [sqlite3_wal_autocheckpoint(D,N)] is a wrapper around
** [sqlite3_wal_hook()] that causes any database on [database connection] D
** to automatically [checkpoint]
** after committing a transaction if there are N or
** more frames in the [write-ahead log] file.  ^Passing zero or
** a negative value as the nFrame parameter disables automatic
** a negative value as the N parameter disables automatic
** checkpoints entirely.
**
** ^The callback registered by this function replaces any existing callback
** registered using [sqlite3_wal_hook()].  ^Likewise, registering a callback
** using [sqlite3_wal_hook()] disables the automatic checkpoint mechanism
** configured by this function.
**
** ^The [wal_autocheckpoint pragma] can be used to invoke this interface
** from SQL.
**
** ^Checkpoints initiated by this mechanism are
** [sqlite3_wal_checkpoint_v2|PASSIVE].
**
** ^Every new [database connection] defaults to having the auto-checkpoint
** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT]
** pages.  The use of this interface
** is only necessary if the default setting is found to be suboptimal
** for a particular application.
** pages.
**
** ^The use of this interface is only necessary if the default setting
** is found to be suboptimal for a particular application.
*/
SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N);

/*
** CAPI3REF: Checkpoint a database
** METHOD: sqlite3
**
9691
9692
9693
9694
9695
9696
9697





9698
9699
9700
9701
9702
9703
9704
10073
10074
10075
10076
10077
10078
10079
10080
10081
10082
10083
10084
10085
10086
10087
10088
10089
10090
10091







+
+
+
+
+







**   ^Like SQLITE_CHECKPOINT_FULL, this mode blocks new
**   database writer attempts while it is pending, but does not impede readers.
**
** <dt>SQLITE_CHECKPOINT_TRUNCATE<dd>
**   ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the
**   addition that it also truncates the log file to zero bytes just prior
**   to a successful return.
**
** <dt>SQLITE_CHECKPOINT_NOOP<dd>
**   ^This mode always checkpoints zero frames. The only reason to invoke
**   a NOOP checkpoint is to access the values returned by
**   sqlite3_wal_checkpoint_v2() via output parameters *pnLog and *pnCkpt.
** </dl>
**
** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in
** the log file or to -1 if the checkpoint could not run because
** of an error or because the database is not in [WAL mode]. ^If pnCkpt is not
** NULL,then *pnCkpt is set to the total number of checkpointed frames in the
** log file (including any that were already checkpointed before the function
9761
9762
9763
9764
9765
9766
9767

9768
9769
9770
9771
9772
9773
9774
10148
10149
10150
10151
10152
10153
10154
10155
10156
10157
10158
10159
10160
10161
10162







+







** KEYWORDS: {checkpoint mode}
**
** These constants define all valid values for the "checkpoint mode" passed
** as the third parameter to the [sqlite3_wal_checkpoint_v2()] interface.
** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the
** meaning of each of these checkpoint modes.
*/
#define SQLITE_CHECKPOINT_NOOP    -1  /* Do no work at all */
#define SQLITE_CHECKPOINT_PASSIVE  0  /* Do as much as possible w/o blocking */
#define SQLITE_CHECKPOINT_FULL     1  /* Wait for writers, then checkpoint */
#define SQLITE_CHECKPOINT_RESTART  2  /* Like FULL but wait for readers */
#define SQLITE_CHECKPOINT_TRUNCATE 3  /* Like RESTART but also truncate WAL */

/*
** CAPI3REF: Virtual Table Interface Configuration
9805
9806
9807
9808
9809
9810
9811
9812

9813
9814
9815
9816
9817
9818
9819
10193
10194
10195
10196
10197
10198
10199

10200
10201
10202
10203
10204
10205
10206
10207







-
+







** <dd>Calls of the form
** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported,
** where X is an integer.  If X is zero, then the [virtual table] whose
** [xCreate] or [xConnect] method invoked [sqlite3_vtab_config()] does not
** support constraints.  In this configuration (which is the default) if
** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire
** statement is rolled back as if [ON CONFLICT | OR ABORT] had been
** specified as part of the users SQL statement, regardless of the actual
** specified as part of the user's SQL statement, regardless of the actual
** ON CONFLICT mode specified.
**
** If X is non-zero, then the virtual table implementation guarantees
** that if [xUpdate] returns [SQLITE_CONSTRAINT], it will do so before
** any modifications to internal or persistent data structures have been made.
** If the [ON CONFLICT] mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite
** is able to roll back a statement or database transaction, and abandon
9839
9840
9841
9842
9843
9844
9845
9846

9847
9848
9849
9850
9851
9852
9853
10227
10228
10229
10230
10231
10232
10233

10234
10235
10236
10237
10238
10239
10240
10241







-
+







** prohibits that virtual table from being used from within triggers and
** views.
** </dd>
**
** [[SQLITE_VTAB_INNOCUOUS]]<dt>SQLITE_VTAB_INNOCUOUS</dt>
** <dd>Calls of the form
** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the
** the [xConnect] or [xCreate] methods of a [virtual table] implementation
** [xConnect] or [xCreate] methods of a [virtual table] implementation
** identify that virtual table as being safe to use from within triggers
** and views.  Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the
** virtual table can do no serious harm even if it is controlled by a
** malicious hacker.  Developers should avoid setting the SQLITE_VTAB_INNOCUOUS
** flag unless absolutely necessary.
** </dd>
**
10007
10008
10009
10010
10011
10012
10013
10014

10015
10016
10017
10018
10019
10020
10021
10022
10023
10024

10025
10026
10027
10028
10029
10030
10031
10395
10396
10397
10398
10399
10400
10401

10402
10403
10404
10405
10406
10407
10408
10409
10410
10411

10412
10413
10414
10415
10416
10417
10418
10419







-
+









-
+







** <tr><td>0<td>yes<td>yes<td>no
** <tr><td>1<td>no<td>yes<td>no
** <tr><td>2<td>no<td>yes<td>yes
** <tr><td>3<td>yes<td>yes<td>yes
** </table>
**
** ^For the purposes of comparing virtual table output values to see if the
** values are same value for sorting purposes, two NULL values are considered
** values are the same value for sorting purposes, two NULL values are considered
** to be the same.  In other words, the comparison operator is "IS"
** (or "IS NOT DISTINCT FROM") and not "==".
**
** If a virtual table implementation is unable to meet the requirements
** specified above, then it must not set the "orderByConsumed" flag in the
** [sqlite3_index_info] object or an incorrect answer may result.
**
** ^A virtual table implementation is always free to return rows in any order
** it wants, as long as the "orderByConsumed" flag is not set.  ^When the
** the "orderByConsumed" flag is unset, the query planner will add extra
** "orderByConsumed" flag is unset, the query planner will add extra
** [bytecode] to ensure that the final results returned by the SQL query are
** ordered correctly.  The use of the "orderByConsumed" flag and the
** sqlite3_vtab_distinct() interface is merely an optimization.  ^Careful
** use of the sqlite3_vtab_distinct() interface and the "orderByConsumed"
** flag might help queries against a virtual table to run faster.  Being
** overly aggressive and setting the "orderByConsumed" flag when it is not
** valid to do so, on the other hand, might cause SQLite to return incorrect
10114
10115
10116
10117
10118
10119
10120
10121

10122
10123
10124
10125
10126
10127
10128
10129
10130
10131
10132
10133
10134
10135
10136

10137
10138
10139
10140
10141
10142
10143
10502
10503
10504
10505
10506
10507
10508

10509
10510
10511
10512
10513
10514
10515
10516
10517
10518
10519
10520
10521
10522
10523

10524
10525
10526
10527
10528
10529
10530
10531







-
+














-
+







** The result of invoking these interfaces from any other context
** is undefined and probably harmful.
**
** The X parameter in a call to sqlite3_vtab_in_first(X,P) or
** sqlite3_vtab_in_next(X,P) should be one of the parameters to the
** xFilter method which invokes these routines, and specifically
** a parameter that was previously selected for all-at-once IN constraint
** processing use the [sqlite3_vtab_in()] interface in the
** processing using the [sqlite3_vtab_in()] interface in the
** [xBestIndex|xBestIndex method].  ^(If the X parameter is not
** an xFilter argument that was selected for all-at-once IN constraint
** processing, then these routines return [SQLITE_ERROR].)^
**
** ^(Use these routines to access all values on the right-hand side
** of the IN constraint using code like the following:
**
** <blockquote><pre>
** &nbsp;  for(rc=sqlite3_vtab_in_first(pList, &pVal);
** &nbsp;      rc==SQLITE_OK && pVal;
** &nbsp;      rc=sqlite3_vtab_in_next(pList, &pVal)
** &nbsp;  ){
** &nbsp;    // do something with pVal
** &nbsp;  }
** &nbsp;  if( rc!=SQLITE_OK ){
** &nbsp;  if( rc!=SQLITE_DONE ){
** &nbsp;    // an error has occurred
** &nbsp;  }
** </pre></blockquote>)^
**
** ^On success, the sqlite3_vtab_in_first(X,P) and sqlite3_vtab_in_next(X,P)
** routines return SQLITE_OK and set *P to point to the first or next value
** on the RHS of the IN constraint.  ^If there are no more values on the
10169
10170
10171
10172
10173
10174
10175
10176

10177
10178
10179
10180
10181
10182
10183
10557
10558
10559
10560
10561
10562
10563

10564
10565
10566
10567
10568
10569
10570
10571







-
+







** attempts to set *V to the value of the right-hand operand of
** that constraint if the right-hand operand is known.  ^If the
** right-hand operand is not known, then *V is set to a NULL pointer.
** ^The sqlite3_vtab_rhs_value(P,J,V) interface returns SQLITE_OK if
** and only if *V is set to a value.  ^The sqlite3_vtab_rhs_value(P,J,V)
** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th
** constraint is not available.  ^The sqlite3_vtab_rhs_value() interface
** can return an result code other than SQLITE_OK or SQLITE_NOTFOUND if
** can return a result code other than SQLITE_OK or SQLITE_NOTFOUND if
** something goes wrong.
**
** The sqlite3_vtab_rhs_value() interface is usually only successful if
** the right-hand operand of a constraint is a literal value in the original
** SQL statement.  If the right-hand operand is an expression or a reference
** to some other column or a [host parameter], then sqlite3_vtab_rhs_value()
** will probably return [SQLITE_NOTFOUND].
10197
10198
10199
10200
10201
10202
10203
10204
10205


10206
10207
10208
10209
10210
10211
10212
10585
10586
10587
10588
10589
10590
10591


10592
10593
10594
10595
10596
10597
10598
10599
10600







-
-
+
+







SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal);

/*
** CAPI3REF: Conflict resolution modes
** KEYWORDS: {conflict resolution mode}
**
** These constants are returned by [sqlite3_vtab_on_conflict()] to
** inform a [virtual table] implementation what the [ON CONFLICT] mode
** is for the SQL statement being evaluated.
** inform a [virtual table] implementation of the [ON CONFLICT] mode
** for the SQL statement being evaluated.
**
** Note that the [SQLITE_IGNORE] constant is also used as a potential
** return value from the [sqlite3_set_authorizer()] callback and that
** [SQLITE_ABORT] is also a [result code].
*/
#define SQLITE_ROLLBACK 1
/* #define SQLITE_IGNORE 2 // Also used by sqlite3_authorizer() callback */
10238
10239
10240
10241
10242
10243
10244
10245

10246
10247
10248

10249
10250
10251
10252
10253

10254
10255
10256
10257
10258

10259
10260
10261
10262
10263
10264

10265
10266
10267
10268

10269
10270

10271
10272
10273
10274
10275
10276
10277

10278
10279
10280
10281
10282
10283
10284
10626
10627
10628
10629
10630
10631
10632

10633
10634
10635

10636
10637
10638
10639
10640

10641
10642
10643
10644
10645

10646
10647
10648
10649
10650
10651

10652
10653
10654
10655

10656
10657

10658
10659
10660
10661
10662
10663
10664

10665
10666
10667
10668
10669
10670
10671
10672







-
+


-
+




-
+




-
+





-
+



-
+

-
+






-
+







** [[SQLITE_SCANSTAT_NVISIT]] <dt>SQLITE_SCANSTAT_NVISIT</dt>
** <dd>^The [sqlite3_int64] variable pointed to by the V parameter will be set
** to the total number of rows examined by all iterations of the X-th loop.</dd>
**
** [[SQLITE_SCANSTAT_EST]] <dt>SQLITE_SCANSTAT_EST</dt>
** <dd>^The "double" variable pointed to by the V parameter will be set to the
** query planner's estimate for the average number of rows output from each
** iteration of the X-th loop.  If the query planner's estimates was accurate,
** iteration of the X-th loop.  If the query planner's estimate was accurate,
** then this value will approximate the quotient NVISIT/NLOOP and the
** product of this value for all prior loops with the same SELECTID will
** be the NLOOP value for the current loop.
** be the NLOOP value for the current loop.</dd>
**
** [[SQLITE_SCANSTAT_NAME]] <dt>SQLITE_SCANSTAT_NAME</dt>
** <dd>^The "const char *" variable pointed to by the V parameter will be set
** to a zero-terminated UTF-8 string containing the name of the index or table
** used for the X-th loop.
** used for the X-th loop.</dd>
**
** [[SQLITE_SCANSTAT_EXPLAIN]] <dt>SQLITE_SCANSTAT_EXPLAIN</dt>
** <dd>^The "const char *" variable pointed to by the V parameter will be set
** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN]
** description for the X-th loop.
** description for the X-th loop.</dd>
**
** [[SQLITE_SCANSTAT_SELECTID]] <dt>SQLITE_SCANSTAT_SELECTID</dt>
** <dd>^The "int" variable pointed to by the V parameter will be set to the
** id for the X-th query plan element. The id value is unique within the
** statement. The select-id is the same value as is output in the first
** column of an [EXPLAIN QUERY PLAN] query.
** column of an [EXPLAIN QUERY PLAN] query.</dd>
**
** [[SQLITE_SCANSTAT_PARENTID]] <dt>SQLITE_SCANSTAT_PARENTID</dt>
** <dd>The "int" variable pointed to by the V parameter will be set to the
** the id of the parent of the current query element, if applicable, or
** id of the parent of the current query element, if applicable, or
** to zero if the query element has no parent. This is the same value as
** returned in the second column of an [EXPLAIN QUERY PLAN] query.
** returned in the second column of an [EXPLAIN QUERY PLAN] query.</dd>
**
** [[SQLITE_SCANSTAT_NCYCLE]] <dt>SQLITE_SCANSTAT_NCYCLE</dt>
** <dd>The sqlite3_int64 output value is set to the number of cycles,
** according to the processor time-stamp counter, that elapsed while the
** query element was being processed. This value is not available for
** all query elements - if it is unavailable the output variable is
** set to -1.
** set to -1.</dd>
** </dl>
*/
#define SQLITE_SCANSTAT_NLOOP    0
#define SQLITE_SCANSTAT_NVISIT   1
#define SQLITE_SCANSTAT_EST      2
#define SQLITE_SCANSTAT_NAME     3
#define SQLITE_SCANSTAT_EXPLAIN  4
10301
10302
10303
10304
10305
10306
10307
10308

10309
10310

10311
10312
10313
10314
10315
10316
10317
10318
10319


10320
10321
10322
10323
10324


10325
10326
10327
10328
10329
10330
10331
10689
10690
10691
10692
10693
10694
10695

10696
10697

10698
10699
10700
10701
10702
10703
10704
10705


10706
10707
10708
10709
10710
10711

10712
10713
10714
10715
10716
10717
10718
10719
10720







-
+

-
+







-
-
+
+




-
+
+







**
** The "iScanStatusOp" parameter determines which status information to return.
** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior
** of this interface is undefined. ^The requested measurement is written into
** a variable pointed to by the "pOut" parameter.
**
** The "flags" parameter must be passed a mask of flags. At present only
** one flag is defined - SQLITE_SCANSTAT_COMPLEX. If SQLITE_SCANSTAT_COMPLEX
** one flag is defined - [SQLITE_SCANSTAT_COMPLEX]. If SQLITE_SCANSTAT_COMPLEX
** is specified, then status information is available for all elements
** of a query plan that are reported by "EXPLAIN QUERY PLAN" output. If
** of a query plan that are reported by "[EXPLAIN QUERY PLAN]" output. If
** SQLITE_SCANSTAT_COMPLEX is not specified, then only query plan elements
** that correspond to query loops (the "SCAN..." and "SEARCH..." elements of
** the EXPLAIN QUERY PLAN output) are available. Invoking API
** sqlite3_stmt_scanstatus() is equivalent to calling
** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter.
**
** Parameter "idx" identifies the specific query element to retrieve statistics
** for. Query elements are numbered starting from zero. A value of -1 may be
** to query for statistics regarding the entire query. ^If idx is out of range
** for. Query elements are numbered starting from zero. A value of -1 may
** retrieve statistics for the entire query. ^If idx is out of range
** - less than -1 or greater than or equal to the total number of query
** elements used to implement the statement - a non-zero value is returned and
** the variable that pOut points to is unchanged.
**
** See also: [sqlite3_stmt_scanstatus_reset()]
** See also: [sqlite3_stmt_scanstatus_reset()] and the
** [nexec and ncycle] columnes of the [bytecode virtual table].
*/
SQLITE_API int sqlite3_stmt_scanstatus(
  sqlite3_stmt *pStmt,      /* Prepared statement for which info desired */
  int idx,                  /* Index of loop to report on */
  int iScanStatusOp,        /* Information desired.  SQLITE_SCANSTAT_* */
  void *pOut                /* Result written here */
);
10355
10356
10357
10358
10359
10360
10361
10362

10363
10364
10365
10366
10367
10368
10369
10744
10745
10746
10747
10748
10749
10750

10751
10752
10753
10754
10755
10756
10757
10758







-
+







SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*);

/*
** CAPI3REF: Flush caches to disk mid-transaction
** METHOD: sqlite3
**
** ^If a write-transaction is open on [database connection] D when the
** [sqlite3_db_cacheflush(D)] interface invoked, any dirty
** [sqlite3_db_cacheflush(D)] interface is invoked, any dirty
** pages in the pager-cache that are not currently in use are written out
** to disk. A dirty page may be in use if a database cursor created by an
** active SQL statement is reading from it, or if it is page 1 of a database
** file (page 1 is always "in use").  ^The [sqlite3_db_cacheflush(D)]
** interface flushes caches for all schemas - "main", "temp", and
** any [attached] databases.
**
10469
10470
10471
10472
10473
10474
10475
10476
10477


10478
10479
10480
10481
10482
10483
10484
10858
10859
10860
10861
10862
10863
10864


10865
10866
10867
10868
10869
10870
10871
10872
10873







-
-
+
+







** ^The [sqlite3_preupdate_depth(D)] interface returns 0 if the preupdate
** callback was invoked as a result of a direct insert, update, or delete
** operation; or 1 for inserts, updates, or deletes invoked by top-level
** triggers; or 2 for changes resulting from triggers called by top-level
** triggers; and so forth.
**
** When the [sqlite3_blob_write()] API is used to update a blob column,
** the pre-update hook is invoked with SQLITE_DELETE. This is because the
** in this case the new values are not available. In this case, when a
** the pre-update hook is invoked with SQLITE_DELETE, because
** the new values are not yet available. In this case, when a
** callback made with op==SQLITE_DELETE is actually a write using the
** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns
** the index of the column being written. In other cases, where the
** pre-update hook is being invoked for some other reason, including a
** regular DELETE, sqlite3_preupdate_blobwrite() returns -1.
**
** See also:  [sqlite3_update_hook()]
10588
10589
10590
10591
10592
10593
10594
10595

10596
10597
10598
10599
10600
10601
10602
10977
10978
10979
10980
10981
10982
10983

10984
10985
10986
10987
10988
10989
10990
10991







-
+







** The [sqlite3_snapshot] object returned from a successful call to
** [sqlite3_snapshot_get()] must be freed using [sqlite3_snapshot_free()]
** to avoid a memory leak.
**
** The [sqlite3_snapshot_get()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get(
SQLITE_API int sqlite3_snapshot_get(
  sqlite3 *db,
  const char *zSchema,
  sqlite3_snapshot **ppSnapshot
);

/*
** CAPI3REF: Start a read transaction on an historical snapshot
10637
10638
10639
10640
10641
10642
10643
10644

10645
10646
10647
10648
10649
10650
10651
10652
10653
10654
10655
10656
10657
10658
10659
10660
10661

10662
10663
10664
10665
10666
10667
10668
11026
11027
11028
11029
11030
11031
11032

11033
11034
11035
11036
11037
11038
11039
11040
11041
11042
11043
11044
11045
11046
11047
11048
11049

11050
11051
11052
11053
11054
11055
11056
11057







-
+
















-
+







** after the most recent I/O on the database connection.)^
** (Hint: Run "[PRAGMA application_id]" against a newly opened
** database connection in order to make it ready to use snapshots.)
**
** The [sqlite3_snapshot_open()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open(
SQLITE_API int sqlite3_snapshot_open(
  sqlite3 *db,
  const char *zSchema,
  sqlite3_snapshot *pSnapshot
);

/*
** CAPI3REF: Destroy a snapshot
** DESTRUCTOR: sqlite3_snapshot
**
** ^The [sqlite3_snapshot_free(P)] interface destroys [sqlite3_snapshot] P.
** The application must eventually free every [sqlite3_snapshot] object
** using this routine to avoid a memory leak.
**
** The [sqlite3_snapshot_free()] interface is only available when the
** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used.
*/
SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*);
SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot*);

/*
** CAPI3REF: Compare the ages of two snapshot handles.
** METHOD: sqlite3_snapshot
**
** The sqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages
** of two valid snapshot handles.
10681
10682
10683
10684
10685
10686
10687
10688

10689
10690
10691
10692
10693
10694
10695
11070
11071
11072
11073
11074
11075
11076

11077
11078
11079
11080
11081
11082
11083
11084







-
+







** Otherwise, this API returns a negative value if P1 refers to an older
** snapshot than P2, zero if the two handles refer to the same database
** snapshot, and a positive value if P1 is a newer snapshot than P2.
**
** This interface is only available if SQLite is compiled with the
** [SQLITE_ENABLE_SNAPSHOT] option.
*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp(
SQLITE_API int sqlite3_snapshot_cmp(
  sqlite3_snapshot *p1,
  sqlite3_snapshot *p2
);

/*
** CAPI3REF: Recover snapshots from a wal file
** METHOD: sqlite3_snapshot
10709
10710
10711
10712
10713
10714
10715
10716

10717
10718
10719
10720
10721
10722



10723
10724
10725
10726
10727
10728
10729

10730
10731
10732
10733
10734
10735
10736
10737
10738

10739
10740
10741
10742
10743
10744
10745
11098
11099
11100
11101
11102
11103
11104

11105
11106
11107
11108
11109


11110
11111
11112
11113
11114
11115
11116
11117
11118

11119
11120
11121
11122
11123
11124
11125
11126
11127

11128
11129
11130
11131
11132
11133
11134
11135







-
+




-
-
+
+
+






-
+








-
+







** database.
**
** SQLITE_OK is returned if successful, or an SQLite error code otherwise.
**
** This interface is only available if SQLite is compiled with the
** [SQLITE_ENABLE_SNAPSHOT] option.
*/
SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);
SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb);

/*
** CAPI3REF: Serialize a database
**
** The sqlite3_serialize(D,S,P,F) interface returns a pointer to memory
** that is a serialization of the S database on [database connection] D.
** The sqlite3_serialize(D,S,P,F) interface returns a pointer to
** memory that is a serialization of the S database on
** [database connection] D.  If S is a NULL pointer, the main database is used.
** If P is not a NULL pointer, then the size of the database in bytes
** is written into *P.
**
** For an ordinary on-disk database file, the serialization is just a
** copy of the disk file.  For an in-memory database or a "TEMP" database,
** the serialization is the same sequence of bytes which would be written
** to disk if that database where backed up to disk.
** to disk if that database were backed up to disk.
**
** The usual case is that sqlite3_serialize() copies the serialization of
** the database into memory obtained from [sqlite3_malloc64()] and returns
** a pointer to that memory.  The caller is responsible for freeing the
** returned value to avoid a memory leak.  However, if the F argument
** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations
** are made, and the sqlite3_serialize() function will return a pointer
** to the contiguous memory representation of the database that SQLite
** is currently using for that database, or NULL if the no such contiguous
** is currently using for that database, or NULL if no such contiguous
** memory representation of the database exists.  A contiguous memory
** representation of the database will usually only exist if there has
** been a prior call to [sqlite3_deserialize(D,S,...)] with the same
** values of D and S.
** The size of the database is written into *P even if the
** SQLITE_SERIALIZE_NOCOPY bit is set but no contiguous copy
** of the database exists.
10782
10783
10784
10785
10786
10787
10788
10789
10790
10791
10792
10793
10794







10795
10796
10797
10798
10799
10800
10801
10802
10803
10804
10805
10806
10807
10808
10809

10810
10811
10812
10813
10814
10815
10816
11172
11173
11174
11175
11176
11177
11178






11179
11180
11181
11182
11183
11184
11185
11186
11187
11188
11189
11190
11191
11192
11193
11194
11195
11196
11197
11198
11199

11200
11201
11202
11203
11204
11205
11206
11207







-
-
-
-
-
-
+
+
+
+
+
+
+














-
+







#define SQLITE_SERIALIZE_NOCOPY 0x001   /* Do no memory allocations */

/*
** CAPI3REF: Deserialize a database
**
** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the
** [database connection] D to disconnect from database S and then
** reopen S as an in-memory database based on the serialization contained
** in P.  The serialized database P is N bytes in size.  M is the size of
** the buffer P, which might be larger than N.  If M is larger than N, and
** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is
** permitted to add content to the in-memory database as long as the total
** size does not exceed M bytes.
** reopen S as an in-memory database based on the serialization
** contained in P.  If S is a NULL pointer, the main database is
** used. The serialized database P is N bytes in size.  M is the size
** of the buffer P, which might be larger than N.  If M is larger than
** N, and the SQLITE_DESERIALIZE_READONLY bit is not set in F, then
** SQLite is permitted to add content to the in-memory database as
** long as the total size does not exceed M bytes.
**
** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will
** invoke sqlite3_free() on the serialization buffer when the database
** connection closes.  If the SQLITE_DESERIALIZE_RESIZEABLE bit is set, then
** SQLite will try to increase the buffer size using sqlite3_realloc64()
** if writes on the database cause it to grow larger than M bytes.
**
** Applications must not modify the buffer P or invalidate it before
** the database connection D is closed.
**
** The sqlite3_deserialize() interface will fail with SQLITE_BUSY if the
** database is currently in a read transaction or is involved in a backup
** operation.
**
** It is not possible to deserialized into the TEMP database.  If the
** It is not possible to deserialize into the TEMP database.  If the
** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the
** function returns SQLITE_ERROR.
**
** The deserialized database should not be in [WAL mode].  If the database
** is in WAL mode, then any attempt to use the database file will result
** in an [SQLITE_CANTOPEN] error.  The application can set the
** [file format version numbers] (bytes 18 and 19) of the input database P
10824
10825
10826
10827
10828
10829
10830
10831

10832
10833
10834
10835
10836
10837
10838
10839

10840
10841
10842
10843
10844
10845
10846
11215
11216
11217
11218
11219
11220
11221

11222
11223
11224
11225
11226
11227
11228
11229

11230
11231
11232
11233
11234
11235
11236
11237







-
+







-
+







** This interface is omitted if SQLite is compiled with the
** [SQLITE_OMIT_DESERIALIZE] option.
*/
SQLITE_API int sqlite3_deserialize(
  sqlite3 *db,            /* The database connection */
  const char *zSchema,    /* Which DB to reopen with the deserialization */
  unsigned char *pData,   /* The serialized database content */
  sqlite3_int64 szDb,     /* Number bytes in the deserialization */
  sqlite3_int64 szDb,     /* Number of bytes in the deserialization */
  sqlite3_int64 szBuf,    /* Total size of buffer pData[] */
  unsigned mFlags         /* Zero or more SQLITE_DESERIALIZE_* flags */
);

/*
** CAPI3REF: Flags for sqlite3_deserialize()
**
** The following are allowed values for 6th argument (the F argument) to
** The following are allowed values for the 6th argument (the F argument) to
** the [sqlite3_deserialize(D,S,P,N,M,F)] interface.
**
** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization
** in the P argument is held in memory obtained from [sqlite3_malloc64()]
** and that SQLite should take ownership of this memory and automatically
** free it when it has finished using it.  Without this flag, the caller
** is responsible for freeing any dynamically allocated memory.
10854
10855
10856
10857
10858
10859
10860






































































10861
10862
10863
10864
10865
10866
10867
11245
11246
11247
11248
11249
11250
11251
11252
11253
11254
11255
11256
11257
11258
11259
11260
11261
11262
11263
11264
11265
11266
11267
11268
11269
11270
11271
11272
11273
11274
11275
11276
11277
11278
11279
11280
11281
11282
11283
11284
11285
11286
11287
11288
11289
11290
11291
11292
11293
11294
11295
11296
11297
11298
11299
11300
11301
11302
11303
11304
11305
11306
11307
11308
11309
11310
11311
11312
11313
11314
11315
11316
11317
11318
11319
11320
11321
11322
11323
11324
11325
11326
11327
11328







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







** The SQLITE_DESERIALIZE_READONLY flag means that the deserialized database
** should be treated as read-only.
*/
#define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call sqlite3_free() on close */
#define SQLITE_DESERIALIZE_RESIZEABLE  2 /* Resize using sqlite3_realloc64() */
#define SQLITE_DESERIALIZE_READONLY    4 /* Database is read-only */

/*
** CAPI3REF: Bind array values to the CARRAY table-valued function
**
** The sqlite3_carray_bind_v2(S,I,P,N,F,X,D) interface binds an array value to
** parameter that is the first argument of the [carray() table-valued function].
** The S parameter is a pointer to the [prepared statement] that uses the carray()
** functions.  I is the parameter index to be bound.  I must be the index of the
** parameter that is the first argument to the carray() table-valued function.
** P is a pointer to the array to be bound, and N is the number of elements in
** the array.  The F argument is one of constants [SQLITE_CARRAY_INT32],
** [SQLITE_CARRAY_INT64], [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT],
** or [SQLITE_CARRAY_BLOB] to indicate the datatype of the array P.
**
** If the X argument is not a NULL pointer or one of the special
** values [SQLITE_STATIC] or [SQLITE_TRANSIENT], then SQLite will invoke
** the function X with argument D when it is finished using the data in P.
** The call to X(D) is a destructor for the array P. The destructor X(D)
** is invoked even if the call to sqlite3_carray_bind() fails. If the X
** parameter is the special-case value [SQLITE_STATIC], then SQLite assumes
** that the data static and the destructor is never invoked.  If the X
** parameter is the special-case value [SQLITE_TRANSIENT], then
** sqlite3_carray_bind_v2() makes its own private copy of the data prior
** to returning and never invokes the destructor X.
**
** The sqlite3_carray_bind() function works the same as sqlite_carray_bind_v2()
** with a D parameter set to P.  In other words,
** sqlite3_carray_bind(S,I,P,N,F,X) is same as
** sqlite3_carray_bind(S,I,P,N,F,X,P).
*/
SQLITE_API int sqlite3_carray_bind_v2(
  sqlite3_stmt *pStmt,        /* Statement to be bound */
  int i,                      /* Parameter index */
  void *aData,                /* Pointer to array data */
  int nData,                  /* Number of data elements */
  int mFlags,                 /* CARRAY flags */
  void (*xDel)(void*),        /* Destructor for aData */
  void *pDel                  /* Optional argument to xDel() */
);
SQLITE_API int sqlite3_carray_bind(
  sqlite3_stmt *pStmt,        /* Statement to be bound */
  int i,                      /* Parameter index */
  void *aData,                /* Pointer to array data */
  int nData,                  /* Number of data elements */
  int mFlags,                 /* CARRAY flags */
  void (*xDel)(void*)         /* Destructor for aData */
);

/*
** CAPI3REF: Datatypes for the CARRAY table-valued function
**
** The fifth argument to the [sqlite3_carray_bind()] interface musts be
** one of the following constants, to specify the datatype of the array
** that is being bound into the [carray table-valued function].
*/
#define SQLITE_CARRAY_INT32     0    /* Data is 32-bit signed integers */
#define SQLITE_CARRAY_INT64     1    /* Data is 64-bit signed integers */
#define SQLITE_CARRAY_DOUBLE    2    /* Data is doubles */
#define SQLITE_CARRAY_TEXT      3    /* Data is char* */
#define SQLITE_CARRAY_BLOB      4    /* Data is struct iovec */

/*
** Versions of the above #defines that omit the initial SQLITE_, for
** legacy compatibility.
*/
#define CARRAY_INT32     0    /* Data is 32-bit signed integers */
#define CARRAY_INT64     1    /* Data is 64-bit signed integers */
#define CARRAY_DOUBLE    2    /* Data is doubles */
#define CARRAY_TEXT      3    /* Data is char* */
#define CARRAY_BLOB      4    /* Data is struct iovec */

/*
** Undo the hack that converts floating point types to integer for
** builds on processors without floating point support.
*/
#ifdef SQLITE_OMIT_FLOATING_POINT
# undef double
#endif
10876
10877
10878
10879
10880
10881
10882
10883

10884
10885
10886
10887
10888
10889
10890
11337
11338
11339
11340
11341
11342
11343

11344
11345
11346
11347
11348
11349
11350
11351







-
+







#  define SQLITE_THREADSAFE 0
# endif
#endif

#ifdef __cplusplus
}  /* End of the 'extern "C"' block */
#endif
#endif /* SQLITE3_H */
/* #endif for SQLITE3_H will be added by mksqlite3.tcl */

/******** Begin file sqlite3rtree.h *********/
/*
** 2010 August 30
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
11357
11358
11359
11360
11361
11362
11363
11364
11365
11366




11367
11368
11369
11370
11371
11372
11373
11818
11819
11820
11821
11822
11823
11824



11825
11826
11827
11828
11829
11830
11831
11832
11833
11834
11835







-
-
-
+
+
+
+







** When a session object is disabled (see the [sqlite3session_enable()] API),
** it does not accumulate records when rows are inserted, updated or deleted.
** This may appear to have some counter-intuitive effects if a single row
** is written to more than once during a session. For example, if a row
** is inserted while a session object is enabled, then later deleted while
** the same session object is disabled, no INSERT record will appear in the
** changeset, even though the delete took place while the session was disabled.
** Or, if one field of a row is updated while a session is disabled, and
** another field of the same row is updated while the session is enabled, the
** resulting changeset will contain an UPDATE change that updates both fields.
** Or, if one field of a row is updated while a session is enabled, and
** then another field of the same row is updated while the session is disabled,
** the resulting changeset will contain an UPDATE change that updates both
** fields.
*/
SQLITE_API int sqlite3session_changeset(
  sqlite3_session *pSession,      /* Session object */
  int *pnChangeset,               /* OUT: Size of buffer at *ppChangeset */
  void **ppChangeset              /* OUT: Buffer containing changeset */
);

11431
11432
11433
11434
11435
11436
11437

11438
11439


11440
11441
11442
11443
11444
11445
11446
11893
11894
11895
11896
11897
11898
11899
11900


11901
11902
11903
11904
11905
11906
11907
11908
11909







+
-
-
+
+







** </ul>
**
** To clarify, if this function is called and then a changeset constructed
** using [sqlite3session_changeset()], then after applying that changeset to
** database zFrom the contents of the two compatible tables would be
** identical.
**
** Unless the call to this function is a no-op as described above, it is an
** It an error if database zFrom does not exist or does not contain the
** required compatible table.
** error if database zFrom does not exist or does not contain the required
** compatible table.
**
** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite
** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg
** may be set to point to a buffer containing an English language error
** message. It is the responsibility of the caller to free this buffer using
** sqlite3_free().
*/
11567
11568
11569
11570
11571
11572
11573
11574

11575
11576
11577
11578
11579
11580
11581
12030
12031
12032
12033
12034
12035
12036

12037
12038
12039
12040
12041
12042
12043
12044







-
+








/*
** CAPI3REF: Flags for sqlite3changeset_start_v2
**
** The following flags may passed via the 4th parameter to
** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]:
**
** <dt>SQLITE_CHANGESETAPPLY_INVERT <dd>
** <dt>SQLITE_CHANGESETSTART_INVERT <dd>
**   Invert the changeset while iterating through it. This is equivalent to
**   inverting a changeset using sqlite3changeset_invert() before applying it.
**   It is an error to specify this flag with a patchset.
*/
#define SQLITE_CHANGESETSTART_INVERT        0x0002


11882
11883
11884
11885
11886
11887
11888
11889
11890
11891
11892
11893
11894
11895
11896
11897
11898
11899
11900
11901
11902
11903
11904
11905
11906
11907
11908
12345
12346
12347
12348
12349
12350
12351













12352
12353
12354
12355
12356
12357
12358







-
-
-
-
-
-
-
-
-
-
-
-
-







  void *pA,                       /* Pointer to buffer containing changeset A */
  int nB,                         /* Number of bytes in buffer pB */
  void *pB,                       /* Pointer to buffer containing changeset B */
  int *pnOut,                     /* OUT: Number of bytes in output changeset */
  void **ppOut                    /* OUT: Buffer containing output changeset */
);


/*
** CAPI3REF: Upgrade the Schema of a Changeset/Patchset
*/
SQLITE_API int sqlite3changeset_upgrade(
  sqlite3 *db,
  const char *zDb,
  int nIn, const void *pIn,       /* Input changeset */
  int *pnOut, void **ppOut        /* OUT: Inverse of input */
);



/*
** CAPI3REF: Changegroup Handle
**
** A changegroup is an object used to combine two or more
** [changesets] or [patchsets]
*/
typedef struct sqlite3_changegroup sqlite3_changegroup;
12123
12124
12125
12126
12127
12128
12129
12130









12131


12132
12133
12134
12135
12136
12137
12138














12139
12140
12141
12142
12143
12144
12145
12146
12147
12148
12149
12150
12151
12152
12153
12154
12155
12156
12157
12158
12159
12160
12161
12162
12163





12164
12165
12166
12167
12168
12169
12170
12573
12574
12575
12576
12577
12578
12579
12580
12581
12582
12583
12584
12585
12586
12587
12588
12589
12590
12591
12592







12593
12594
12595
12596
12597
12598
12599
12600
12601
12602
12603
12604
12605
12606
12607
12608
12609
12610
12611
12612
12613
12614
12615
12616
12617
12618
12619
12620
12621
12622
12623
12624
12625
12626





12627
12628
12629
12630
12631
12632
12633
12634
12635
12636
12637
12638








+
+
+
+
+
+
+
+
+

+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+




















-
-
-
-
-
+
+
+
+
+








/*
** CAPI3REF: Apply A Changeset To A Database
**
** Apply a changeset or patchset to a database. These functions attempt to
** update the "main" database attached to handle db with the changes found in
** the changeset passed via the second and third arguments.
**
** All changes made by these functions are enclosed in a savepoint transaction.
** If any other error (aside from a constraint failure when attempting to
** write to the target database) occurs, then the savepoint transaction is
** rolled back, restoring the target database to its original state, and an
** SQLite error code returned. Additionally, starting with version 3.51.0,
** an error code and error message that may be accessed using the
** [sqlite3_errcode()] and [sqlite3_errmsg()] APIs are left in the database
** handle.
**
** The fourth argument (xFilter) passed to these functions is the "filter
** callback". This may be passed NULL, in which case all changes in the
** changeset are applied to the database. For sqlite3changeset_apply() and
** callback". If it is not NULL, then for each table affected by at least one
** change in the changeset, the filter callback is invoked with
** the table name as the second argument, and a copy of the context pointer
** passed as the sixth argument as the first. If the "filter callback"
** returns zero, then no attempt is made to apply any changes to the table.
** Otherwise, if the return value is non-zero or the xFilter argument to
** is NULL, all changes related to the table are attempted.
** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once
** for each table affected by at least one change in the changeset. In this
** case the table name is passed as the second argument, and a copy of
** the context pointer passed as the sixth argument to apply() or apply_v2()
** as the first. If the "filter callback" returns zero, then no attempt is
** made to apply any changes to the table. Otherwise, if the return value is
** non-zero, all changes related to the table are attempted.
**
** For sqlite3_changeset_apply_v3(), the xFilter callback is invoked once
** per change. The second argument in this case is an sqlite3_changeset_iter
** that may be queried using the usual APIs for the details of the current
** change. If the "filter callback" returns zero in this case, then no attempt
** is made to apply the current change. If it returns non-zero, the change
** is applied.
**
** For each table that is not excluded by the filter callback, this function
** tests that the target database contains a compatible table. A table is
** considered compatible if all of the following are true:
**
** <ul>
**   <li> The table has the same name as the name recorded in the
**        changeset, and
**   <li> The table has at least as many columns as recorded in the
**        changeset, and
**   <li> The table has primary key columns in the same position as
**        recorded in the changeset.
** </ul>
**
** If there is no compatible table, it is not an error, but none of the
** changes associated with the table are applied. A warning message is issued
** via the sqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most
** one such warning is issued for each table in the changeset.
**
** For each change for which there is a compatible table, an attempt is made
** to modify the table contents according to the UPDATE, INSERT or DELETE
** change. If a change cannot be applied cleanly, the conflict handler
** function passed as the fifth argument to sqlite3changeset_apply() may be
** invoked. A description of exactly when the conflict handler is invoked for
** each type of change is below.
** to modify the table contents according to each UPDATE, INSERT or DELETE
** change that is not excluded by a filter callback. If a change cannot be
** applied cleanly, the conflict handler function passed as the fifth argument
** to sqlite3changeset_apply() may be invoked. A description of exactly when
** the conflict handler is invoked for each type of change is below.
**
** Unlike the xFilter argument, xConflict may not be passed NULL. The results
** of passing anything other than a valid function pointer as the xConflict
** argument are undefined.
**
** Each time the conflict handler function is invoked, it must return one
** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or
12252
12253
12254
12255
12256
12257
12258
12259
12260
12261
12262
12263
12264
12265
12266
12267
12268
12269
12270
12271
12720
12721
12722
12723
12724
12725
12726






12727
12728
12729
12730
12731
12732
12733







-
-
-
-
-
-







** </dl>
**
** It is safe to execute SQL statements, including those that write to the
** table that the callback related to, from within the xConflict callback.
** This can be used to further customize the application's conflict
** resolution strategy.
**
** All changes made by these functions are enclosed in a savepoint transaction.
** If any other error (aside from a constraint failure when attempting to
** write to the target database) occurs, then the savepoint transaction is
** rolled back, restoring the target database to its original state, and an
** SQLite error code returned.
**
** If the output parameters (ppRebase) and (pnRebase) are non-NULL and
** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2()
** may set (*ppRebase) to point to a "rebase" that may be used with the
** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase)
** is set to the size of the buffer in bytes. It is the responsibility of the
** caller to eventually free any such buffer using sqlite3_free(). The buffer
** is only allocated and populated if one or more conflicts were encountered
12298
12299
12300
12301
12302
12303
12304

















12305
12306
12307
12308
12309
12310
12311
12760
12761
12762
12763
12764
12765
12766
12767
12768
12769
12770
12771
12772
12773
12774
12775
12776
12777
12778
12779
12780
12781
12782
12783
12784
12785
12786
12787
12788
12789
12790







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  sqlite3 *db,                    /* Apply change to "main" db of this handle */
  int nChangeset,                 /* Size of changeset in bytes */
  void *pChangeset,               /* Changeset blob */
  int(*xFilter)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    const char *zTab              /* Table name */
  ),
  int(*xConflict)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
    sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  ),
  void *pCtx,                     /* First argument passed to xConflict */
  void **ppRebase, int *pnRebase, /* OUT: Rebase data */
  int flags                       /* SESSION_CHANGESETAPPLY_* flags */
);
SQLITE_API int sqlite3changeset_apply_v3(
  sqlite3 *db,                    /* Apply change to "main" db of this handle */
  int nChangeset,                 /* Size of changeset in bytes */
  void *pChangeset,               /* Changeset blob */
  int(*xFilter)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    sqlite3_changeset_iter *p     /* Handle describing change */
  ),
  int(*xConflict)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
    sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  ),
  void *pCtx,                     /* First argument passed to xConflict */
  void **ppRebase, int *pnRebase, /* OUT: Rebase data */
12725
12726
12727
12728
12729
12730
12731

















12732
12733
12734
12735
12736
12737
12738
13204
13205
13206
13207
13208
13209
13210
13211
13212
13213
13214
13215
13216
13217
13218
13219
13220
13221
13222
13223
13224
13225
13226
13227
13228
13229
13230
13231
13232
13233
13234







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    void *pCtx,                   /* Copy of sixth arg to _apply() */
    int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
    sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  ),
  void *pCtx,                     /* First argument passed to xConflict */
  void **ppRebase, int *pnRebase,
  int flags
);
SQLITE_API int sqlite3changeset_apply_v3_strm(
  sqlite3 *db,                    /* Apply change to "main" db of this handle */
  int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
  void *pIn,                                          /* First arg for xInput */
  int(*xFilter)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    sqlite3_changeset_iter *p
  ),
  int(*xConflict)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
    sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  ),
  void *pCtx,                     /* First argument passed to xConflict */
  void **ppRebase, int *pnRebase,
  int flags
);
SQLITE_API int sqlite3changeset_concat_strm(
  int (*xInputA)(void *pIn, void *pData, int *pnData),
  void *pInA,
  int (*xInputB)(void *pIn, void *pData, int *pnData),
  void *pInB,
  int (*xOutput)(void *pOut, const void *pData, int nData),
13127
13128
13129
13130
13131
13132
13133
13134
13135
13136

13137
13138
13139
13140

















13141
13142
13143
13144
13145
13146
13147
13623
13624
13625
13626
13627
13628
13629



13630
13631
13632
13633
13634
13635
13636
13637
13638
13639
13640
13641
13642
13643
13644
13645
13646
13647
13648
13649
13650
13651
13652
13653
13654
13655
13656
13657
13658







-
-
-
+




+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







**
** xInstToken(pFts5, iIdx, iToken, ppToken, pnToken)
**   This is used to access token iToken of phrase hit iIdx within the
**   current row. If iIdx is less than zero or greater than or equal to the
**   value returned by xInstCount(), SQLITE_RANGE is returned.  Otherwise,
**   output variable (*ppToken) is set to point to a buffer containing the
**   matching document token, and (*pnToken) to the size of that buffer in
**   bytes. This API is not available if the specified token matches a
**   prefix query term. In that case both output variables are always set
**   to 0.
**   bytes.
**
**   The output text is not a copy of the document text that was tokenized.
**   It is the output of the tokenizer module. For tokendata=1 tables, this
**   includes any embedded 0x00 and trailing data.
**
**   This API may be slow in some cases if the token identified by parameters
**   iIdx and iToken matched a prefix token in the query. In most cases, the
**   first call to this API for each prefix token in the query is forced
**   to scan the portion of the full-text index that matches the prefix
**   token to collect the extra data required by this API. If the prefix
**   token matches a large number of token instances in the document set,
**   this may be a performance problem.
**
**   If the user knows in advance that a query may use this API for a
**   prefix token, FTS5 may be configured to collect all required data as part
**   of the initial querying of the full-text index, avoiding the second scan
**   entirely. This also causes prefix queries that do not use this API to
**   run more slowly and use more memory. FTS5 may be configured in this way
**   either on a per-table basis using the [FTS5 insttoken | 'insttoken']
**   option, or on a per-query basis using the
**   [fts5_insttoken | fts5_insttoken()] user function.
**
**   This API can be quite slow if used with an FTS5 table created with the
**   "detail=none" or "detail=column" option.
**
** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale)
**   If parameter iCol is less than zero, or greater than or equal to the
**   number of columns in the table, SQLITE_RANGE is returned.
13568
13569
13570
13571
13572
13573
13574

14079
14080
14081
14082
14083
14084
14085
14086







+
#ifdef __cplusplus
}  /* end of the 'extern "C"' block */
#endif

#endif /* _FTS5_H */

/******** End of fts5.h *********/
#endif /* SQLITE3_H */
Changes to skins/blitz/footer.txt.
1
2
3
4
5
6

7
8
9
10
1
2
3
4
5

6
7
8
9
10





-
+




  </div> <!-- end div container -->
</div> <!-- end div middle max-full-width -->
<footer>
  <div class="container">
    <div class="pull-right">
      <a href="https://www.fossil-scm.org/">Fossil $release_version $manifest_version $manifest_date</a>
      <a href="https://fossil-scm.org/">Fossil $release_version $manifest_version $manifest_date</a>
    </div>
    This page was generated in about <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s
  </div>
</footer>
Changes to skins/darkmode/css.txt.
96
97
98
99
100
101
102



103
104
105
106

107
108
109
110
111
112
113
96
97
98
99
100
101
102
103
104
105
106
107
108

109
110
111
112
113
114
115
116







+
+
+



-
+







  color: rgba(24,24,24,0.8);
  border-radius: 0.1em;
}
.fileage tr:hover,
div.filetreeline:hover {
  background-color: #333;
}
div.file-change-line button {
  background-color: #484848
}
.button,
button {
  color: #aaa;
  background-color: #444;
  background-color: #484848;
  border-radius: 5px;
  border: 0
}
.button:hover,
button:hover {
  background-color: #FF4500f0;
  color: rgba(24,24,24,0.8);
Changes to skins/darkmode/footer.txt.
1
2
3
4

5
6
7
8
1
2
3

4
5
6
7
8



-
+




<footer>
  <div class="container">
    <div class="pull-right">
      <a href="https://www.fossil-scm.org/">Fossil $release_version $manifest_version $manifest_date</a>
      <a href="https://fossil-scm.org/">Fossil $release_version $manifest_version $manifest_date</a>
    </div>
    This page was generated in about <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s
  </div>
</footer>
Changes to skins/default/css.txt.
354
355
356
357
358
359
360
361

362
363

364
365
366
367
368
369
370
354
355
356
357
358
359
360

361
362
363
364
365
366
367
368
369
370
371







-
+


+







  padding: 0 4px;
}
.content pre, table.numbered-lines > tbody > tr {
  hyphens: none;
  line-height: 1.25;
}

.content ul:not(.browser) li {
.content ul:not(.browser) > li {
  list-style-type: disc;
}

div.filetree ul li.dir,
div.filetree ul li.subdir,
div.filetree ul li.file{
  list-style-type: none;
}

.artifact > .content table,
Changes to skins/default/header.txt.
26
27
28
29
30
31
32
33

34
35
36
37
38
39
40
26
27
28
29
30
31
32

33
34
35
36
37
38
39
40







-
+







        set logourl $baseurl
      }
      return $logourl
    }
    set logourl [getLogoUrl $baseurl]
    </th1>
    <a href="$logourl">
      <img src="$logo_image_url" border="0" alt="$project_name">
      <img src="$logo_image_url" border="0" alt="$<project_name>">
    </a>
  </div>
  <div class="title">
    <h1>$<project_name></h1>
    <span class="page-title">$<title></span>
  </div>
  <div class="status">
Changes to skins/eagle/css.txt.
178
179
180
181
182
183
184
185

186
187
188
189
190
191
192
178
179
180
181
182
183
184

185
186
187
188
189
190
191
192







-
+







/* the format for the timeline data table */
table.timelineTable {
  cellspacing: 0;
  border: 0;
  cellpadding: 0;
  font-family: "courier new";
  border-spacing: 0px 2px;
  // border-collapse: collapse;
  /* border-collapse: collapse; */
}

.timelineSelected {
  background-color: #7EA2D9;
}
.timelineSecondary {
  background-color: #7EA27E;
Changes to skins/eagle/footer.txt.
8
9
10
11
12
13
14
15

16
17
18
19
20
21
22
8
9
10
11
12
13
14

15
16
17
18
19
20
21
22







-
+







  }
  proc getVersion { version } {
    set length [string length $version]
    return [string range $version 1 [expr {$length - 2}]]
  }
  set version [getVersion $manifest_version]
  set tclVersion [getTclVersion]
  set fossilUrl https://www.fossil-scm.org
  set fossilUrl https://fossil-scm.org
  set fossilDate [string range $manifest_date 0 9]T[string range $manifest_date 11 end]
  </th1>
  This page was generated in about
  <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by
  <a href="$fossilUrl/">Fossil</a>
  version $release_version $tclVersion
  <a href="$fossilUrl/index.html/info/$version">$manifest_version</a>
Changes to skins/eagle/header.txt.
63
64
65
66
67
68
69
70

71
72
73
74
75
76
77
63
64
65
66
67
68
69

70
71
72
73
74
75
76
77







-
+







      set logourl [getLogoUrl $baseurl]
    } else {
      # Link logo to the top of the current repo
      set logourl $baseurl
    }
    </th1>
    <a href="$logourl">
      <img src="$logo_image_url" border="0" alt="$project_name">
      <img src="$logo_image_url" border="0" alt="$<project_name>">
    </a>
  </div>
  <div class="title">$<title></div>
  <div class="status"><nobr><th1>
     if {[info exists login]} {
       puts "Logged in as $login"
     } else {
Changes to skins/original/footer.txt.
8
9
10
11
12
13
14
15

16
17
18
19
20
21
22
8
9
10
11
12
13
14

15
16
17
18
19
20
21
22







-
+







  }
  proc getVersion { version } {
    set length [string length $version]
    return [string range $version 1 [expr {$length - 2}]]
  }
  set version [getVersion $manifest_version]
  set tclVersion [getTclVersion]
  set fossilUrl https://www.fossil-scm.org
  set fossilUrl https://fossil-scm.org
  set fossilDate [string range $manifest_date 0 9]T[string range $manifest_date 11 end]
  </th1>
  This page was generated in about
  <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by
  <a href="$fossilUrl/">Fossil</a>
  version $release_version $tclVersion
  <a href="$fossilUrl/index.html/info/$version">$manifest_version</a>
Changes to skins/original/header.txt.
57
58
59
60
61
62
63
64

65
66
67
68
69
70
71
57
58
59
60
61
62
63

64
65
66
67
68
69
70
71







-
+







        set logourl $baseurl
      }
      return $logourl
    }
    set logourl [getLogoUrl $baseurl]
    </th1>
    <a href="$logourl">
      <img src="$logo_image_url" border="0" alt="$project_name">
      <img src="$logo_image_url" border="0" alt="$<project_name>">
    </a>
  </div>
  <div class="title">$<title></div>
  <div class="status"><nobr><th1>
     if {[info exists login]} {
       puts "Logged in as $login"
     } else {
Changes to skins/xekri/css.txt.
18
19
20
21
22
23
24

25
26
27
28
29
30
31
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32







+







  color: #eee;
  font-family: Monospace;
  font-size: 1em;
  min-height: 100%;
}

body {
  background-color: #333;
  margin: 0;
  padding: 0;
  text-size-adjust: none;
}

a {
  color: #40a0ff;
880
881
882
883
884
885
886




































































































887
888
889
890
891
892
893
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  white-space:  nowrap;
}

/* the format for the timeline version links */
a.timelineHistLink {
}

/* Timeline graph style taken from Ardoise, with
** minor adjustments (2025-03-28) */
.tl-canvas {
  margin: 0 6px 0 10px
}
.tl-rail {
  width: 18px
}
.tl-mergeoffset {
  width: 2px
}
.tl-nodemark {
  margin-top: .8em
}
.tl-node {
  width: 10px;
  height: 10px;
  border: 1px solid #bbb;
  background: #111;
  cursor: pointer
}
.tl-node.leaf:after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 4px;
  height: 4px;
  background: #bbb
}
.tl-node.closed-leaf svg {
  position: absolute;
  top: 0px;
  left: 0px;
  width: 10px;
  height: 10px;
  color: #bbb;
}
.tl-node.sel:after {
  content: '';
  position: absolute;
  top: 1px;
  left: 1px;
  width: 8px;
  height: 8px;
  background: #ff8000
}
.tl-arrow {
  width: 0;
  height: 0;
  transform: scale(.999);
  border: 0 solid transparent
}
.tl-arrow.u {
  margin-top: -1px;
  border-width: 0 3px;
  border-bottom: 7px solid
}
.tl-arrow.u.sm {
  border-bottom: 5px solid #bbb
}
.tl-line {
  background: #bbb;
  width: 2px
}
.tl-arrow.merge {
  height: 1px;
  border-width: 2px 0
}
.tl-arrow.merge.l {
  border-right: 3px solid #bbb
}
.tl-arrow.merge.r {
  border-left: 3px solid #bbb
}
.tl-line.merge {
  width: 1px
}
.tl-arrow.cherrypick {
  height: 1px;
  border-width: 2px 0;
}
.tl-arrow.cherrypick.l {
  border-right: 3px solid #bbb;
}
.tl-arrow.cherrypick.r {
  border-left: 3px solid #bbb;
}
.tl-line.cherrypick.h {
  width: 0px;
  border-top: 1px dashed #bbb;
  border-left: 0px dashed #bbb;
  background: rgba(255,255,255,0);
}
.tl-line.cherrypick.v {
  width: 0px;
  border-top: 0px dashed #bbb;
  border-left: 1px dashed #bbb;
  background: rgba(255,255,255,0);
}

/**************************************
 * User Edit
 */

/* layout definition for the capabilities box on the user edit detail page */
div.ueditCapBox {
Changes to skins/xekri/header.txt.
63
64
65
66
67
68
69
70

71
72
73
74
75
76
77
63
64
65
66
67
68
69

70
71
72
73
74
75
76
77







-
+







      set logourl [getLogoUrl $baseurl]
    } else {
      # Link logo to the top of the current repo
      set logourl $baseurl
    }
    </th1>
    <a href="$logourl">
      <img src="$logo_image_url" border="0" alt="$project_name">
      <img src="$logo_image_url" border="0" alt="$<project_name>">
    </a>
  </div>
  <div class="title">$<title></div>
  <div class="status"><nobr>
    <th1>
      if {[info exists login]} {
        puts "Logged in as $login"
Changes to src/accordion.js.
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



















-
+
+
+
+
+
+





+
+
+



+
+
+







** space required by the vertical scrollbar that may become necessary, causing
** additional horizontal shrinking and consequently more vertical growth than
** calculated. That's why setting `maxHeight' to `scrollHeight' is considered
** "good enough" only during animation, but cleared afterwards.
**
** https://fossil-scm.org/forum/forumpost/66d7075f40
** https://fossil-scm.org/home/timeline?r=accordion-fix
**
** To work around the missing support for `overflow-y: clip', this script uses a
** fallback and sets `overflow-y: hidden' during the accordion panel animations.
** That's because if `overflow-y: hidden' is set statically from the stylesheet,
** the shadow of the selected or current timeline entries in the context section
** of `/info' pages is still truncated on the right (which is strange, as that's
** not the "y" direction) in all major browsers. Otherwise, the stylesheet might
** define the fallback using the `@supports(…)' at-rule:
**
**   .accordion_panel {
**     overflow-y: hidden;
**   }
**   @supports(overflow-y: clip) {
**     .accordion_panel {
**       overflow-y: clip;
**     }
**   }
*/
var acc_svgdata = ["data:image/svg+xml,"+
  "%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E"+
  "%3Cpath style='fill:black;opacity:0' d='M16,16H0V0h16v16z'/%3E"+
  "%3Cpath style='fill:rgb(240,240,240)' d='M14,14H2V2h12v12z'/%3E"+
  "%3Cpath style='fill:rgb(64,64,64)' d='M13,13H3V3h10v10z'/%3E"+
  "%3Cpath style='fill:rgb(248,248,248)' d='M12,12H4V4h8v8z'/%3E"+
  "%3Cpath style='fill:rgb(80,128,208)' d='", "'/%3E%3C/svg%3E",
  "M5,7h2v-2h2v2h2v2h-2v2h-2v-2h-2z", "M11,9H5V7h6v6z"];
var a = document.getElementsByClassName("accordion");
for(var i=0; i<a.length; i++){
  var img = document.createElement("img");
  img.src = acc_svgdata[0]+acc_svgdata[2]+acc_svgdata[1];
  img.className = "accordion_btn accordion_btn_plus";
  a[i].insertBefore(img,a[i].firstChild);
  img = document.createElement("img");
  img.src = acc_svgdata[0]+acc_svgdata[3]+acc_svgdata[1];
  img.className = "accordion_btn accordion_btn_minus";
  a[i].insertBefore(img,a[i].firstChild);
  a[i].addEventListener("click",function(){
  a[i].addEventListener("click",function(evt){
    /* Ignore clicks to hyperlinks and other "click-responsive" HTML elements in
    ** ".accordion" headers (for which Fossil uses <DIV> elements that represent
    ** section headers). */
    var xClickyHTML = /^(?:A|AREA|BUTTON|INPUT|LABEL|SELECT|TEXTAREA|DETAILS)$/;
    if( xClickyHTML.test(evt.target.tagName) ) return;
    var x = this.nextElementSibling;
    if( this.classList.contains("accordion_closed") ){
      x.style.maxHeight = x.scrollHeight + "px";
      setTimeout(function(){
        x.style.maxHeight = "";
        if( !window.CSS || !window.CSS.supports("overflow: clip") ){
          x.style.overflowY = "";
        }
      },250); // default.css: .accordion_panel { transition-duration }
    }else{
      x.style.maxHeight = x.scrollHeight + "px";
      if( !window.CSS || !window.CSS.supports("overflow: clip") ){
        x.style.overflowY = "hidden";
      }
      setTimeout(function(){
        x.style.maxHeight = "0";
      },1);
    }
    this.classList.toggle("accordion_closed");
  });
}
Changes to src/add.c.
76
77
78
79
80
81
82
83

84
85
86
87
88
89
90
76
77
78
79
80
81
82

83
84
85
86
87
88
89
90







-
+







  /* Cached setting "manifest" */
  static int cachedManifest = -1;
  static int numManifests;

  if( cachedManifest == -1 ){
    int i;
    Blob repo;
    cachedManifest = db_get_manifest_setting();
    cachedManifest = db_get_manifest_setting(0);
    numManifests = 0;
    for(i=0; i<count(aManifestflags); i++){
      if( cachedManifest&aManifestflags[i].flg ) {
        azManifests[numManifests++] = aManifestflags[i].fname;
      }
    }
    blob_zero(&repo);
351
352
353
354
355
356
357
358

359
360
361
362
363
364
365
351
352
353
354
355
356
357

358
359
360
361
362
363
364
365







-
+







**
** The --ignore and --clean options are comma-separated 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
** When attempting to explicitly add files on the command line, and if those
** 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.
**
474
475
476
477
478
479
480
481

482
483
484
485
486
487
488
474
475
476
477
478
479
480

481
482
483
484
485
486
487
488







-
+







    }
    blob_reset(&fullName);
  }
  glob_free(pIgnore);
  glob_free(pClean);

  /** Check for Windows-reserved names and warn or exit, as
   ** appopriate. Note that the 'add' internal machinery already
   ** appropriate. Note that the 'add' internal machinery already
   ** _silently_ skips over any names for which
   ** file_is_reserved_name() returns true or which is in the
   ** fossil_reserved_name() list. We do not need to warn for those,
   ** as they're outright verboten. */
  if(db_exists("SELECT 1 FROM sfile WHERE win_reserved(pathname)")){
    int reservedCount = 0;
    Stmt q = empty_Stmt;
751
752
753
754
755
756
757
758
759

760
761
762
763
764
765
766
751
752
753
754
755
756
757


758
759
760
761
762
763
764
765







-
-
+







**     all files displayed using the "extras" command) are added as
**     if by the "[[add]]" command.
**
**  *  All files in the repository but missing from the check-out (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
** as a separate step.
** Note that this command does not "commit", as that is a separate step.
**
** Files and directories whose names begin with "." are ignored unless
** the --dotfiles option is used.
**
** The --ignore option overrides the "ignore-glob" setting, as do the
** --case-sensitive option with the "case-sensitive" setting and the
** --clean option with the "clean-glob" setting. See the documentation
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
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







-
+
+















+
+
+
+
+
+







**
** The original name of the file is zOrig.  The new filename is zNew.
*/
static void mv_one_file(
  int vid,
  const char *zOrig,
  const char *zNew,
  int dryRunFlag
  int dryRunFlag,
  int moveFiles
){
  int x = db_int(-1, "SELECT deleted FROM vfile WHERE pathname=%Q %s",
                         zNew, filename_collation());
  if( x>=0 ){
    if( x==0 ){
      if( !filenames_are_case_sensitive() && fossil_stricmp(zOrig,zNew)==0 ){
        /* Case change only */
      }else{
        fossil_fatal("cannot rename '%s' to '%s' since another file named '%s'"
                     " is currently under management", zOrig, zNew, zNew);
      }
    }else{
      fossil_fatal("cannot rename '%s' to '%s' since the delete of '%s' has "
                   "not yet been committed", zOrig, zNew, zNew);
    }
  }
  if( moveFiles ){
    if( file_size(zNew, ExtFILE) != -1 ){
      fossil_fatal("cannot rename '%s' to '%s' on disk since another file"
        " named '%s' already exists", zOrig, zNew, zNew);      
    }
  }
  fossil_print("RENAME %s %s\n", zOrig, zNew);
  if( !dryRunFlag ){
    db_multi_exec(
      "UPDATE vfile SET pathname='%q' WHERE pathname='%q' %s AND vid=%d",
      zNew, zOrig, filename_collation(), vid
    );
998
999
1000
1001
1002
1003
1004
1005
1006


1007
1008
1009
1010
1011
1012
1013
1004
1005
1006
1007
1008
1009
1010


1011
1012
1013
1014
1015
1016
1017
1018
1019







-
-
+
+







  }
}

/*
** COMMAND: mv
** COMMAND: rename*
**
** Usage: %fossil mv|rename OLDNAME NEWNAME
**    or: %fossil mv|rename OLDNAME... DIR
** Usage: %fossil mv|rename ?OPTIONS? OLDNAME NEWNAME
**    or: %fossil mv|rename ?OPTIONS? OLDNAME... DIR
**
** Move or rename one or more files or directories within the repository tree.
** You can either rename a file or directory or move it to another subdirectory.
**
** The 'mv' command does NOT normally rename or move the files on disk.
** This command merely records the fact that file names have changed so
** that appropriate notations can be made at the next [[commit]].
1134
1135
1136
1137
1138
1139
1140
1141

1142
1143
1144
1145
1146
1147
1148
1140
1141
1142
1143
1144
1145
1146

1147
1148
1149
1150
1151
1152
1153
1154







-
+







      db_finalize(&q);
    }
  }
  db_prepare(&q, "SELECT f, t FROM mv ORDER BY f");
  while( db_step(&q)==SQLITE_ROW ){
    const char *zFrom = db_column_text(&q, 0);
    const char *zTo = db_column_text(&q, 1);
    mv_one_file(vid, zFrom, zTo, dryRunFlag);
    mv_one_file(vid, zFrom, zTo, dryRunFlag, moveFiles);
    if( moveFiles ) add_file_to_move(zFrom, zTo);
  }
  db_finalize(&q);
  undo_reset();
  db_end_transaction(0);
  if( moveFiles ) process_files_to_move(dryRunFlag);
  fossil_free(zDest);
Changes to src/ajax.c.
305
306
307
308
309
310
311
312

313
314
315
316
317
318
319
305
306
307
308
309
310
311

312
313
314
315
316
317
318
319







-
+







  int ln = atoi(PD("ln","0"));
  int iframeHeight = atoi(PD("iframe_height","40"));
  Blob content = empty_blob;
  const char * zRenderMode = 0;

  ajax_get_fnci_args( &zFilename, 0 );

  if(!ajax_route_bootstrap(1,1)){
  if(!ajax_route_bootstrap(0,1)){
    return;
  }
  if(zFilename==0){
    /* The filename is only used for mimetype determination,
    ** so we can default it... */
    zFilename = "foo.txt";
  }
393
394
395
396
397
398
399
400

401
402
403
404
405
406
407
393
394
395
396
397
398
399

400
401
402
403
404
405
406
407







-
+







  AjaxRoute routeName = {0,0,0,0};
  const AjaxRoute * pRoute = 0;
  const AjaxRoute routes[] = {
  /* Keep these sorted by zName (for bsearch()) */
  {"preview-text", ajax_route_preview_text, 0, 1
   /* Note that this does not require write permissions in the repo.
   ** It should arguably require write permissions but doing means
   ** that /chat does not work without checkin permissions:
   ** that /chat does not work without check-in permissions:
   **
   ** https://fossil-scm.org/forum/forumpost/ed4a762b3a557898
   **
   ** This particular route is used by /fileedit and /chat, whereas
   ** /wikiedit uses a simpler wiki-specific route.
   */ }
  };
Changes to src/alerts.c.
49
50
51
52
53
54
55

56
57
58
59
60
61
62
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63







+







@ --     a - Announcements
@ --     c - Check-ins
@ --     f - Forum posts
@ --     k - ** Special: Unsubscribed using /oneclickunsub
@ --     n - New forum threads
@ --     r - Replies to my own forum posts
@ --     t - Ticket changes
@ --     u - Changes of users' permissions (admins only)
@ --     w - Wiki changes
@ --     x - Edits to forum posts
@ -- Probably different codes will be added in the future.  In the future
@ -- we might also add a separate table that allows subscribing to email
@ -- notifications for specific branches or tags or tickets.
@ --
@ CREATE TABLE repository.subscriber(
156
157
158
159
160
161
162

163
164
165
166
167
168
169
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171







+







*/
static int alert_process_deferred_triggers(void){
  if( db_table_exists("temp","deferred_chat_events")
   && db_table_exists("repository","chat")
  ){
    const char *zChatUser = db_get("chat-timeline-user", 0);
    if( zChatUser && zChatUser[0] ){
      chat_create_tables(); /* Make sure TEMP TRIGGERs for FTS exist */
      db_multi_exec(
        "INSERT INTO chat(mtime,lmtime,xfrom,xmsg)"
        " SELECT julianday(), "
               " strftime('%%Y-%%m-%%dT%%H:%%M:%%S','now','localtime'),"
               " %Q,"
               " chat_msg_from_event(type, objid, user, comment)\n"
        "   FROM deferred_chat_events;\n",
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
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







+
+
+













-
+


-
+












+
-
+
+








-

+
-
+
+







  if( g.perm.Admin ){
    if( fossil_strcmp(g.zPath,"subscribers") ){
      style_submenu_element("Subscribers","%R/subscribers");
    }
    if( fossil_strcmp(g.zPath,"subscribe") ){
      style_submenu_element("Add New Subscriber","%R/subscribe");
    }
    if( fossil_strcmp(g.zPath,"setup_notification") ){
      style_submenu_element("Notification Setup","%R/setup_notification");
    }
  }
}


/*
** WEBPAGE: setup_notification
**
** Administrative page for configuring and controlling email notification.
** Normally accessible via the /Admin/Notification menu.
*/
void setup_notification(void){
  static const char *const azSendMethods[] = {
    "off",   "Disabled",
    "pipe",  "Pipe to a command",
    "relay", "SMTP relay",
    "db",    "Store in a database",
    "dir",   "Store in a directory",
    "relay", "SMTP relay"
    "pipe",  "Pipe to a command",
  };
  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }
  db_begin_transaction();

  alert_submenu_common();
  style_submenu_element("Send Announcement","%R/announce");
  style_set_current_feature("alerts");
  style_header("Email Notification Setup");
  @ <form action="%R/setup_notification" method="post"><div>
  @ <h1>Status</h1>
  @ <h1>Status &ensp; <input type="submit"  name="submit" value="Refresh"></h1>
  @ </form>
  @ <table class="label-value">
  if( alert_enabled() ){
    stats_for_email();
  }else{
    @ <th>Disabled</th>
  }
  @ </table>
  @ <hr>
  @ <h1> Configuration </h1>
  @ <form action="%R/setup_notification" method="post"><div>
  @ <h1> Configuration </h1>
  @ <input type="submit"  name="submit" value="Apply Changes"><hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ <hr>
  login_insert_csrf_secret();

  entry_attribute("Canonical Server URL", 40, "email-url",
                   "eurl", "", 0);
  @ <p><b>Required.</b>
  @ This URL is used as the basename for hyperlinks included in
  @ email alert text.  Omit the trailing "/".
354
355
356
357
358
359
360
361


362
363
364
365
366
367
368
362
363
364
365
366
367
368

369
370
371
372
373
374
375
376
377







-
+
+







  @ can handle bounces. (Property: "email-self")</p>
  @ <hr>

  entry_attribute("List-ID", 40, "email-listid",
                   "elistid", "", 0);
  @ <p>
  @ If this is not an empty string, then it becomes the argument to
  @ a "List-ID:" header on all out-bound notification emails.
  @ a "List-ID:" header on all out-bound notification emails. A list ID
  @ is required for the generation of unsubscribe links in notifications.
  @ (Property: "email-listid")</p>
  @ <hr>

  entry_attribute("Repository Nickname", 16, "email-subname",
                   "enn", "", 0);
  @ <p><b>Required.</b>
  @ This is short name used to identifies the repository in the
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
425
426
427
428
429
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
435
436
437
438
439
440







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







-
-
-
-
-
-
-






-
-
-
-
-
-
-
-







  multiple_choice_attribute("Email Send Method", "email-send-method", "esm",
       "off", count(azSendMethods)/2, azSendMethods);
  @ <p>How to send email.  Requires auxiliary information from the fields
  @ that follow.  Hint: Use the <a href="%R/announce">/announce</a> page
  @ to send test message to debug this setting.
  @ (Property: "email-send-method")</p>
  alert_schema(1);
  entry_attribute("SMTP Relay Host", 60, "email-send-relayhost",
                   "esrh", "localhost", 0);
  @ <p>When the send method is "SMTP relay", each email message is
  @ transmitted via the SMTP protocol (rfc5321) to a "Mail Submission
  @ Agent" or "MSA" (rfc4409) at the hostname shown here.  Optionally
  @ append a colon and TCP port number (ex: smtp.example.com:587).
  @ The default TCP port number is 25.
  @ Usage Hint:  If Fossil is running inside of a chroot jail, then it might
  @ not be able to resolve hostnames.  Work around this by using a raw IP
  @ address or create a "/etc/hosts" file inside the chroot jail.
  @ (Property: "email-send-relayhost")</p>
  @ 
  entry_attribute("Store Emails In This Database", 60, "email-send-db",
                   "esdb", "", 0);
  @ <p>When the send method is "store in a database", each email message is
  @ stored in an SQLite database file with the name given here.
  @ (Property: "email-send-db")</p>
  entry_attribute("Pipe Email Text Into This Command", 60, "email-send-command",
                   "ecmd", "sendmail -ti", 0);
  @ <p>When the send method is "pipe to a command", this is the command
  @ that is run.  Email messages are piped into the standard input of this
  @ command.  The command is expected to extract the sender address,
  @ recipient addresses, and subject from the header of the piped email
  @ text.  (Property: "email-send-command")</p>

  entry_attribute("Store Emails In This Database", 60, "email-send-db",
                   "esdb", "", 0);
  @ <p>When the send method is "store in a database", each email message is
  @ stored in an SQLite database file with the name given here.
  @ (Property: "email-send-db")</p>

  entry_attribute("Store Emails In This Directory", 60, "email-send-dir",
                   "esdir", "", 0);
  @ <p>When the send method is "store in a directory", each email message is
  @ stored as a separate file in the directory shown here.
  @ (Property: "email-send-dir")</p>

  entry_attribute("SMTP Relay Host", 60, "email-send-relayhost",
                   "esrh", "", 0);
  @ <p>When the send method is "SMTP relay", each email message is
  @ transmitted via the SMTP protocol (rfc5321) to a "Mail Submission
  @ Agent" or "MSA" (rfc4409) at the hostname shown here.  Optionally
  @ append a colon and TCP port number (ex: smtp.example.com:587).
  @ The default TCP port number is 25.
  @ (Property: "email-send-relayhost")</p>
  @ <hr>

  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ </div></form>
  db_end_transaction(0);
  style_finish_page();
}
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
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







-
+
+
+





+

-
-
+
+
+
+
+
+
+







-
+







    }
  }else if( fossil_strcmp(p->zDest, "pipe")==0 ){
    emailerGetSetting(p, &p->zCmd, "email-send-command");
  }else if( fossil_strcmp(p->zDest, "dir")==0 ){
    emailerGetSetting(p, &p->zDir, "email-send-dir");
  }else if( fossil_strcmp(p->zDest, "blob")==0 ){
    blob_init(&p->out, 0, 0);
  }else if( fossil_strcmp(p->zDest, "relay")==0 ){
  }else if( fossil_strcmp(p->zDest, "relay")==0
         || fossil_strcmp(p->zDest, "debug-relay")==0
  ){
    const char *zRelay = 0;
    emailerGetSetting(p, &zRelay, "email-send-relayhost");
    if( zRelay ){
      u32 smtpFlags = SMTP_DIRECT;
      if( mFlags & ALERT_TRACE ) smtpFlags |= SMTP_TRACE_STDOUT;
      blob_init(&p->out, 0, 0);
      p->pSmtp = smtp_session_new(domain_of_addr(p->zFrom), zRelay,
                                  smtpFlags);
      smtp_client_startup(p->pSmtp);
                                  smtpFlags, 0);
      if( p->pSmtp==0 || p->pSmtp->zErr ){
        emailerError(p, "Could not start SMTP session: %s",
                        p->pSmtp ? p->pSmtp->zErr : "reason unknown");
      }else if( p->zDest[0]=='d' ){
        smtp_session_config(p->pSmtp, SMTP_TRACE_BLOB, &p->out);
      }
    }
  }
  return p;
}

/*
** Scan the header of the email message in pMsg looking for the
** (first) occurrance of zField.  Fill pValue with the content of
** (first) occurrence of zField.  Fill pValue with the content of
** that field.
**
** This routine initializes pValue.  Any prior content of pValue is
** discarded (leaked).
**
** Return non-zero on success.  Return 0 if no instance of the header
** is found.
689
690
691
692
693
694
695
696

697
698
699
700
701
702
703
708
709
710
711
712
713
714

715
716
717
718
719
720
721
722







-
+







}

/*
** Determine whether or not the input string is a valid email address.
** Only look at character up to but not including the first \000 or
** the first cTerm character, whichever comes first.
**
** Return the length of the email addresss string in bytes if the email
** Return the length of the email address string in bytes if the email
** address is valid.  If the email address is misformed, return 0.
*/
int email_address_is_valid(const char *z, char cTerm){
  int i;
  int nAt = 0;
  int nDot = 0;
  char c;
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
984
985
986
987
988
989
990



991
992
993
994
995
996
997







-
-
-







    blob_appendf(pOut, "From: %s <%s@%s>\r\n",
       zFromName, alert_mailbox_name(zFromName), alert_hostname(p->zFrom));
    blob_appendf(pOut, "Sender: <%s>\r\n", p->zFrom);
  }else{
    blob_appendf(pOut, "From: <%s>\r\n", p->zFrom);
  }
  blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0)));
  if( p->zListId  && p->zListId[0] ){
    blob_appendf(pOut, "List-Id: %s\r\n", p->zListId);
  }
  if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){
    /* Message-id format:  "<$(date)x$(random)@$(from-host)>" where $(date) is
    ** the current unix-time in hex, $(random) is a 64-bit random number,
    ** and $(from) is the domain part of the email-self setting. */
    sqlite3_randomness(sizeof(r1), &r1);
    r2 = time(0);
    blob_appendf(pOut, "Message-Id: <%llxx%016llx@%s>\r\n",
1013
1014
1015
1016
1017
1018
1019

1020
1021
1022









1023
1024
1025
1026
1027
1028
1029
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







+

-
-
+
+
+
+
+
+
+
+
+







  }else if( p->zDir ){
    char *zFile = file_time_tempname(p->zDir, ".email");
    blob_write_to_file(&all, zFile);
    fossil_free(zFile);
  }else if( p->pSmtp ){
    char **azTo = 0;
    int nTo = 0;
    SmtpSession *pSmtp = p->pSmtp;
    email_header_to(pHdr, &nTo, &azTo);
    if( nTo>0 ){
      smtp_send_msg(p->pSmtp, p->zFrom, nTo, (const char**)azTo,blob_str(&all));
    if( nTo>0 && !pSmtp->bFatal ){
      smtp_send_msg(pSmtp,p->zFrom,nTo,(const char**)azTo,blob_str(&all));
      if( pSmtp->zErr && !pSmtp->bFatal ){
        smtp_send_msg(pSmtp,p->zFrom,nTo,(const char**)azTo,blob_str(&all));
      }
      if( pSmtp->zErr ){
        fossil_errorlog("SMTP: (%s) %s", pSmtp->bFatal ? "fatal" : "retry",
                        pSmtp->zErr);
      }
      email_header_to_free(nTo, azTo);
    }
  }else if( strcmp(p->zDest, "stdout")==0 ){
    char **azTo = 0;
    int nTo = 0;
    int i;
    email_header_to(pHdr, &nTo, &azTo);
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
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
1169
1170
1171







+
+


-
+








-
+







** This is the email address for the repository.  Outbound emails add
** this email address as the "From:" field.
*/
/*
** SETTING: email-listid             width=40
** If this setting is not an empty string, then it becomes the argument to
** a "List-ID:" header that is added to all out-bound notification emails.
** A list ID is required for the generation of unsubscribe links in
** notifications.
*/
/*
** SETTING: email-send-relayhost      width=40 sensitive
** SETTING: email-send-relayhost      width=40 sensitive default=127.0.0.1
** This is the hostname and TCP port to which output email messages
** are sent when email-send-method is "relay".  There should be an
** SMTP server configured as a Mail Submission Agent listening on the
** designated host and port and all times.
*/


/*
** COMMAND: alerts*
** COMMAND: alerts*                     abbrv-subcom
**
** Usage: %fossil alerts SUBCOMMAND ARGS...
**
** Subcommands:
**
**    pending                 Show all pending alerts.  Useful for debugging.
**
1256
1257
1258
1259
1260
1261
1262
1263

1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278

1279
1280
1281
1282
1283
1284
1285
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







-
+














-
+







      }
      db_set(pSetting->name/*works-like:""*/, g.argv[4], isGlobal);
      g.argc = 3;
    }
    pSetting = setting_info(&nSetting);
    for(; nSetting>0; nSetting--, pSetting++ ){
      if( strncmp(pSetting->name,"email-",6)!=0 ) continue;
      print_setting(pSetting, 0);
      print_setting(pSetting, 0, 0);
    }
  }else
  if( strncmp(zCmd, "status", nCmd)==0 ){
    Stmt q;
    int iCutoff;
    int nSetting, n;
    static const char *zFmt = "%-29s %d\n";
    const Setting *pSetting = setting_info(&nSetting);
    db_open_config(1, 0);
    verify_all_options();
    if( g.argc!=3 ) usage("status");
    pSetting = setting_info(&nSetting);
    for(; nSetting>0; nSetting--, pSetting++ ){
      if( strncmp(pSetting->name,"email-",6)!=0 ) continue;
      print_setting(pSetting, 0);
      print_setting(pSetting, 0, 0);
    }
    n = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentSep");
    fossil_print(zFmt/*works-like:"%s%d"*/, "pending-alerts", n);
    n = db_int(0,"SELECT count(*) FROM pending_alert WHERE NOT sentDigest");
    fossil_print(zFmt/*works-like:"%s%d"*/, "pending-digest-alerts", n);
    db_prepare(&q,
       "SELECT"
1561
1562
1563
1564
1565
1566
1567

1568
1569
1570
1571
1572
1573
1574
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601







+







    if( suname && suname[0]==0 ) suname = 0;
    if( PB("sa") ) ssub[nsub++] = 'a';
    if( g.perm.Read && PB("sc") )    ssub[nsub++] = 'c';
    if( g.perm.RdForum && PB("sf") ) ssub[nsub++] = 'f';
    if( g.perm.RdForum && PB("sn") ) ssub[nsub++] = 'n';
    if( g.perm.RdForum && PB("sr") ) ssub[nsub++] = 'r';
    if( g.perm.RdTkt && PB("st") )   ssub[nsub++] = 't';
    if( g.perm.Admin && PB("su") )   ssub[nsub++] = 'u';
    if( g.perm.RdWiki && PB("sw") )  ssub[nsub++] = 'w';
    if( g.perm.RdForum && PB("sx") ) ssub[nsub++] = 'x';
    ssub[nsub] = 0;
    zCode = db_text(0,
      "INSERT INTO subscriber(semail,suname,"
      "  sverified,sdonotcall,sdigest,ssub,sctime,mtime,smip,lastContact)"
      "VALUES(%Q,%Q,%d,0,%d,%Q,now(),now(),%Q,now()/86400)"
1625
1626
1627
1628
1629
1630
1631

1632
1633
1634
1635
1636
1637
1638
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666







+







    ** subscription options to "on" */
    cgi_set_parameter_nocopy("sa","1",1);
    if( g.perm.Read )    cgi_set_parameter_nocopy("sc","1",1);
    if( g.perm.RdForum ) cgi_set_parameter_nocopy("sf","1",1);
    if( g.perm.RdForum ) cgi_set_parameter_nocopy("sn","1",1);
    if( g.perm.RdForum ) cgi_set_parameter_nocopy("sr","1",1);
    if( g.perm.RdTkt )   cgi_set_parameter_nocopy("st","1",1);
    if( g.perm.Admin )   cgi_set_parameter_nocopy("su","1",1);
    if( g.perm.RdWiki )  cgi_set_parameter_nocopy("sw","1",1);
  }
  @ <p>To receive email notifications for changes to this
  @ repository, fill out the form below and press the "Submit" button.</p>
  form_begin(0, "%R/subscribe");
  @ <table class="subscribe">
  @ <tr>
1697
1698
1699
1700
1701
1702
1703




1704
1705
1706
1707
1708
1709
1710
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742







+
+
+
+







    @  <label><input type="checkbox" name="st" %s(PCK("st"))> \
    @  Ticket changes</label><br>
  }
  if( g.perm.RdWiki ){
    @  <label><input type="checkbox" name="sw" %s(PCK("sw"))> \
    @  Wiki</label><br>
  }
  if( g.perm.Admin ){
    @  <label><input type="checkbox" name="su" %s(PCK("su"))> \
    @  User permission changes</label>
  }
  di = PB("di");
  @ </td></tr>
  @ <tr>
  @  <td class="form_label">Delivery:</td>
  @  <td><select size="1" name="di">
  @     <option value="0" %s(di?"":"selected")>Individual Emails</option>
  @     <option value="1" %s(di?"selected":"")>Daily Digest</option>
1818
1819
1820
1821
1822
1823
1824
1825

1826
1827
1828
1829
1830
1831
1832
1850
1851
1852
1853
1854
1855
1856

1857
1858
1859
1860
1861
1862
1863
1864







-
+







**         in length.) This uniquely identifies the subscriber without
**         revealing the complete subscriber code, and hence without
**         verifying the email address.
*/
void alert_page(void){
  const char *zName = 0;        /* Value of the name= query parameter */
  Stmt q;                       /* For querying the database */
  int sa, sc, sf, st, sw, sx;   /* Types of notifications requested */
  int sa, sc, sf, st, su, sw, sx; /* Types of notifications requested */
  int sn, sr;
  int sdigest = 0, sdonotcall = 0, sverified = 0;  /* Other fields */
  int isLogin;                  /* True if logged in as an individual */
  const char *ssub = 0;         /* Subscription flags */
  const char *semail = 0;       /* Email address */
  const char *smip;             /* */
  const char *suname = 0;       /* Corresponding user.login value */
1879
1880
1881
1882
1883
1884
1885

1886
1887
1888
1889
1890
1891
1892
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925







+







    semail = P("semail");
    if( PB("sa") )                   newSsub[nsub++] = 'a';
    if( g.perm.Read && PB("sc") )    newSsub[nsub++] = 'c';
    if( g.perm.RdForum && PB("sf") ) newSsub[nsub++] = 'f';
    if( g.perm.RdForum && PB("sn") ) newSsub[nsub++] = 'n';
    if( g.perm.RdForum && PB("sr") ) newSsub[nsub++] = 'r';
    if( g.perm.RdTkt && PB("st") )   newSsub[nsub++] = 't';
    if( g.perm.Admin && PB("su") )   newSsub[nsub++] = 'u';
    if( g.perm.RdWiki && PB("sw") )  newSsub[nsub++] = 'w';
    if( g.perm.RdForum && PB("sx") ) newSsub[nsub++] = 'x';
    newSsub[nsub] = 0;
    ssub = newSsub;
    blob_init(&update, "UPDATE subscriber SET", -1);
    blob_append_sql(&update,
        " sdonotcall=%d,"
1977
1978
1979
1980
1981
1982
1983

1984
1985
1986
1987
1988
1989
1990
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024







+







  }
  sa = strchr(ssub,'a')!=0;
  sc = strchr(ssub,'c')!=0;
  sf = strchr(ssub,'f')!=0;
  sn = strchr(ssub,'n')!=0;
  sr = strchr(ssub,'r')!=0;
  st = strchr(ssub,'t')!=0;
  su = strchr(ssub,'u')!=0;
  sw = strchr(ssub,'w')!=0;
  sx = strchr(ssub,'x')!=0;
  smip = db_column_text(&q, 5);
  mtime = db_column_text(&q, 7);
  sctime = db_column_text(&q, 8);
  if( !g.perm.Admin && !sverified ){
    if( nName==64 ){
2095
2096
2097
2098
2099
2100
2101
2102









2103
2104
2105
2106
2107
2108
2109
2129
2130
2131
2132
2133
2134
2135

2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151







-
+
+
+
+
+
+
+
+
+







  }
  if( g.perm.RdTkt ){
    @  <label><input type="checkbox" name="st" %s(st?"checked":"")>\
    @  Ticket changes</label><br>
  }
  if( g.perm.RdWiki ){
    @  <label><input type="checkbox" name="sw" %s(sw?"checked":"")>\
    @  Wiki</label>
    @  Wiki</label><br>
  }
  if( g.perm.Admin ){
    /* Corner-case bug: if an admin assigns 'u' to a non-admin, that
    ** subscription will get removed if the user later edits their
    ** subscriptions, as non-admins are not permitted to add that
    ** subscription. */
    @  <label><input type="checkbox" name="su" %s(su?"checked":"")>\
    @  User permission changes</label>
  }
  @ </td></tr>
  if( strchr(ssub,'k')!=0 ){
    @ <tr><td></td><td>&nbsp;&uarr;&nbsp;
    @ Note: User did a one-click unsubscribe</td></tr>
  }
  @ <tr>
2215
2216
2217
2218
2219
2220
2221
2222

2223
2224
2225
2226
2227
2228
2229
2257
2258
2259
2260
2261
2262
2263

2264
2265
2266
2267
2268
2269
2270
2271







-
+







** WEBPAGE: oneclickunsub
**
** Users visit this page to be delisted from email alerts.
**
** If a valid subscriber code is supplied in the name= query parameter,
** then that subscriber is delisted.
**
** Otherwise, If the users is logged in, then they are redirected
** Otherwise, if the users are logged in, then they are redirected
** to the /alerts page where they have an unsubscribe button.
**
** Non-logged-in users with no name= query parameter are invited to enter
** an email address to which will be sent the unsubscribe link that
** contains the correct subscriber code.
**
** The /unsubscribe page requires comfirmation.  The /oneclickunsub
2527
2528
2529
2530
2531
2532
2533

2534
2535
2536
2537
2538

2539
2540
2541
2542
2543
2544
2545
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580

2581
2582
2583
2584
2585
2586
2587
2588







+




-
+







**
**      c       A new check-in
**      f       An original forum post
**      n       New forum threads
**      r       Replies to my forum posts
**      x       An edit to a prior forum post
**      t       A new ticket or a change to an existing ticket
**      u       A user was added or received new permissions
**      w       A change to a wiki page
**      x       Edits to forum posts
*/
struct EmailEvent {
  int type;          /* 'c', 'f', 'n', 'r', 't', 'w', 'x' */
  int type;          /* 'c', 'f', 'n', 'r', 't', 'u', 'w', 'x' */
  int needMod;       /* Pending moderator approval */
  Blob hdr;          /* Header content, for forum entries */
  Blob txt;          /* Text description to appear in an alert */
  char *zFromName;   /* Human name of the sender */
  char *zPriors;     /* Upthread sender IDs for forum posts */
  EmailEvent *pNext; /* Next in chronological order */
};
2561
2562
2563
2564
2565
2566
2567
2568

2569
2570
2571
2572
2573
2574
2575
2604
2605
2606
2607
2608
2609
2610

2611
2612
2613
2614
2615
2616
2617
2618







-
+







}

/*
** Compute a string that is appropriate for the EmailEvent.zPriors field
** for a particular forum post.
**
** This string is an encode list of sender names and rids for all ancestors
** of the fpdi post - the post that fpid answer, the post that that parent
** of the fpid post - the post that fpid answers, the post that parent
** post answers, and so forth back up to the root post. Duplicates sender
** names are omitted.
**
** The EmailEvent.zPriors field is used to screen events for people who
** only want to see replies to their own posts or to specific posts.
*/
static char *alert_compute_priors(int fpid){
2910
2911
2912
2913
2914
2915
2916
2917
2918


2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936

2937
2938
2939
2940
2941
2942
2943
2953
2954
2955
2956
2957
2958
2959


2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987







-
-
+
+


















+







static void alert_renewal_msg(
  Blob *pHdr,            /* Write email header here */
  Blob *pBody,           /* Write email body here */
  const char *zCode,     /* The subscriber code */
  int lastContact,       /* Last contact (days since 1970) */
  const char *zEAddr,    /* Subscriber email address.  Send to this. */
  const char *zSub,      /* Subscription codes */
  const char *zRepoName, /* Name of the sending Fossil repostory */
  const char *zUrl       /* URL for the sending Fossil repostory */
  const char *zRepoName, /* Name of the sending Fossil repository */
  const char *zUrl       /* URL for the sending Fossil repository */
){
  blob_appendf(pHdr,"To: <%s>\r\n", zEAddr);
  blob_appendf(pHdr,"Subject: %s Subscription to %s expires soon\r\n",
    zRepoName, zUrl);
  blob_appendf(pBody,
    "\nTo renew your subscription, click the following link:\n"
    "\n  %s/renew/%s\n\n",
    zUrl, zCode
  );
  blob_appendf(pBody,
    "You are currently receiving email notification for the following events\n"
    "on the %s Fossil repository at %s:\n\n",
    zRepoName, zUrl
  );
  if( strchr(zSub, 'a') )  blob_appendf(pBody, "  *  Announcements\n");
  if( strchr(zSub, 'c') )  blob_appendf(pBody, "  *  Check-ins\n");
  if( strchr(zSub, 'f') )  blob_appendf(pBody, "  *  Forum posts\n");
  if( strchr(zSub, 't') )  blob_appendf(pBody, "  *  Ticket changes\n");
  if( strchr(zSub, 'u') )  blob_appendf(pBody, "  *  User permission elevation\n");
  if( strchr(zSub, 'w') )  blob_appendf(pBody, "  *  Wiki changes\n");
  blob_appendf(pBody, "\n"
    "If you take no action, your subscription will expire and you will be\n"
    "unsubscribed in about %d days.  To make other changes or to unsubscribe\n"
    "immediately, visit the following webpage:\n\n"
    "  %s/alerts/%s\n\n",
    ALERT_RENEWAL_MSG_FREQUENCY, zUrl, zCode
3142
3143
3144
3145
3146
3147
3148

3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164

3165
3166
3167
3168
3169
3170
3171
3172
3173
3174


3175
3176
3177
3178
3179
3180
3181
3182









3183
3184
3185
3186
3187
3188
3189
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222








3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238







+
















+










+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+







        char xType = '*';
        if( strpbrk(zCap,"as")==0 ){
          switch( p->type ){
            case 'x': case 'f':
            case 'n': case 'r':  xType = '5';  break;
            case 't':            xType = 'q';  break;
            case 'w':            xType = 'l';  break;
            /* Note: case 'u' is not handled here */
          }
          if( strchr(zCap,xType)==0 ) continue;
        }
      }else if( strchr(zCap,'s')!=0 || strchr(zCap,'a')!=0 ){
        /* Setup and admin users can get any notification that does not
        ** require moderation */
      }else{
        /* Other users only see the alert if they have sufficient
        ** privilege to view the event itself */
        char xType = '*';
        switch( p->type ){
          case 'c':            xType = 'o';  break;
          case 'x': case 'f':
          case 'n': case 'r':  xType = '2';  break;
          case 't':            xType = 'r';  break;
          case 'w':            xType = 'j';  break;
          /* Note: case 'u' is not handled here */
        }
        if( strchr(zCap,xType)==0 ) continue;
      }
      if( blob_size(&p->hdr)>0 ){
        /* This alert should be sent as a separate email */
        Blob fhdr, fbody;
        blob_init(&fhdr, 0, 0);
        blob_appendf(&fhdr, "To: <%s>\r\n", zEmail);
        blob_append(&fhdr, blob_buffer(&p->hdr), blob_size(&p->hdr));
        blob_init(&fbody, blob_buffer(&p->txt), blob_size(&p->txt));
        if( pSender->zListId  && pSender->zListId[0] ){
           blob_appendf(&fhdr, "List-Id: %s\r\n", pSender->zListId);
        blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
                     zUrl, zCode);
        blob_appendf(&fhdr,
                   "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
        blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
           zUrl, zCode);
        /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n",
        **   zUrl, zCode); */
           blob_appendf(&fhdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
                        zUrl, zCode);
           blob_appendf(&fhdr,
                       "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
           blob_appendf(&fbody, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
                        zUrl, zCode);
           /* blob_appendf(&fbody, "Subscription settings: %s/alerts/%s\n",
           **   zUrl, zCode); */
        }
        alert_send(pSender,&fhdr,&fbody,p->zFromName);
        nSent++;
        blob_reset(&fhdr);
        blob_reset(&fbody);
      }else{
        /* Events other than forum posts are gathered together into
        ** a single email message */
3198
3199
3200
3201
3202
3203
3204


3205
3206
3207
3208
3209







3210
3211
3212
3213
3214
3215
3216
3247
3248
3249
3250
3251
3252
3253
3254
3255





3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269







+
+
-
-
-
-
-
+
+
+
+
+
+
+







        }
        nHit++;
        blob_append(&body, "\n", 1);
        blob_append(&body, blob_buffer(&p->txt), blob_size(&p->txt));
      }
    }
    if( nHit==0 ) continue;
    if( pSender->zListId  && pSender->zListId[0] ){
      blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId);
    blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
         zUrl, zCode);
    blob_appendf(&hdr, "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
    blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
         zUrl, zCode);
      blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
           zUrl, zCode);
      blob_appendf(&hdr,
            "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
      blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
           zUrl, zCode);
    }
    alert_send(pSender,&hdr,&body,0);
    nSent++;
    blob_truncate(&hdr, 0);
    blob_truncate(&body, 0);
  }
  blob_reset(&hdr);
  blob_reset(&body);
3248
3249
3250
3251
3252
3253
3254

3255
3256
3257
3258

3259
3260
3261
3262









3263
3264
3265
3266
3267
3268
3269
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311

3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332







+



-
+




+
+
+
+
+
+
+
+
+







         " WHERE lastContact<=%d AND lastContact>%d"
         "   AND NOT sdonotcall"
         "   AND length(sdigest)>0",
         iNewWarn, iOldWarn
      );
      while( db_step(&q)==SQLITE_ROW ){
        Blob hdr, body;
        const char *zCode = db_column_text(&q,0);
        blob_init(&hdr, 0, 0);
        blob_init(&body, 0, 0);
        alert_renewal_msg(&hdr, &body,
           db_column_text(&q,0),
           zCode,
           db_column_int(&q,1),
           db_column_text(&q,2),
           db_column_text(&q,3),
           zRepoName, zUrl);
        if( pSender->zListId  && pSender->zListId[0] ){
           blob_appendf(&hdr, "List-Id: %s\r\n", pSender->zListId);
           blob_appendf(&hdr, "List-Unsubscribe: <%s/oneclickunsub/%s>\r\n",
                        zUrl, zCode);
           blob_appendf(&hdr,
                       "List-Unsubscribe-Post: List-Unsubscribe=One-Click\r\n");
           blob_appendf(&body, "\n-- \nUnsubscribe: %s/unsubscribe/%s\n",
                        zUrl, zCode);
        }
        alert_send(pSender,&hdr,&body,0);
        blob_reset(&hdr);
        blob_reset(&body);
      }
      db_finalize(&q);
      if( (flags & SENDALERT_PRESERVE)==0 ){
        if( iOldWarn>0 ){
3401
3402
3403
3404
3405
3406
3407
3408

3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419


3420

3421










3422
3423
3424
3425

3426
3427
3428
3429
3430
3431
3432
3464
3465
3466
3467
3468
3469
3470

3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484

3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499

3500
3501
3502
3503
3504
3505
3506
3507







-
+











+
+
-
+

+
+
+
+
+
+
+
+
+
+



-
+







    @ </td></tr></table></div>
  }
  @ </form>
  style_finish_page();
}

/*
** Send an annoucement message described by query parameter.
** Send an announcement message described by query parameter.
** Permission to do this has already been verified.
*/
static char *alert_send_announcement(void){
  AlertSender *pSender;
  char *zErr;
  const char *zTo = PT("to");
  char *zSubject = PT("subject");
  int bAll = PB("all");
  int bAA = PB("aa");
  int bMods = PB("mods");
  const char *zSub = db_get("email-subname", "[Fossil Repo]");
  const char *zName = P("name");    /* Debugging options */
  const char *zDest = 0;            /* How to send the announcement */
  int bTest2 = fossil_strcmp(P("name"),"test2")==0;
  int bTest = 0;
  Blob hdr, body;

  if( fossil_strcmp(zName, "test2")==0 ){
    bTest = 2;
    zDest = "blob";
  }else if( fossil_strcmp(zName, "test3")==0 ){
    bTest = 3;
    if( fossil_strcmp(db_get("email-send-method",""),"relay")==0 ){
      zDest = "debug-relay";
    }
  }
  blob_init(&body, 0, 0);
  blob_init(&hdr, 0, 0);
  blob_appendf(&body, "%s", PT("msg")/*safe-for-%s*/);
  pSender = alert_sender_new(bTest2 ? "blob" : 0, 0);
  pSender = alert_sender_new(zDest, 0);
  if( zTo[0] ){
    blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n", zTo, zSub, zSubject);
    alert_send(pSender, &hdr, &body, 0);
  }
  if( bAll || bAA || bMods ){
    Stmt q;
    int nUsed = blob_size(&body);
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467











3468
3469

3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490


3491
3492
3493
3494









3495
3496
3497
3498
3499
3500



3501
3502

3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513



3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531






3532
3533
3534
3535
3536
3537
3538
3531
3532
3533
3534
3535
3536
3537





3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570


3571
3572




3573
3574
3575
3576
3577
3578
3579
3580
3581

3582
3583
3584
3585
3586
3587
3588
3589
3590

3591
3592
3593
3594
3595
3596
3597
3598
3599
3600


3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634







-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+


+



















-
-
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
-





+
+
+

-
+









-
-
+
+
+


















+
+
+
+
+
+







        blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
           zURL, zCode);
      }
      alert_send(pSender, &hdr, &body, 0);
    }
    db_finalize(&q);
  }
  if( bTest2 ){
    /* If the URL is /announce/test2 instead of just /announce, then no
    ** email is actually sent.  Instead, the text of the email that would
    ** have been sent is displayed in the result window. */
    @ <pre style='border: 2px solid blue; padding: 1ex'>
  if( bTest && blob_size(&pSender->out) ){
    /* If the URL is "/announce/test2" then no email is actually sent.
    ** Instead, the text of the email that would have been sent is
    ** displayed in the result window.
    **
    ** If the URL is "/announce/test3" and the email-send-method is "relay"
    ** then the announcement is sent as it normally would be, but a
    ** transcript of the SMTP conversation with the MTA is shown here.
    */
    blob_trim(&pSender->out);
    @ <pre style='border: 2px solid blue; padding: 1ex;'>
    @ %h(blob_str(&pSender->out))
    @ </pre>
    blob_reset(&pSender->out);
  }
  zErr = pSender->zErr;
  pSender->zErr = 0;
  alert_sender_free(pSender);
  return zErr;
}


/*
** WEBPAGE: announce
**
** A web-form, available to users with the "Send-Announcement" or "A"
** capability, that allows one to send announcements to whomever
** has subscribed to receive announcements.  The administrator can
** also send a message to an arbitrary email address and/or to all
** subscribers regardless of whether or not they have elected to
** receive announcements.
*/
void announce_page(void){
  const char *zAction = "announce"
    /* Maintenance reminder: we need an explicit action=THIS_PAGE on the
  const char *zAction = "announce";
  const char *zName = PD("name","");
    ** form element to avoid that a URL arg of to=... passed to this
    ** page ends up overwriting the form-posted "to" value. This
    ** action value differs for the test1 request path.
    */;
  /*
  ** Debugging Notes:
  **
  **    /announce/test1           ->   Shows query parameter values
  **    /announce/test2           ->   Shows the formatted message but does
  **                                   not send it.
  **    /announce/test3           ->   Sends the message, but also shows
  **                                   the SMTP transcript.
  */

  login_check_credentials();
  if( !g.perm.Announce ){
    login_needed(0);
    return;
  }
  if( !g.perm.Setup ){
    zName = 0;   /* Disable debugging feature for non-admin users */
  }
  style_set_current_feature("alerts");
  if( fossil_strcmp(P("name"),"test1")==0 ){
  if( fossil_strcmp(zName,"test1")==0 ){
    /* Visit the /announce/test1 page to see the CGI variables */
    zAction = "announce/test1";
    @ <p style='border: 1px solid black; padding: 1ex;'>
    cgi_print_all(0, 0, 0);
    @ </p>
  }else if( P("submit")!=0 && cgi_csrf_safe(2) ){
    char *zErr = alert_send_announcement();
    style_header("Announcement Sent");
    if( zErr ){
      @ <h1>Internal Error</h1>
      @ <p>The following error was reported by the system:
      @ <h1>Error</h1>
      @ <p>The following error was reported by the
      @ announcement-sending subsystem:
      @ <blockquote><pre>
      @ %h(zErr)
      @ </pre></blockquote>
    }else{
      @ <p>The announcement has been sent.
      @ <a href="%h(PD("REQUEST_URI","/"))">Send another</a></p>
    }
    style_finish_page();
    return;
  } else if( !alert_enabled() ){
    style_header("Cannot Send Announcement");
    @ <p>Either you have no subscribers yet, or email alerts are not yet
    @ <a href="https://fossil-scm.org/fossil/doc/trunk/www/alerts.md">set up</a>
    @ for this repository.</p>
    return;
  }

  style_header("Send Announcement");
  alert_submenu_common();
  if( fossil_strcmp(zName,"test2")==0 ){
    zAction = "announce/test2";
  }else if( fossil_strcmp(zName,"test3")==0 ){
    zAction = "announce/test3";
  }
  @ <form method="POST" action="%R/%s(zAction)">
  login_insert_csrf_secret();
  @ <table class="subscribe">
  if( g.perm.Admin ){
    int aa = PB("aa");
    int all = PB("all");
    int aMod = PB("mods");
3561
3562
3563
3564
3565
3566
3567
3568

3569
3570
3571
3572
3573
3574
3575













3576
3577
3657
3658
3659
3660
3661
3662
3663

3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686







-
+







+
+
+
+
+
+
+
+
+
+
+
+
+


  @ <tr>
  @  <td class="form_label">Message:</td>
  @  <td><textarea name="msg" cols="80" rows="10" wrap="virtual">\
  @ %h(PT("msg"))</textarea>
  @ </tr>
  @ <tr>
  @   <td></td>
  if( fossil_strcmp(P("name"),"test2")==0 ){
  if( fossil_strcmp(zName,"test2")==0 ){
    @   <td><input type="submit" name="submit" value="Dry Run">
  }else{
    @   <td><input type="submit" name="submit" value="Send Message">
  }
  @ </tr>
  @ </table>
  @ </form>
  if( g.perm.Setup ){
    @ <hr>
    @ <p>Trouble-shooting Options:</p>
    @ <ol>
    @ <li> <a href="%R/announce">Normal Processing</a>
    @ <li> Only <a href="%R/announce/test1">show POST parameters</a>
    @      - Do not send the announcement.
    @ <li> <a href="%R/announce/test2">Show the email text</a> but do
    @      not actually send it.
    @ <li> Send the message and also <a href="%R/announce/test3">show the
    @      SMTP traffic</a> when using "relay" mode.
    @ </ol>
  }
  style_finish_page();
}
Changes to src/allrepo.c.
48
49
50
51
52
53
54
55
56
57

58
59
60
61
62
63
64
48
49
50
51
52
53
54

55

56
57
58
59
60
61
62
63







-

-
+







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
** COMMAND: all               abbrv-subcom
**
** Usage: %fossil all SUBCOMMAND ...
**
** 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.
**
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
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







-
+


+




-
+

+
+










+







**
** 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:
**   --dry-run         If given, display instead of run actions
**   --dry-run         Just display commands that would have run
**   --showfile        Show the repository or check-out being operated upon
**   --stop-on-error   Halt immediately if any subprocess fails
**   -s|--stop         Shorthand for "--stop-on-error"
*/
void all_cmd(void){
  Stmt q;
  const char *zCmd;
  char *zSyscmd;
  char *zSyscmd = 0;
  Blob extra;
  int bHalted = 0;
  int rc = 0;
  int useCheckouts = 0;
  int quiet = 0;
  int dryRunFlag = 0;
  int showFile = find_option("showfile",0,0)!=0;
  int stopOnError;
  int nToDel = 0;
  int showLabel = 0;

  (void)find_option("dontstop",0,0);   /* Legacy.  Now the default */
  stopOnError = find_option("stop-on-error",0,0)!=0;
  if( find_option("stop","s",0)!=0 ) stopOnError = 1;
  dryRunFlag = find_option("dry-run","n",0)!=0;
  if( !dryRunFlag ){
    dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
  }

  if( g.argc<3 ){
    usage("SUBCOMMAND ...");
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
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







+













+
+







    }
  }else if( fossil_strcmp(zCmd, "repack")==0 ){
    zCmd = "repack";
  }else if( fossil_strcmp(zCmd, "set")==0
            || fossil_strcmp(zCmd, "setting")==0
            || fossil_strcmp(zCmd, "settings")==0 ){
    zCmd = "settings -R";
    collect_argument(&extra, "changed", 0);
    collect_argv(&extra, 3);
  }else if( fossil_strcmp(zCmd, "unset")==0 ){
    zCmd = "unset -R";
    collect_argv(&extra, 3);
  }else if( fossil_strcmp(zCmd, "fts-config")==0 ){
    zCmd = "fts-config -R";
    collect_argv(&extra, 3);
  }else if( fossil_strcmp(zCmd, "sync")==0 ){
    zCmd = "sync -autourl -R";
    collect_argument(&extra, "share-links",0);
    collect_argument(&extra, "verbose","v");
    collect_argument(&extra, "unversioned","u");
    collect_argument(&extra, "all",0);
    collect_argument(&extra, "quiet","q");
    collect_argument(&extra, "ping",0);
  }else if( fossil_strcmp(zCmd, "test-integrity")==0 ){
    collect_argument(&extra, "db-only", "d");
    collect_argument(&extra, "parse", 0);
    collect_argument(&extra, "quick", "q");
    zCmd = "test-integrity";
  }else if( fossil_strcmp(zCmd, "test-orphans")==0 ){
    zCmd = "test-orphans -R";
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
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







+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



-
+







-
+





-
-
+

-

+






+





+







  }else{
    fossil_fatal("\"all\" subcommand should be one of: "
      "add cache changes clean dbstat extras fts-config git ignore "
      "info list ls pull push rebuild remote "
      "server settings sync ui unset whatis");
  }
  verify_all_options();
  db_multi_exec(
  db_multi_exec("CREATE TEMP TABLE repolist(name,tag);");
     "CREATE TEMP TABLE repolist(\n"
     "  name TEXT, -- Filename\n"
     "  tag TEXT,  -- Key for the GLOBAL_CONFIG table entry\n"
     "  inode TEXT -- Unique identifier for this file\n"
     ");\n"

     /* The seenFile() table holds inode names for entries that have
     ** already been processed.  */
     "CREATE TEMP TABLE seenFile(x TEXT COLLATE nocase);\n"

     /* The toDel() table holds the "tag" for entries that need to be
     ** deleted because they are redundant or no longer exist */
     "CREATE TEMP TABLE toDel(x TEXT);\n"
  );
  sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
                          file_inode_sql_func, 0, 0);
  if( useCheckouts ){
    db_multi_exec(
       "INSERT INTO repolist "
       "SELECT DISTINCT substr(name, 7), name COLLATE nocase"
       "SELECT substr(name, 7), name, inode(substr(name,7))"
       "  FROM global_config"
       " WHERE substr(name, 1, 6)=='ckout:'"
       " ORDER BY 1"
    );
  }else{
    db_multi_exec(
       "INSERT INTO repolist "
       "SELECT DISTINCT substr(name, 6), name COLLATE nocase"
       "SELECT substr(name, 6), name, inode(substr(name,6))"
       "  FROM global_config"
       " WHERE substr(name, 1, 5)=='repo:'"
       " ORDER BY 1"
    );
  }
  db_multi_exec("CREATE TEMP TABLE toDel(x TEXT)");
  db_prepare(&q, "SELECT name, tag FROM repolist ORDER BY 1");
  db_prepare(&q,"SELECT name, tag, inode FROM repolist ORDER BY 1");
  while( db_step(&q)==SQLITE_ROW ){
    int rc;
    const char *zFilename = db_column_text(&q, 0);
    const char *zInode = db_column_text(&q,2);
#if !USE_SEE
    if( sqlite3_strglob("*.efossil", zFilename)==0 ) continue;
#endif
    if( file_access(zFilename, F_OK)
     || !file_is_canonical(zFilename)
     || (useCheckouts && file_isdir(zFilename, ExtFILE)!=1)
     || db_exists("SELECT 1 FROM temp.seenFile where x=%Q", zInode)
    ){
      db_multi_exec("INSERT INTO toDel VALUES(%Q)", db_column_text(&q, 1));
      nToDel++;
      continue;
    }
    db_multi_exec("INSERT INTO seenFile(x) VALUES(%Q)", zInode);
    if( zCmd[0]=='l' ){
      fossil_print("%s\n", zFilename);
      continue;
    }else if( showFile ){
      fossil_print("%s: %s\n", useCheckouts ? "check-out" : "repository",
                   zFilename);
    }
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






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







-

-
+
+
+
+





+


















-
+
+
+
+
+
+
      fflush(stdout);
    }
    if( !quiet || dryRunFlag ){
      fossil_print("%s\n", zSyscmd);
      fflush(stdout);
    }
    rc = dryRunFlag ? 0 : fossil_system(zSyscmd);
    free(zSyscmd);
    if( rc ){
      if( stopOnError ) break;
      if( stopOnError ){
        bHalted = 1;
        break;
      }
      /* If there is an error, pause briefly, but do not stop.  The brief
      ** pause is so that if the prior command failed with Ctrl-C then there
      ** will be time to stop the whole thing with a second Ctrl-C. */
      sqlite3_sleep(330);
    }
    fossil_free(zSyscmd);
  }
  db_finalize(&q);

  blob_reset(&extra);

  /* If any repositories whose names appear in the ~/.fossil file could not
  ** be found, remove those names from the ~/.fossil file.
  */
  if( nToDel>0 ){
    const char *zSql = "DELETE FROM global_config WHERE name IN toDel";
    if( dryRunFlag ){
      fossil_print("%s\n", zSql);
    }else{
      db_unprotect(PROTECT_CONFIG);
      db_multi_exec("%s", zSql /*safe-for-%s*/ );
      db_protect_pop();
    }
  }
}

  if( stopOnError && bHalted ){
    fossil_fatal("STOPPED: non-zero result code (%d) from\nSTOPPED: %s",
                     rc, zSyscmd);
  }    
}
Changes to src/attach.c.
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
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







-
+
+



















+
+
+
+

+












+







}

/*
** Output HTML to show a list of attachments.
*/
void attachment_list(
  const char *zTarget,   /* Object that things are attached to */
  const char *zHeader    /* Header to display with attachments */
  const char *zHeader,   /* Header to display with attachments */
  int fHorizontalRule    /* Insert <hr> separator above header */
){
  int cnt = 0;
  Stmt q;
  db_prepare(&q,
     "SELECT datetime(mtime,toLocal()), filename, user,"
     "       (SELECT uuid FROM blob WHERE rid=attachid), src"
     "  FROM attachment"
     " WHERE isLatest AND src!='' AND target=%Q"
     " ORDER BY mtime DESC",
     zTarget
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zDate = db_column_text(&q, 0);
    const char *zFile = db_column_text(&q, 1);
    const char *zUser = db_column_text(&q, 2);
    const char *zUuid = db_column_text(&q, 3);
    const char *zSrc = db_column_text(&q, 4);
    const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
    if( cnt==0 ){
      @ <section class='attachlist'>
      if( fHorizontalRule ){
        @ <hr>
      }
      @ %s(zHeader)
      @ <ul>
    }
    cnt++;
    @ <li>
    @ %z(href("%R/artifact/%!S",zSrc))%h(zFile)</a>
    @ [<a href="%R/attachdownload/%t(zFile)?page=%t(zTarget)&file=%t(zFile)">download</a>]
    @ added by %h(zDispUser) on
    hyperlink_to_date(zDate, ".");
    @ [%z(href("%R/ainfo/%!S",zUuid))details</a>]
    @ </li>
  }
  if( cnt ){
    @ </ul>
    @ </section>
  }
  db_finalize(&q);

}

/*
** COMMAND: attachment*
Changes to src/backlink.c.
336
337
338
339
340
341
342
343

344
345
346
347
348
349
350
336
337
338
339
340
341
342

343
344
345
346
347
348
349
350







-
+







  markdown(&out, &in, &html_renderer);
  blob_reset(&out);
  blob_reset(&in);
}

/*
** Transform mimetype string into an integer code.
** NOTE: In the sake of compatability empty string is parsed as MT_UNKNOWN;
** NOTE: In the sake of compatibility empty string is parsed as MT_UNKNOWN;
**       it is yet unclear whether it can safely be changed to MT_NONE.
*/
int parse_mimetype(const char* zMimetype){
  if( zMimetype==0 ) return MT_NONE;
  if( strstr(zMimetype,"wiki")!=0 )     return MT_WIKI;
  if( strstr(zMimetype,"markdown")!=0 ) return MT_MARKDOWN;
  return MT_UNKNOWN;
429
430
431
432
433
434
435
436

437
438

439
440
441
442
443
444
445
429
430
431
432
433
434
435

436
437

438
439
440
441
442
443
444
445







-
+

-
+







  );
  backlink_extract(blob_str(&in),mimetype,srcid,srctype,mtime,0);
  blob_reset(&in);
}


/*
** COMMAND: test-wiki-relink
** COMMAND: test-relink-wiki
**
** Usage: %fossil test-wiki-relink  WIKI-PAGE-NAME
** Usage: %fossil test-relink-wiki  WIKI-PAGE-NAME
**
** Run the backlink_wiki_refresh() procedure on the wiki page
** named.  WIKI-PAGE-NAME can be a glob pattern or a prefix
** of the wiki page.
*/
void test_wiki_relink_cmd(void){
  Stmt q;
Changes to src/backoffice.c.
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
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







-
+














-
+







** Backoffice processes should die off after doing whatever work they need
** to do.  In this way, we avoid having lots of idle processes in the
** process table, doing nothing on rarely accessed repositories, and
** if the Fossil binary is updated on a system, the backoffice processes
** will restart using the new binary automatically.
**
** At any point in time there should be at most two backoffice processes.
** There is a main process that is doing the actually work, and there is
** There is a main process that is doing the actual work, and there is
** a second stand-by process that is waiting for the main process to finish
** and that will become the main process after a delay.
**
** After any successful web page reply, the backoffice_check_if_needed()
** routine is called.  That routine checks to see if both one or both of
** the backoffice processes are already running.  That routine remembers the
** status in a global variable.
**
** Later, after the repository database is closed, the
** backoffice_run_if_needed() routine is called.  If the prior call
** to backoffice_check_if_needed() indicated that backoffice processing
** might be required, the run_if_needed() attempts to kick off a backoffice
** process.
**
** All work performance by the backoffice is in the backoffice_work()
** All work performed by the backoffice is in the backoffice_work()
** routine.
*/
#if defined(_WIN32)
# if defined(_WIN32_WINNT)
#  undef _WIN32_WINNT
# endif
# define _WIN32_WINNT 0x501
483
484
485
486
487
488
489
490

491
492
493
494
495
496
497
483
484
485
486
487
488
489

490
491
492
493
494
495
496
497







-
+







  sqlite3_uint64 idSelf;
  int lastWarning = 0;
  int warningDelay = 30;
  static int once = 0;

  if( sqlite3_db_readonly(g.db, 0) ) return;
  if( db_is_protected(PROTECT_READONLY) ) return;
  g.zPhase = "backoffice";
  g.zPhase = "backoffice-pending";
  backoffice_error_check_one(&once);
  idSelf = backofficeProcessId();
  while(1){
    tmNow = time(0);
    db_begin_write();
    backofficeReadLease(&x);
    if( x.tmNext>=tmNow
508
509
510
511
512
513
514

515
516
517
518
519
520
521
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522







+







    }
    if( x.tmCurrent<tmNow && backofficeProcessDone(x.idCurrent) ){
      /* This process can start doing backoffice work immediately */
      x.idCurrent = idSelf;
      x.tmCurrent = tmNow + BKOFCE_LEASE_TIME;
      x.idNext = 0;
      x.tmNext = 0;
      g.zPhase = "backoffice-work";
      backofficeWriteLease(&x);
      db_end_transaction(0);
      backofficeTrace("/***** Begin Backoffice Processing %d *****/\n",
                      GETPID());
      backoffice_work();
      break;
    }
541
542
543
544
545
546
547


548

549
550


551
552
553
554
555
556
557
542
543
544
545
546
547
548
549
550

551
552

553
554
555
556
557
558
559
560
561







+
+
-
+

-
+
+







        /* The sleep was interrupted by a signal from another thread. */
        backofficeTrace("/***** Backoffice Interrupt %d *****/\n", GETPID());
        db_end_transaction(0);
        break;
      }
    }else{
      if( (sqlite3_uint64)(lastWarning+warningDelay) < tmNow ){
        sqlite3_int64 runningFor = BKOFCE_LEASE_TIME + tmNow - x.tmCurrent;
        if( warningDelay>=240 && runningFor<1800 ){
        fossil_warning(
          fossil_warning(
           "backoffice process %lld still running after %d seconds",
           x.idCurrent, (int)(BKOFCE_LEASE_TIME + tmNow - x.tmCurrent));
           x.idCurrent, runningFor);
        }
        lastWarning = tmNow;
        warningDelay *= 2;
      }
      if( backofficeSleep(1000) ){
        /* The sleep was interrupted by a signal from another thread. */
        backofficeTrace("/***** Backoffice Interrupt %d *****/\n", GETPID());
        db_end_transaction(0);
640
641
642
643
644
645
646

647
648

649
650

651
652
653
654
655
656
657
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664







+


+


+







    }
    blob_init(&log, 0, 0);
    backofficeBlob = &log;
    blob_appendf(&log, "%s %s", db_text(0, "SELECT datetime('now')"), zName);
  }

  /* Here is where the actual work of the backoffice happens */
  g.zPhase = "backoffice-alerts";
  nThis = alert_backoffice(0);
  if( nThis ){ backoffice_log("%d alerts", nThis); nTotal += nThis; }
  g.zPhase = "backoffice-hooks";
  nThis = hook_backoffice();
  if( nThis ){ backoffice_log("%d hooks", nThis); nTotal += nThis; }
  g.zPhase = "backoffice-close";

  /* Close the log */
  if( backofficeFILE ){
    if( nTotal || backofficeLogDetail ){
      if( nTotal==0 ) backoffice_log("no-op");
#if !defined(_WIN32)
      gettimeofday(&sEnd,0);
689
690
691
692
693
694
695
696

697
698
699
700
701
702
703
696
697
698
699
700
701
702

703
704
705
706
707
708
709
710







-
+







**
**    --min N                 When polling, invoke backoffice at least
**                            once every N seconds even if the repository
**                            never changes.  0 or negative means disable
**                            this feature.  Default: 3600 (once per hour).
**
**    --poll N                Repeat backoffice calls for repositories that
**                            change in appoximately N-second intervals.
**                            change in approximately N-second intervals.
**                            N less than 1 turns polling off (the default).
**                            Recommended polling interval: 60 seconds.
**
**    --trace                 Enable debugging output on stderr
**
** Options intended for internal use only which may change or be
** discontinued in future releases:
Changes to src/bisect.c.
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
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







-
+

-
+















-
+







** Find the shortest path between bad and good.
*/
void bisect_path(void){
  PathNode *p;
  bisect.bad = db_lget_int("bisect-bad", 0);
  bisect.good = db_lget_int("bisect-good", 0);
  if( bisect.good>0 && bisect.bad==0 ){
    path_shortest(bisect.good, bisect.good, 0, 0, 0);
    path_shortest(bisect.good, bisect.good, 0, 0, 0, 0);
  }else if( bisect.bad>0 && bisect.good==0 ){
    path_shortest(bisect.bad, bisect.bad, 0, 0, 0);
    path_shortest(bisect.bad, bisect.bad, 0, 0, 0, 0);
  }else if( bisect.bad==0 && bisect.good==0 ){
    fossil_fatal("neither \"good\" nor \"bad\" versions have been identified");
  }else{
    Bag skip;
    int bDirect = bisect_option("direct-only");
    char *zLog = db_lget("bisect-log","");
    Blob log, id;
    bag_init(&skip);
    blob_init(&log, zLog, -1);
    while( blob_token(&log, &id) ){
      if( blob_str(&id)[0]=='s' ){
        bag_insert(&skip, atoi(blob_str(&id)+1));
      }
    }
    blob_reset(&log);
    p = path_shortest(bisect.good, bisect.bad, bDirect, 0, &skip);
    p = path_shortest(bisect.good, bisect.bad, bDirect, 0, &skip, 0);
    bag_clear(&skip);
    if( p==0 ){
      char *zBad = db_text(0,"SELECT uuid FROM blob WHERE rid=%d",bisect.bad);
      char *zGood = db_text(0,"SELECT uuid FROM blob WHERE rid=%d",bisect.good);
      fossil_fatal("no path from good ([%S]) to bad ([%S]) or back",
                   zGood, zBad);
    }
169
170
171
172
173
174
175
176

177
178
179
180
181
182
183
169
170
171
172
173
174
175

176
177
178
179
180
181
182
183







-
+







  db_finalize(&s);
}

/*
** Append a new entry to the bisect log.  Update the bisect-good or
** bisect-bad values as appropriate.
**
** The bisect-log consists of a list of token.  Each token is an
** The bisect-log consists of a list of tokens.  Each token is an
** integer RID of a check-in.  The RID is negative for "bad" check-ins
** and positive for "good" check-ins.
*/
static void bisect_append_log(int rid){
  if( rid<0 ){
    if( db_lget_int("bisect-bad",0)==(-rid) ) return;
    db_lset_int("bisect-bad", -rid);
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


















-



+







** Append a new skip entry to the bisect log.
*/
static void bisect_append_skip(int rid){
  db_multi_exec(
     "UPDATE vvar SET value=value||' s%d' WHERE name='bisect-log'", rid
  );
}

/*
** Append a VALUES entry to the bilog table insert
*/
static void bisect_log_append(Blob *pSql,int iSeq,const char *zStat,int iRid){
  if( (iSeq%6)==3 ){
    blob_append_sql(pSql, ",\n  ");
  }else if( iSeq>1 ){
    blob_append_sql(pSql, ",");
  }
  if( zStat ){
    blob_append_sql(pSql, "(%d,%Q,%d)", iSeq, zStat, iRid);
  }else{
    blob_append_sql(pSql, "(NULL,NULL,%d)", iRid);
  }
}

/*
** Create a TEMP table named "bilog" that contains the complete history
** of the current bisect.
**
** If iCurrent>0 then it is the RID of the current check-out and is included
** in the history table.
**
** If zDesc is not NULL, then it is the bid= query parameter to /timeline
** that describes a bisect.  Use the information in zDesc rather than in
** the bisect-log variable.
**
** If bDetail is true, then also include information about every node
** in between the inner-most GOOD and BAD nodes.
*/
int bisect_create_bilog_table(int iCurrent, const char *zDesc, int bDetail){
  char *zLog;
  Blob log, id;
  Stmt q;
  int cnt = 0;
  int lastGood = -1;
  int lastBad = -1;
  Blob ins = BLOB_INITIALIZER;

  if( zDesc!=0 ){
    blob_init(&log, 0, 0);
    while( zDesc[0]=='y' || zDesc[0]=='n' || zDesc[0]=='s' ){
      int i;
      char c;
      int rid;
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
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







-
+
-


-
+


-
+
-



-
+
-


-
-
+



-
-


-
-
+
-
-
-



-
+

-
-
-
+
-
-




-
+
+







  db_multi_exec(
     "CREATE TEMP TABLE bilog("
     "  rid INTEGER PRIMARY KEY,"  /* Sequence of events */
     "  stat TEXT,"                /* Type of occurrence */
     "  seq INTEGER UNIQUE"        /* Check-in number */
     ");"
  );
  db_prepare(&q, "INSERT OR IGNORE INTO bilog(seq,stat,rid)"
  blob_append_sql(&ins, "INSERT OR IGNORE INTO bilog(seq,stat,rid) VALUES");
                 " VALUES(:seq,:stat,:rid)");
  while( blob_token(&log, &id) ){
    int rid;
    db_bind_int(&q, ":seq", ++cnt);
    cnt++;
    if( blob_str(&id)[0]=='s' ){
      rid = atoi(blob_str(&id)+1);
      db_bind_text(&q, ":stat", "SKIP");
      bisect_log_append(&ins, cnt, "SKIP", rid);
      db_bind_int(&q, ":rid", rid);
    }else{
      rid = atoi(blob_str(&id));
      if( rid>0 ){
        db_bind_text(&q, ":stat","GOOD");
        bisect_log_append(&ins, cnt, "GOOD", rid);
        db_bind_int(&q, ":rid", rid);
        lastGood = rid;
      }else{
        db_bind_text(&q, ":stat", "BAD");
        db_bind_int(&q, ":rid", -rid);
        bisect_log_append(&ins, cnt, "BAD", -rid);
        lastBad = -rid;
      }
    }
    db_step(&q);
    db_reset(&q);
  }
  if( iCurrent>0 ){
    db_bind_int(&q, ":seq", ++cnt);
    db_bind_text(&q, ":stat", "CURRENT");
    bisect_log_append(&ins, ++cnt, "CURRENT", iCurrent);
    db_bind_int(&q, ":rid", iCurrent);
    db_step(&q);
    db_reset(&q);
  }
  if( bDetail && lastGood>0 && lastBad>0 ){
    PathNode *p;
    p = path_shortest(lastGood, lastBad, bisect_option("direct-only"),0, 0);
    p = path_shortest(lastGood, lastBad, bisect_option("direct-only"),0, 0, 0);
    while( p ){
      db_bind_null(&q, ":seq");
      db_bind_null(&q, ":stat");
      db_bind_int(&q, ":rid", p->rid);
      bisect_log_append(&ins, ++cnt, 0, p->rid);
      db_step(&q);
      db_reset(&q);
      p = p->u.pTo;
    }
    path_reset();
  }
  db_finalize(&q);
  db_exec_sql(blob_sql_text(&ins));
  blob_reset(&ins);
  return 1;
}

/* Return a permalink description of a bisect.  Space is obtained from
** fossil_malloc() and should be freed by the caller.
**
** A bisect description consists of characters 'y' and 'n' and lowercase
331
332
333
334
335
336
337
338

339
340
341
342
343
344
345
334
335
336
337
338
339
340

341
342
343
344
345
346
347
348







-
+







        rid = -rid;
      }
    }
    zUuid = db_text(0,"SELECT lower(uuid) FROM blob WHERE rid=%d", rid);
    if( blob_size(&link)>0 ) blob_append(&link, "-", 1);
    blob_appendf(&link, "%c%.10s", cPrefix, zUuid);
  }
  zResult = mprintf("%s", blob_str(&link));
  zResult = fossil_strdup(blob_str(&link));
  blob_reset(&link);
  blob_reset(&log);
  blob_reset(&id);
  return zResult;
}

/*
541
542
543
544
545
546
547
548

549
550
551



552
553
554
555
556
557
558
544
545
546
547
548
549
550

551
552
553

554
555
556
557
558
559
560
561
562
563







-
+


-
+
+
+







**       be done, for example, because VERSION does not compile correctly
**       or is otherwise unsuitable to participate in this bisect.
**
** > fossil bisect vlist|ls|status ?-a|--all?
**
**       List the versions in between the inner-most "bad" and "good".
**
** > fossil bisect ui
** > fossil bisect ui ?HOST@USER:PATH?
**
**       Like "fossil ui" except start on a timeline that shows only the
**       check-ins that are part of the current bisect.
**       check-ins that are part of the current bisect.  If the optional
**       fourth term is added, then information is shown for the bisect that
**       occurred in the PATH directory by USER on remote machine HOST.
**
** > fossil bisect undo
**
**       Undo the most recent "good", "bad", or "skip" command.
*/
void bisect_cmd(void){
  int n;
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
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







+




+
-
-
-
+
+
+
+
+
+
+












    }else{
      usage("options ?NAME? ?VALUE?");
    }
  }else if( strncmp(zCmd, "reset", n)==0 ){
    bisect_reset();
  }else if( strcmp(zCmd, "ui")==0 ){
    char *newArgv[8];
    verify_all_options();
    newArgv[0] = g.argv[0];
    newArgv[1] = "ui";
    newArgv[2] = "--page";
    newArgv[3] = "timeline?bisect";
    if( g.argc==4 ){
    newArgv[4] = 0;
    g.argv = newArgv;
    g.argc = 4;
      newArgv[4] = g.argv[3];
      g.argc = 5;
    }else{
      g.argc = 4;
    }
    newArgv[g.argc] = 0;
    g.argv = newArgv;
    cmd_webserver();
  }else if( strncmp(zCmd, "vlist", n)==0
         || strncmp(zCmd, "ls", n)==0
         || strncmp(zCmd, "status", n)==0
  ){
    int fAll = find_option("all", "a", 0)!=0;
    bisect_list(!fAll);
  }else if( !foundCmd ){
usage:
    usage("bad|good|log|chart|next|options|reset|run|skip|status|ui|undo");
  }
}
Changes to src/blob.c.
663
664
665
666
667
668
669
670


671
672
673
674
675
676
677
663
664
665
666
667
668
669

670
671
672
673
674
675
676
677
678







-
+
+







void blob_dehttpize(Blob *pBlob){
  blob_materialize(pBlob);
  pBlob->nUsed = dehttpize(pBlob->aData);
}

/*
** Extract N bytes from blob pFrom and use it to initialize blob pTo.
** Return the actual number of bytes extracted.
** Return the actual number of bytes extracted.  The cursor position
** is advanced by the number of bytes extracted.
**
** After this call completes, pTo will be an ephemeral blob.
*/
int blob_extract(Blob *pFrom, int N, Blob *pTo){
  blob_is_init(pFrom);
  assert_blob_is_reset(pTo);
  if( pFrom->iCursor + N > pFrom->nUsed ){
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+









-
+




+
+
+
+
+
+
+
+
+
+
+







  pTo->nAlloc = N;
  pTo->aData = &pFrom->aData[pFrom->iCursor];
  pTo->iCursor = 0;
  pTo->xRealloc = blobReallocStatic;
  pFrom->iCursor += N;
  return N;
}

/*
** Extract N **lines** of text from blob pFrom beginning at the current
** cursor position and use that text to initialize blob pTo.  Unlike the
** blob_extract() routine,  the cursor position is unchanged.
**
** pTo is assumed to be uninitialized.
**
** After this call completes, pTo will be an ephemeral blob.
*/
int blob_extract_lines(Blob *pFrom, int N, Blob *pTo){
  int i;
  int mx;
  int iStart;
  int n;
  const char *z;

  blob_zero(pTo);
  z = pFrom->aData;
  i = pFrom->iCursor;
  mx = pFrom->nUsed;
  while( N>0 ){
    while( i<mx && z[i]!='\n' ){ i++; }
    if( i>=mx ) break;
    i++;
    N--;
  }
  iStart = pFrom->iCursor;
  n = blob_extract(pFrom, i-pFrom->iCursor, pTo);
  pFrom->iCursor = iStart;
  return n;
}

/*
** Return the number of lines of text in the blob.  If the last
** line is incomplete (if it does not have a \n at the end) then
** it still counts.
*/
int blob_linecount(Blob *p){
  int n = 0;
  int i;
  for(i=0; i<p->nUsed; i++){
    if( p->aData[i]=='\n' ) n++;
  }
  if( p->nUsed>0 && p->aData[p->nUsed-1]!='\n' ) n++;
  return n;
}

/*
** Rewind the cursor on a blob back to the beginning.
*/
void blob_rewind(Blob *p){
  p->iCursor = 0;
}

/*
** Truncate a blob back to zero length
** Truncate a blob to the specified length in bytes.
*/
void blob_truncate(Blob *p, int sz){
  if( sz>=0 && sz<(int)(p->nUsed) ) p->nUsed = sz;
}

/*
** Truncate a blob to the specified length in bytes. If truncation
** results in an incomplete UTF-8 sequence at the end, remove up
** to three more bytes back to the last complete UTF-8 sequence.
*/
void blob_truncate_utf8(Blob *p, int sz){
  if( sz>=0 && sz<(int)(p->nUsed) ){
    p->nUsed = utf8_nearest_codepoint(p->aData,sz);
  }
}

/*
** Seek the cursor in a blob to the indicated offset.
*/
int blob_seek(Blob *p, int offset, int whence){
  if( whence==BLOB_SEEK_SET ){
    p->iCursor = offset;
732
733
734
735
736
737
738
739

740
741
742
743
744
745
746
791
792
793
794
795
796
797

798
799
800
801
802
803
804
805







-
+







** pTo will include the terminating \n.  Return the number of bytes
** in the line including the \n at the end.  0 is returned at
** end-of-file.
**
** The cursor of pFrom is left pointing at the first byte past the
** \n that terminated the line.
**
** pTo will be an ephermeral blob.  If pFrom changes, it might alter
** pTo will be an ephemeral blob.  If pFrom changes, it might alter
** pTo as well.
*/
int blob_line(Blob *pFrom, Blob *pTo){
  char *aData = pFrom->aData;
  int n = pFrom->nUsed;
  int i = pFrom->iCursor;

775
776
777
778
779
780
781
782

783
784
785
786
787
788
789
834
835
836
837
838
839
840

841
842
843
844
845
846
847
848







-
+







**
** A token consists of one or more non-space characters.  Leading
** whitespace is ignored.
**
** The cursor of pFrom is left pointing at the first character past
** the end of the token.
**
** pTo will be an ephermeral blob.  If pFrom changes, it might alter
** pTo will be an ephemeral blob.  If pFrom changes, it might alter
** pTo as well.
*/
int blob_token(Blob *pFrom, Blob *pTo){
  char *aData = pFrom->aData;
  int n = pFrom->nUsed;
  int i = pFrom->iCursor;
  while( i<n && fossil_isspace(aData[i]) ){ i++; }
803
804
805
806
807
808
809
810

811
812
813
814
815
816
817
862
863
864
865
866
867
868

869
870
871
872
873
874
875
876







-
+







** An SQL token consists of one or more non-space characters.  If the
** first character is ' then the token is terminated by a matching '
** (ignoring double '') or by the end of the string
**
** The cursor of pFrom is left pointing at the first character past
** the end of the token.
**
** pTo will be an ephermeral blob.  If pFrom changes, it might alter
** pTo will be an ephemeral blob.  If pFrom changes, it might alter
** pTo as well.
*/
int blob_sqltoken(Blob *pFrom, Blob *pTo){
  char *aData = pFrom->aData;
  int n = pFrom->nUsed;
  int i = pFrom->iCursor;
  while( i<n && fossil_isspace(aData[i]) ){ i++; }
831
832
833
834
835
836
837
838

839
840
841
842
843
844
845
890
891
892
893
894
895
896

897
898
899
900
901
902
903
904







-
+







  while( i<n && fossil_isspace(aData[i]) ){ i++; }
  pFrom->iCursor = i;
  return pTo->nUsed;
}

/*
** Extract everything from the current cursor to the end of the blob
** into a new blob.  The new blob is an ephemerial reference to the
** into a new blob.  The new blob is an ephemeral reference to the
** original blob.  The cursor of the original blob is unchanged.
*/
int blob_tail(Blob *pFrom, Blob *pTo){
  int iCursor = pFrom->iCursor;
  blob_extract(pFrom, pFrom->nUsed-pFrom->iCursor, pTo);
  pFrom->iCursor = iCursor;
  return pTo->nUsed;
926
927
928
929
930
931
932
933

934
935
936
937
938
939
940
985
986
987
988
989
990
991

992
993
994
995
996
997
998
999







-
+







** This is used to test and debug the blob_strip_comment_lines() routine.
**
** Options:
**   -y|--side-by-side    Show diff of INPUTFILE and output side-by-side
**   -W|--width N         Width of lines in side-by-side diff
*/
void test_strip_comment_lines_cmd(void){
  Blob f, h;   /* unitialized */
  Blob f, h;   /* uninitialized */
  Blob out;
  DiffConfig dCfg;
  int sbs = 0;
  const char *z;
  int w = 0;

  memset(&dCfg, 0, sizeof(dCfg));
1756
1757
1758
1759
1760
1761
1762
1763

1764
1765
1766
1767
1768
1769
1770
1815
1816
1817
1818
1819
1820
1821

1822
1823
1824
1825
1826
1827
1828
1829







-
+







**    --hex HEX                 Skip the --hex flag and instead decode HEX
**                              into ascii.  This provides a way to insert
**                              unusual characters as an argument for testing.
**
**    --compare HEX ASCII       Verify that argument ASCII is identical to
**                              to decoded HEX.
**
**    --fuzz N                  Run N fuzz cases.  Each cases is a call
**    --fuzz N                  Run N fuzz cases.  Each case is a call
**                              to "fossil test-escaped-arg --compare HEX ARG"
**                              where HEX and ARG are the same argument.
**                              The argument is chosen at random.
*/
void test_escaped_arg_command(void){
  int i;
  Blob x;
1944
1945
1946
1947
1948
1949
1950
1951

1952
1953
1954
1955
1956
1957
1958
1959
1960
2003
2004
2005
2006
2007
2008
2009

2010
2011
2012
2013
2014
2015
2016
2017
2018
2019







-
+









    /* Make sure the blob contains two terminating 0-bytes */
    blob_append(pBlob, "\000\000", 3);
    zUtf8 = blob_str(pBlob) + bomSize;
    zUtf8 = fossil_unicode_to_utf8(zUtf8);
    blob_reset(pBlob);
    blob_set_dynamic(pBlob, zUtf8);
  }else if( useMbcs && invalid_utf8(pBlob) ){
#if defined(_WIN32) || defined(__CYGWIN__)
#if defined(_WIN32)
    zUtf8 = fossil_mbcs_to_utf8(blob_str(pBlob));
    blob_reset(pBlob);
    blob_append(pBlob, zUtf8, -1);
    fossil_mbcs_free(zUtf8);
#else
    blob_cp1252_to_utf8(pBlob);
#endif /* _WIN32 */
  }
}
Changes to src/branch.c.
16
17
18
19
20
21
22












23
24
25
26
27
28
29
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







+
+
+
+
+
+
+
+
+
+
+
+







*******************************************************************************
**
** This file contains code used to create new branches within a repository.
*/
#include "config.h"
#include "branch.h"
#include <assert.h>

/*
** Return the name of the main branch.  Cache the result.
**
** This is the current value of the "main-branch" setting, or its default
** value (historically, and as of 2025-10-28: "trunk") if not set.
*/
const char *db_main_branch(void){
  static char *zMainBranch = 0;
  if( zMainBranch==0 ) zMainBranch = db_get("main-branch", 0);
  return zMainBranch;
}

/*
** Return true if zBr is the branch name associated with check-in with
** blob.uuid value of zUuid
*/
int branch_includes_uuid(const char *zBr, const char *zUuid){
  return db_exists(
51
52
53
54
55
56
57
58
59
60

61
62
63
64
65
66
67
63
64
65
66
67
68
69



70
71
72
73
74
75
76
77







-
-
-
+







      " AND tagtype>0", TAG_BRANCH);
  db_bind_int(&q, "$rid", rid);
  if( db_step(&q)==SQLITE_ROW ){
    zBr = fossil_strdup(db_column_text(&q,0));
  }
  db_reset(&q);
  if( zBr==0 ){
    static char *zMain = 0;
    if( zMain==0 ) zMain = db_get("main-branch",0);
    zBr = fossil_strdup(zMain);
    zBr = fossil_strdup(db_main_branch());
  }
  return zBr;
}

/*
**  fossil branch new    NAME  BASIS ?OPTIONS?
**  argv0  argv1  argv2  argv3 argv4
220
221
222
223
224
225
226
227

228
229
230
231
232
233
234
230
231
232
233
234
235
236

237
238
239
240
241
242
243
244







-
+







/*
** Create a TEMP table named "tmp_brlist" with 7 columns:
**
**      name           Name of the branch
**      mtime          Time of last check-in on this branch
**      isclosed       True if the branch is closed
**      mergeto        Another branch this branch was merged into
**      nckin          Number of checkins on this branch
**      nckin          Number of check-ins on this branch
**      ckin           Hash of the last check-in on this branch
**      isprivate      True if the branch is private
**      bgclr          Background color for this branch
*/
static const char createBrlistQuery[] =
@ CREATE TEMP TABLE IF NOT EXISTS tmp_brlist AS
@ SELECT
546
547
548
549
550
551
552
553

554
555
556
557
558
559
560
556
557
558
559
560
561
562

563
564
565
566
567
568
569
570







-
+







  }
  branch_cmd_tag_finalize(fDryRun, fVerbose, zDateOvrd, zUserOvrd);
}

/*
** Implementation of (branch close|reopen) subcommands. nStartAtArg is
** the g.argv index to start reading branch/check-in names. The given
** checkins are closed if fClose is true, else their "closed" tag (if
** check-ins are closed if fClose is true, else their "closed" tag (if
** any) is cancelled. Fails fatally on error.
*/
static void branch_cmd_close(int nStartAtArg, int fClose){
  int argPos = nStartAtArg;    /* g.argv pos with first branch name */
  char * zUuid = 0;            /* Resolved branch UUID. */
  const int fVerbose = find_option("verbose","v",0)!=0;
  const int fDryRun = find_option("dry-run","n",0)!=0;
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
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







-
+













-
+



-
+





-
+



-
-
+
+












-
-
+
+











-
+


+
+
+
+
+
+










-
-
-
-
-
-







** COMMAND: branch
**
** Usage: %fossil branch SUBCOMMAND ... ?OPTIONS?
**
** Run various subcommands to manage branches of the open repository or
** of the repository identified by the -R or --repository option.
**
** >  fossil branch close|reopen ?OPTIONS? BRANCH-NAME ?...BRANCH-NAMES?
** > fossil branch close|reopen ?OPTIONS? BRANCH-NAME ?...BRANCH-NAMES?
**
**       Adds or cancels the "closed" tag to one or more branches.
**       It accepts arbitrary unambiguous symbolic names but
**       will only resolve check-in names and skips any which resolve
**       to non-leaf check-ins.
**
**       Options:
**         -n|--dry-run          Do not commit changes, but dump artifact
**                               to stdout
**         -v|--verbose          Output more information
**         --date-override DATE  DATE to use instead of 'now'
**         --user-override USER  USER to use instead of the current default
**
** >  fossil branch current
** > fossil branch current
**
**        Print the name of the branch for the current check-out
**
** >  fossil branch hide|unhide ?OPTIONS? BRANCH-NAME ?...BRANCH-NAMES?
** > fossil branch hide|unhide ?OPTIONS? BRANCH-NAME ?...BRANCH-NAMES?
**
**       Adds or cancels the "hidden" tag for the specified branches or
**       or check-in IDs. Accepts the same options as the close
**       subcommand.
**
** >  fossil branch info BRANCH-NAME
** > fossil branch info BRANCH-NAME
**
**        Print information about a branch
**
** >  fossil branch list|ls ?OPTIONS? ?GLOB?
** >  fossil branch lsh ?OPTIONS? ?LIMIT?
** > fossil branch list|ls ?OPTIONS? ?GLOB?
** > fossil branch lsh ?OPTIONS? ?LIMIT?
**
**        List all branches.
**
**        Options:
**          -a|--all         List all branches.  Default show only open branches
**          -c|--closed      List closed branches
**          -m|--merged      List branches merged into the current branch
**          -M|--unmerged    List branches not merged into the current branch
**          -p               List only private branches
**          -r               Reverse the sort order
**          -t               Show recently changed branches first
**          --self           List only branches where you participate
**          --username USER  List only branches where USER participate
**          --users N        List up to N users partipiating
**          --username USER  List only branches where USER participates
**          --users N        List up to N users participating
**
**        The current branch is marked with an asterisk.  Private branches are
**        marked with a hash sign.
**
**        If GLOB is given, show only branches matching the pattern.
**
**        The "lsh" variant of this subcommand shows recently changed branches,
**        and accepts an optional LIMIT argument (defaults to 5) to cap output,
**        but no GLOB argument.  All other options are supported, with -t being
**        an implied no-op.
**
** >  fossil branch new BRANCH-NAME BASIS ?OPTIONS?
** > fossil branch new BRANCH-NAME BASIS ?OPTIONS?
**
**        Create a new branch BRANCH-NAME off of check-in BASIS.
**
**        This command is available for people who want to create a branch
**        in advance.  But  the use of this command is discouraged.  The
**        preferred idiom in Fossil is to create new branches at the point
**        of need, using the "--branch NAME" option to the "fossil commit"
**        command.
**
**        Options:
**          --private             Branch is private (i.e., remains local)
**          --bgcolor COLOR       Use COLOR instead of automatic background
**          --nosign              Do not sign the manifest for the check-in
**                                that creates this branch
**          --nosync              Do not auto-sync prior to creating the branch
**          --date-override DATE  DATE to use instead of 'now'
**          --user-override USER  USER to use instead of the current default
**
**        DATE may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in
**        year-month-day form, it may be truncated, the "T" may be
**        replaced by a space, and it may also name a timezone offset
**        from UTC as "-HH:MM" (westward) or "+HH:MM" (eastward).
**        Either no timezone suffix or "Z" means UTC.
**
** Options:
**    -R|--repository REPO       Run commands on repository REPO
*/
void branch_cmd(void){
  int n;
  const char *zCmd = "list";
  db_find_and_open_repository(0, 0);
835
836
837
838
839
840
841

842
843
844
845
846
847

848

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







+






+

+







** Control jumps to this routine from brlist_page() (the /brlist handler)
** if there are no query parameters.
*/
static void new_brlist_page(void){
  Stmt q;
  double rNow;
  int show_colors = PB("colors");
  const char *zMainBranch;
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  style_set_current_feature("branch");
  style_header("Branches");
  style_adunit_config(ADUNIT_RIGHT_OK);
  style_submenu_checkbox("colors", "Use Branch Colors", 0, 0);

  login_anonymous_available();
  zMainBranch = db_main_branch();

  brlist_create_temp_table();
  db_prepare(&q, "SELECT * FROM tmp_brlist ORDER BY mtime DESC");
  rNow = db_double(0.0, "SELECT julianday('now')");
  @ <script id="brlist-data" type="application/json">\
  @ {"timelineUrl":"%R/timeline"}</script>
  @ <div class="brlist">
868
869
870
871
872
873
874

875
876


877
878
879
880
881
882
883
881
882
883
884
885
886
887
888


889
890
891
892
893
894
895
896
897







+
-
-
+
+







    const char *zMergeTo = db_column_text(&q, 3);
    int nCkin = db_column_int(&q, 4);
    const char *zLastCkin = db_column_text(&q, 5);
    const char *zBgClr = db_column_text(&q, 6);
    char *zAge = human_readable_age(rNow - rMtime);
    sqlite3_int64 iMtime = (sqlite3_int64)(rMtime*86400.0);
    if( zMergeTo && zMergeTo[0]==0 ) zMergeTo = 0;
    if( zBgClr ) zBgClr = reasonable_bg_color(zBgClr, 0);
    if( zBgClr == 0 ){
      if( zBranch==0 || strcmp(zBranch,"trunk")==0 ){
    if( zBgClr==0 ){
      if( zBranch==0 || strcmp(zBranch, zMainBranch)==0 ){
        zBgClr = 0;
      }else{
        zBgClr = hash_color(zBranch);
      }
    }
    if( zBgClr && zBgClr[0] && show_colors ){
      @ <tr style="background-color:%s(zBgClr)">
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












































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
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161







-
-
-
-
+
+
+
+
-
-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-





+
-
+



-
-
-
-
-
+










+



-
+




















-
-
-
+
+
+
+
+




+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
}

/*
** 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.
*/
static void brtimeline_extra(int rid){
  Stmt q;
  if( !g.perm.Hyperlink ) return;
  db_prepare(&q,
static void brtimeline_extra(
  Stmt *pQuery,               /* Current row of the timeline query */
  int tmFlags,                /* Flags to www_print_timeline() */
  const char *zThisUser,      /* Suppress links to this user */
    "SELECT substr(tagname,5) FROM tagxref, tag"
    " WHERE tagxref.rid=%d"
  const char *zThisTag        /* Suppress links to this tag */
    "   AND tagxref.tagid=tag.tagid"
    "   AND tagxref.tagtype>0"
    "   AND tag.tagname GLOB 'sym-*'",
    rid
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zTagName = db_column_text(&q, 0);
    @  %z(href("%R/timeline?r=%T",zTagName))[timeline]</a>
){
  int rid;
  int tmFlagsNew;
  char *zBrName;

  if( (tmFlags & TIMELINE_INLINE)!=0 ){
    tmFlagsNew = (tmFlags & ~TIMELINE_VIEWS) | TIMELINE_MODERN;
    cgi_printf("(");
  }else{
    tmFlagsNew = tmFlags;
  }
  timeline_extra(pQuery,tmFlagsNew,zThisUser,zThisTag);

  if( !g.perm.Hyperlink ) return;
  rid = db_column_int(pQuery,0);
  zBrName = branch_of_rid(rid);
  @  branch:&nbsp;<span class='timelineHash'>\
  @ %z(href("%R/timeline?r=%T",zBrName))%h(zBrName)</a></span>
  if( (tmFlags & TIMELINE_INLINE)!=0 ){
    cgi_printf(")");
  }
  db_finalize(&q);
}

/*
** WEBPAGE: brtimeline
**
** List the first check of every branch, starting with the most recent
** Show a timeline of all branches
** and going backwards in time.
**
** Query parameters:
**
**     ng            No graph
**     nohidden      Hide check-ins with "hidden" tag
**     onlyhidden    Show only check-ins with "hidden" tag
**     brbg          Background color by branch name
**     ubg           Background color by user name
**    ubg            Color the graph by user, not by branch.
*/
void brtimeline_page(void){
  Blob sql = empty_blob;
  Stmt q;
  int tmFlags;                            /* Timeline display flags */
  int fNoHidden = PB("nohidden")!=0;      /* The "nohidden" query parameter */
  int fOnlyHidden = PB("onlyhidden")!=0;  /* The "onlyhidden" query parameter */

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( robot_restrict("timelineX") ) return;

  style_set_current_feature("branch");
  style_header("Branches");
  style_submenu_element("List", "brlist");
  style_submenu_element("Branch List", "brlist");
  login_anonymous_available();
  timeline_ss_submenu();
  cgi_check_for_malice();
  @ <h2>The initial check-in for each branch:</h2>
  blob_append(&sql, timeline_query_for_www(), -1);
  blob_append_sql(&sql,
    "AND blob.rid IN (SELECT rid FROM tagxref"
    "                  WHERE tagtype>0 AND tagid=%d AND srcid!=0)", TAG_BRANCH);
  if( fNoHidden || fOnlyHidden ){
    const char* zUnaryOp = fNoHidden ? "NOT" : "";
    blob_append_sql(&sql,
      " AND %s EXISTS(SELECT 1 FROM tagxref"
      " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid)\n",
      zUnaryOp/*safe-for-%s*/, TAG_HIDDEN);
  }
  db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql));
  blob_reset(&sql);
  /* Always specify TIMELINE_DISJOINT, or graph_finish() may fail because of too
  ** many descenders to (off-screen) parents. */
  tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL;
  if( PB("ng")==0 ) tmFlags |= TIMELINE_GRAPH;
  if( PB("brbg")!=0 ) tmFlags |= TIMELINE_BRCOLOR;
  if( PB("ubg")!=0 ) tmFlags |= TIMELINE_UCOLOR;
  if( PB("ubg")!=0 ){
    tmFlags |= TIMELINE_UCOLOR;
  }else{
    tmFlags |= TIMELINE_BRCOLOR;
  }
  www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, brtimeline_extra);
  db_finalize(&q);
  style_finish_page();
}

/*
** Generate a multichoice submenu for the few recent active branches. zName is
** the query parameter used to select the current check-in. zCI is optional and
** represent the currently selected check-in, so if it is a check-in hash
** instead of a branch, it can be part of the multichoice menu.
*/
void generate_branch_submenu_multichoice(
    const char* zName,    /* Query parameter name */
    const char* zCI       /* Current check-in */
){
  Stmt q;
  const int brFlags = BRL_ORDERBY_MTIME | BRL_OPEN_ONLY;
  static const char *zBranchMenuList[32*2]; /* 2 per entries */
  const int nLimit = count(zBranchMenuList)/2;
  int i = 0;

  if( zName == 0 ) zName = "ci";

  branch_prepare_list_query(&q, brFlags, 0, nLimit, 0);
  zBranchMenuList[i++] = "";
  zBranchMenuList[i++] = "All Check-ins";

  if( zCI ){
    zCI = fossil_strdup(zCI);
    zBranchMenuList[i++] = zCI;
    zBranchMenuList[i++] = zCI;
  }
  /* If current check-in is not "tip", add it to the list */
  if( zCI==0 || strcmp(zCI, "tip") ){
    zBranchMenuList[i++] = "tip";
    zBranchMenuList[i++] = "tip";
  }
  while( i/2 < nLimit && db_step(&q)==SQLITE_ROW ){
    const char* zBr = fossil_strdup(db_column_text(&q, 0));
    /* zCI is already in the list, don't add it twice */
    if( zCI==0 || strcmp(zBr, zCI) ){
      zBranchMenuList[i++] = zBr;
      zBranchMenuList[i++] = zBr;
    }
  }
  db_finalize(&q);
  style_submenu_multichoice(zName, i/2, zBranchMenuList, 0);
}
Changes to src/browse.c.
125
126
127
128
129
130
131
132

133
134
135
136
137
138
139
125
126
127
128
129
130
131

132
133
134
135
136
137
138
139







-
+







** source tree.  This works similarly to /dir but with the following
** differences:
**
**    *   Links to files go to /doc (showing the file content directly,
**        depending on mimetype) rather than to /file (which always shows
**        the file embedded in a standard Fossil page frame).
**
**    *   The submenu and the page title is now show.  The page is plain.
**    *   The submenu and the page title is not shown.  The page is plain.
**
** The /docdir page is a shorthand for /dir with the "dx" query parameter.
**
** Query parameters:
**
**    ci=LABEL         Show only files in this check-in.  If omitted, the
**                     "trunk" directory is used.
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
169
170
171
172
173
174
175


176
177
178
179
180
181
182







-
-







  char *zPrefix;
  Stmt q;
  const char *zCI = P("ci");
  int rid = 0;
  char *zUuid = 0;
  Manifest *pM = 0;
  const char *zSubdirLink;
  int linkTrunk = 1;
  int linkTip = 1;
  HQuery sURI;
  int isSymbolicCI = 0;   /* ci= is symbolic name, not a hash prefix */
  int isBranchCI = 0;     /* True if ci= refers to a branch name */
  char *zHeader = 0;
  const char *zRegexp;    /* The re= query parameter */
  char *zMatch;           /* Extra title text describing the match */
  int bDocDir = PB("dx") || strncmp(g.zPath, "docdir", 6)==0;
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
190
191
192
193
194
195
196

197
198
199
200



201
202
203
204

205
206
207
208
209
210
211
212







-
+



-
-
-




-
+







  /* If the name= parameter is an empty string, make it a NULL pointer */
  if( zD && strlen(zD)==0 ){ zD = 0; }

  /* If a specific check-in is requested, fetch and parse it.  If the
  ** specific check-in does not exist, clear zCI.  zCI==0 will cause all
  ** files from all check-ins to be displayed.
  */
  if( bDocDir && zCI==0 ) zCI = "trunk";
  if( bDocDir && zCI==0 ) zCI = db_main_branch();
  if( zCI ){
    pM = manifest_get_by_name(zCI, &rid);
    if( pM ){
      int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
      linkTrunk = trunkRid && rid != trunkRid;
      linkTip = rid != symbolic_name_to_rid("tip", "ci");
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
      isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI))!=0);
      isBranchCI = branch_includes_uuid(zCI, zUuid);
      if( bDocDir ) zCI = mprintf("%S", zUuid);
      Th_Store("current_checkin", zCI);
      Th_StoreUnsafe("current_checkin", zCI);
    }else{
      zCI = 0;
    }
  }

  assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) );
  if( zD==0 ){
232
233
234
235
236
237
238



239
240
241
242
243
244
245
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243







+
+
+







    zHeader = mprintf("%z matching \"%s\"", zHeader, zRegexp);
    zMatch = mprintf(" matching \"%h\"", zRegexp);
  }else{
    zMatch = "";
  }
  style_header("%s", zHeader);
  fossil_free(zHeader);
  if( rid && zD==0 && zMatch[0]==0 && g.perm.Zip ){
    style_submenu_element("Download","%R/rchvdwnld/%!S",zUuid);
  }
  style_adunit_config(ADUNIT_RIGHT_OK);
  sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
                          pathelementFunc, 0, 0);
  url_initialize(&sURI, "dir");
  cgi_check_for_malice();
  cgi_query_parameters_to_url(&sURI);

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
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







-
-
-
-
-
-
-




-



+
+
+
+
+







    if( nD==0 && !bDocDir ){
      style_submenu_element("File Ages", "%R/fileage?name=%T", zCI);
    }
  }else{
    @ in any check-in</h2>
    zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
  }
  if( linkTrunk && !bDocDir ){
    style_submenu_element("Trunk", "%s",
                          url_render(&sURI, "ci", "trunk", 0, 0));
  }
  if( linkTip && !bDocDir ){
    style_submenu_element("Tip", "%s", url_render(&sURI, "ci", "tip", 0, 0));
  }
  if( zD && !bDocDir ){
    style_submenu_element("History","%R/timeline?chng=%T/*", zD);
  }
  if( !bDocDir ){
    style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0));
    style_submenu_element("Tree-View", "%s",
                          url_render(&sURI, "type", "tree", 0, 0));
  }

  if( !bDocDir ){
    /* Generate the Branch list submenu */
    generate_branch_submenu_multichoice("ci", zCI);
  }

  /* Compute the temporary table "localfiles" containing the names
  ** of all files and subdirectories in the zD[] directory.
  **
  ** Subdirectory names begin with "/".  This causes them to sort
  ** first and it also gives us an easy way to distinguish files
  ** from directories in the loop that follows.
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
698
699
700
701
702
703
704


705
706
707
708
709
710
711







-
-







  char *zUuid = 0;
  Blob dirname;
  Manifest *pM = 0;
  double rNow = 0;
  char *zNow = 0;
  int useMtime = atoi(PD("mtime","0"));
  int sortOrder = atoi(PD("sort",useMtime?"1":"0"));
  int linkTrunk = 1;       /* include link to "trunk" */
  int linkTip = 1;         /* include link to "tip" */
  const char *zRE;         /* the value for the re=REGEXP query parameter */
  const char *zObjType;    /* "files" by default or "folders" for "nofiles" */
  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 */
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
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







-
+














-
-
-






-
+









+
+
+







  }else{
    startExpanded = 0;
  }

  /* If a regular expression is specified, compile it */
  zRE = P("re");
  if( zRE ){
    re_compile(&pRE, zRE, 0);
    fossil_re_compile(&pRE, zRE, 0);
    zREx = mprintf("&re=%T", zRE);
  }
  cgi_check_for_malice();

  /* If the name= parameter is an empty string, make it a NULL pointer */
  if( zD && strlen(zD)==0 ){ zD = 0; }

  /* If a specific check-in is requested, fetch and parse it.  If the
  ** specific check-in does not exist, clear zCI.  zCI==0 will cause all
  ** files from all check-ins to be displayed.
  */
  if( zCI ){
    pM = manifest_get_by_name(zCI, &rid);
    if( pM ){
      int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
      linkTrunk = trunkRid && rid != trunkRid;
      linkTip = rid != symbolic_name_to_rid("tip", "ci");
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
      rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
      zNow = db_text("", "SELECT datetime(mtime,toLocal())"
                         " FROM event WHERE objid=%d", rid);
      isSymbolicCI = (sqlite3_strnicmp(zUuid, zCI, strlen(zCI)) != 0);
      isBranchCI = branch_includes_uuid(zCI, zUuid);
      Th_Store("current_checkin", zCI);
      Th_StoreUnsafe("current_checkin", zCI);
    }else{
      zCI = 0;
    }
  }
  if( zCI==0 ){
    rNow = db_double(0.0, "SELECT max(mtime) FROM event");
    zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event");
  }

  /* Generate the Branch list submenu */
  generate_branch_submenu_multichoice("ci", zCI);

  assert( isSymbolicCI==0 || (zCI!=0 && zCI[0]!=0) );
  if( zD==0 ){
    if( zCI ){
      zHeader = mprintf("Top-level Files of %s", zCI);
    }else{
      zHeader = mprintf("All Top-level Files");
    }
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
843
809
810
811
812
813
814
815

816
817
818
819



820
821



822
823
824


825
826
827
828
829
830
831







-




-
-
-
+
+
-
-
-
+
+

-
-







       "0", "Sort By Filename",
       "1", "Sort By Age",
       "2", "Sort By Size"
    };
    style_submenu_multichoice("sort", 3, sort_orders, 0);
  }
  if( zCI ){
    style_submenu_element("All", "%s", url_render(&sURI, "ci", 0, 0, 0));
    if( nD==0 && !showDirOnly ){
      style_submenu_element("File Ages", "%R/fileage?name=%T", zCI);
    }
  }
  if( linkTrunk ){
    style_submenu_element("Trunk", "%s",
                          url_render(&sURI, "ci", "trunk", 0, 0));
  style_submenu_element("Flat-View", "%s",
                        url_render(&sURI, "type", "flat", 0, 0));
  }
  if( linkTip ){
    style_submenu_element("Tip", "%s", url_render(&sURI, "ci", "tip", 0, 0));
  if( rid && zD==0 && zRE==0 && !showDirOnly && g.perm.Zip ){
    style_submenu_element("Download","%R/rchvdwnld/%!S", zUuid);
  }
  style_submenu_element("Flat-View", "%s",
                        url_render(&sURI, "type", "flat", 0, 0));

  /* Compute the file hierarchy.
  */
  if( zCI ){
    Stmt q;
    compute_fileage(rid, 0);
    db_prepare(&q,
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
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







-
+
+











-
-
+
+
+







*/
static const char zComputeFileAgeSetup[] =
@ CREATE TABLE IF NOT EXISTS temp.fileage(
@   fnid INTEGER PRIMARY KEY,
@   fid INTEGER,
@   mid INTEGER,
@   mtime DATETIME,
@   pathname TEXT
@   pathname TEXT,
@   uuid TEXT
@ );
@ CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;
;

static const char zComputeFileAgeRun[] =
@ WITH RECURSIVE
@  ckin(x) AS (VALUES(:ckin)
@              UNION
@              SELECT plink.pid
@                FROM ckin, plink
@               WHERE plink.cid=ckin.x)
@ INSERT OR IGNORE INTO fileage(fnid, fid, mid, mtime, pathname)
@   SELECT filename.fnid, mlink.fid, mlink.mid, event.mtime, filename.name
@ INSERT OR IGNORE INTO fileage(fnid, fid, mid, mtime, pathname, uuid)
@   SELECT filename.fnid, mlink.fid, mlink.mid, event.mtime, filename.name,
@          foci.uuid
@     FROM foci, filename, blob, mlink, event
@    WHERE foci.checkinID=:ckin
@      AND foci.filename GLOB :glob
@      AND filename.name=foci.filename
@      AND blob.uuid=foci.uuid
@      AND mlink.fid=blob.rid
@      AND mlink.fid!=mlink.pid
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
1185
1186
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







-












+
+
+
+







  const char *zNow;            /* Time of check-in */
  int isBranchCI;              /* name= is a branch name */
  int showId = PB("showid");
  Stmt q1, q2;
  double baseTime;
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( exclude_spiders(0) ) 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);
  }
  zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  isBranchCI = branch_includes_uuid(zName,zUuid);
  baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid);
  zNow = db_text("", "SELECT datetime(mtime,toLocal()) FROM event"
                     " WHERE objid=%d", rid);
  style_submenu_element("Tree-View", "%R/tree?ci=%T&mtime=1&type=tree", zName);

  /* Generate the Branch list submenu */
  generate_branch_submenu_multichoice("name", zName);

  style_header("File Ages");
  zGlob = P("glob");
  cgi_check_for_malice();
  compute_fileage(rid,zGlob);
  db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);");

  if( fossil_strcmp(zName,"tip")==0 ){
Changes to src/builtin.c.
50
51
52
53
54
55
56






57
58
59
60
61
62
63










64
65
66
67
68
69
70
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







+
+
+
+
+
+







+
+
+
+
+
+
+
+
+
+







    }
  }
  return -1;
}

/*
** Return a pointer to built-in content
**
** If the filename contains "-vNNNNNNNN" just before the final file
** suffix, where each N is a random digit, then omit that part of the
** filename before doing the lookup.  The extra -vNNNNNNNN was added
** to defeat overly aggressive caching by web browsers.  There must be
** at least 8 digits in NNNNNNNN but more than 8 are allowed.
*/
const unsigned char *builtin_file(const char *zFilename, int *piSize){
  int i = builtin_file_index(zFilename);
  if( i>=0 ){
    if( piSize ) *piSize = aBuiltinFiles[i].nByte;
    return aBuiltinFiles[i].pData;
  }else{
    char *zV = strstr(zFilename, "-v");
    if( zV!=0 ){
      for(i=0; fossil_isdigit(zV[i+2]); i++){}
      if( i>=8 && zV[i+2]=='.' ){
        char *zNew = mprintf("%.*s%s", (int)(zV-zFilename), zFilename, zV+i+2);
        const unsigned char *pRes = builtin_file(zNew, piSize);
        fossil_free(zNew);
        return pRes;
      }
    }
    if( piSize ) *piSize = 0;
    return 0;
  }
}
const char *builtin_text(const char *zFilename){
  return (char*)builtin_file(zFilename, 0);
}
717
718
719
720
721
722
723
724

725
726
727
728
729
730
731
733
734
735
736
737
738
739

740
741
742
743
744
745
746
747







-
+







                        ** REQUIRES an EXPLICIT trailing \0, including
                        ** the final one! */
  } fjs[] = {
  /* This list ordering isn't strictly important. */
  {"confirmer",      0, 0},
  {"copybutton",     0, "dom\0"},
  {"diff",           0, "dom\0fetch\0storage\0"
   /* maintenance note: "diff" needs "storage" for storing the the
   /* maintenance note: "diff" needs "storage" for storing the
   ** sbs-sync-scroll toggle. */},
  {"dom",            0, 0},
  {"fetch",          0, 0},
  {"numbered-lines", 0, "popupwidget\0copybutton\0"},
  {"pikchr",         0, "dom\0"},
  {"popupwidget",    0, "dom\0"},
  {"storage",        0, 0},
Changes to src/cache.c.
68
69
70
71
72
73
74
75

76
77
78
79
80
81
82
68
69
70
71
72
73
74

75
76
77
78
79
80
81
82







-
+







    rc = sqlite3_exec(db,
       "PRAGMA page_size=8192;"
       "CREATE TABLE IF NOT EXISTS blob(id INTEGER PRIMARY KEY, data BLOB);"
       "CREATE TABLE IF NOT EXISTS cache("
         "key TEXT PRIMARY KEY,"     /* Key used to access the cache */
         "id INT REFERENCES blob,"   /* The cache content */
         "sz INT,"                   /* Size of content in bytes */
         "tm INT,"                   /* Last access time (unix timestampe) */
         "tm INT,"                   /* Last access time (unix timestamp) */
         "nref INT"                  /* Number of uses */
       ");"
       "CREATE TRIGGER IF NOT EXISTS cacheDel AFTER DELETE ON cache BEGIN"
       "  DELETE FROM blob WHERE id=OLD.id;"
       "END;",
       0, 0, 0
    );
253
254
255
256
257
258
259
260

261
262
263
264
265
266
267
253
254
255
256
257
258
259

260
261
262
263
264
265
266
267







-
+







** database already exists.
*/
void cache_initialize(void){
  sqlite3_close(cacheOpen(1));
}

/*
** COMMAND: cache*
** COMMAND: cache*                    abbrv-subcom
**
** Usage: %fossil cache SUBCOMMAND
**
** Manage the cache used for potentially expensive web pages such as
** /zip and /tarball.   SUBCOMMAND can be:
**
**    clear        Remove all entries from the cache.
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
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
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
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







-
+

+
+
+
+






+
+
-
-
+
+
-
-
-
+
+
+







-



+
+
+
+

-
-
-
+
+
+

-
+



-
+


+
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-








/*
** WEBPAGE: cachestat
**
** Show information about the webpage cache.  Requires Setup privilege.
*/
void cache_page(void){
  sqlite3 *db;
  sqlite3 *db = 0;
  sqlite3_stmt *pStmt;
  int doInit;
  char *zDbName = cacheName();
  int nEntry = 0;
  int mxEntry = 0;
  char zBuf[100];

  login_check_credentials();
  if( !g.perm.Setup ){ login_needed(0); return; }
  style_set_current_feature("cache");
  style_header("Web Cache Status");
  style_submenu_element("Refresh","%R/cachestat");
  doInit = P("init")!=0 && cgi_csrf_safe(2);
  db = cacheOpen(0);
  if( db==0 ){
  db = cacheOpen(doInit);
  if( db!=0 ){
    @ The web-page cache is disabled for this repository
  }else{
    char *zDbName = cacheName();
    if( P("clear")!=0 && cgi_csrf_safe(2) ){
      sqlite3_exec(db, "DELETE FROM cache; DELETE FROM blob; VACUUM;",0,0,0);
    }
    cache_register_sizename(db);
    pStmt = cacheStmt(db,
         "SELECT key, sz, nRef, datetime(tm,'unixepoch')"
         "  FROM cache"
         " ORDER BY (tm + 3600*min(nRef,48)) DESC"
    );
    if( pStmt ){
      @ <ol>
      while( sqlite3_step(pStmt)==SQLITE_ROW ){
        const unsigned char *zName = sqlite3_column_text(pStmt,0);
        char *zHash = cache_hash_of_key((const char*)zName);
        if( nEntry==0 ){
          @ <h2>Current Cache Entries:</h2>
          @ <ol>
        }
        @ <li><p>%z(href("%R/cacheget?key=%T",zName))%h(zName)</a><br>
        @ size: %,lld(sqlite3_column_int64(pStmt,1))
        @ hit-count: %d(sqlite3_column_int(pStmt,2))
        @ last-access: %s(sqlite3_column_text(pStmt,3)) \
        @ size: %,lld(sqlite3_column_int64(pStmt,1)),
        @ hit-count: %d(sqlite3_column_int(pStmt,2)),
        @ last-access: %s(sqlite3_column_text(pStmt,3))Z \
        if( zHash ){
          @ %z(href("%R/timeline?c=%S",zHash))check-in</a>\
          @ &rarr; %z(href("%R/timeline?c=%S",zHash))check-in info</a>\
          fossil_free(zHash);
        }
        @ </p></li>

        nEntry++;
      }
      sqlite3_finalize(pStmt);
      if( nEntry ){
      @ </ol>
    }
    zDbName = cacheName();
        @ </ol>
      }
    }
  }
  @ <h2>About The Web-Cache</h2>
    bigSizeName(sizeof(zBuf), zBuf, file_size(zDbName, ExtFILE));
    @ <p>
    @ cache-file name: %h(zDbName)<br>
    @ cache-file size: %s(zBuf)<br>
    @ max-cache-entry: %d(db_get_int("max-cache-entry",10))
    @ </p>
    @ <p>
    @ Use the "<a href="%R/help?cmd=cache">fossil cache</a>" command
    @ on the command-line to create and configure the web-cache.
    @ </p>
    fossil_free(zDbName);
    sqlite3_close(db);
  @ <p>
  @ The web-cache is a separate database file that holds cached copies
  @ tarballs, ZIP archives, and other pages that are expensive to compute
  @ and are likely to be reused.
  @ <form method="post">
  login_insert_csrf_secret();
  @ <ul>
  if( db==0 ){
    @ <li> Web-cache is currently disabled.
    @ <input type="submit" name="init" value="Enable">
  }else{
    bigSizeName(sizeof(zBuf), zBuf, file_size(zDbName, ExtFILE));
    mxEntry = db_get_int("max-cache-entry",10);
    @ <li> Filename of the cache database: <b>%h(zDbName)</b>
    @ <li> Size of the cache database: %s(zBuf)
    @ <li> Maximum number of entries: %d(mxEntry)
    @ <li> Number of cache entries used: %d(nEntry)
    @ <li> Change the max-cache-entry setting on the
    @ <a href="%R/setup_settings">Settings</a> page to adjust the
    @ maximum number of entries in the cache.
    @ <li><input type="submit" name="clear" value="Clear the cache">
    @ <li> Disable the cache by manually deleting the cache database file.
  }
  @ </ul>
  @ </form>
  fossil_free(zDbName);
  if( db ) sqlite3_close(db);
  }
  style_finish_page();
}

/*
** WEBPAGE: cacheget
**
** Usage:  /cacheget?key=KEY
Changes to src/captcha.c.
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
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







-
+



















-
+







unsigned int captcha_seed(void){
  unsigned int x;
  sqlite3_randomness(sizeof(x), &x);
  x &= 0x7fffffff;
  return x;
}

/* The SQL that will rotate the the captcha-secret. */
/* The SQL that will rotate the captcha-secret. */
static const char captchaSecretRotationSql[] = 
@ SAVEPOINT rotate;
@ DELETE FROM config
@  WHERE name GLOB 'captcha-secret-*'
@    AND mtime<unixepoch('now','-6 hours');
@ UPDATE config
@    SET name=format('captcha-secret-%%d',substr(name,16)+1)
@  WHERE name GLOB 'captcha-secret-*';
@ UPDATE config
@    SET name='captcha-secret-1', mtime=unixepoch()
@  WHERE name='captcha-secret';
@ REPLACE INTO config(name,value,mtime)
@   VALUES('captcha-secret',%Q,unixepoch());
@ RELEASE rotate;
;


/*
** Create a new random captcha-secret.  Rotate the old one into
** the captcha-secret-N backups.  Purge captch-secret-N backups
** the captcha-secret-N backups.  Purge captcha-secret-N backups
** older than 6 hours.
**
** Do this on the current database and in all other databases of
** the same login group.
*/
void captcha_secret_rotate(void){
  char *zNew = db_text(0, "SELECT lower(hex(randomblob(20)))");
557
558
559
560
561
562
563
564

565
566
567
568
569
570
571
557
558
559
560
561
562
563

564
565
566
567
568
569
570
571







-
+







    sqlite3_free(zErrs);  /* Silently ignore errors on other repos */
  }
  fossil_free(zSql);
}

/*
** Return the value of the N-th more recent captcha-secret.  The
** most recent captch-secret is 0.  Others are prior captcha-secrets
** most recent captcha-secret is 0.  Others are prior captcha-secrets
** that have expired, but are retained for a limited period of time
** so that pending anonymous login cookies and/or captcha dialogs
** don't malfunction when the captcha-secret changes.
**
** Clients should start by using the 0-th captcha-secret.  Only if
** that one does not work should they advance to 1 and 2 and so forth,
** until this routine returns a NULL pointer.
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
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







-
+














+
+
+
+
+
+
+
+

















-









-
+


-
-
-
+
-







  const char *zPw = P("name");
  if( zPw==0 || zPw[0]==0 ){
    (void)exclude_spiders(1);
    @ <hr><p>The captcha is shown above.  Add a name=HEX query parameter
    @ to see how HEX would be rendered in the current captcha font.
    @ <h2>Debug/Testing Values:</h2>
    @ <ul>
    @ <li> g.isHuman = %d(g.isHuman)
    @ <li> g.isRobot = %d(g.isRobot)
    @ <li> g.zLogin = %h(g.zLogin)
    @ <li> login_cookie_welformed() = %d(login_cookie_wellformed())
    @ <li> captcha_is_correct(1) = %d(captcha_is_correct(1)).
    @ </ul>
    style_finish_page();
  }else{
    style_set_current_feature("test");
    style_header("Captcha Test");
    @ <pre class="captcha">
    @ %s(captcha_render(zPw))
    @ </pre>
    style_finish_page();
  }
}

/*
** WEBPAGE: honeypot
** This page is a honeypot for spiders and bots.
*/
void honeypot_page(void){
  (void)exclude_spiders(0);
}

/*
** Check to see if the current request is coming from an agent that 
** self-identifies as a spider.
**
** If the agent does not claim to be a spider or if the user has logged
** in (even as anonymous), then return 0 without doing anything.
**
** But if the user agent does self-identify as a spider and there is
** no login, offer a captcha challenge to allow the user agent to prove
** that he is human and return non-zero.
**
** If the bTest argument is non-zero, then show the captcha regardless of
** how the agent identifies.  This is used for testing only.
*/
int exclude_spiders(int bTest){
  if( !bTest ){
    if( g.isHuman ) return 0;  /* This user has already proven human */
    if( g.zLogin!=0 ) return 0;  /* Logged in.  Consider them human */
    if( login_cookie_wellformed() ){
      /* Logged into another member of the login group */
      return 0;
    }
  }

  /* This appears to be a spider.  Offer the captcha */
  style_set_current_feature("captcha");
  style_header("I think you are a robot");
  style_header("Captcha");
  style_submenu_enable(0);
  @ <form method='POST' action='%R/ityaar'>
  @ <p>You seem like a robot.
  @
  @ <p>If you are human, you can prove that by solving the captcha below,
  @ <h2>Prove that you are human:
  @ after which you will be allowed to proceed.
  if( bTest ){
    @ <input type="hidden" name="istest" value="1">
  }
  captcha_generate(3);
  @ </form>
  if( !bTest ){
    if( P("fossil-goto")==0 ){
828
829
830
831
832
833
834
835

836
837
838
839
840
841
842
832
833
834
835
836
837
838

839
840
841
842
843
844
845
846







-
+







        /* ^^^^--- Don't overwrite a valid login on another repo! */
        login_set_anon_cookie(0, 0);
      }
      cgi_append_header("X-Robot: 0\r\n");
    }
    login_redirect_to_g();
  }else{
    g.isHuman = 0;
    g.isRobot = 1;
    (void)exclude_spiders(bTest);
    if( bTest ){
      @ <hr><p>Wrong code.  Try again
      style_finish_page();
    }
  }
}
Changes to src/cgi.c.
11
12
13
14
15
16
17
18

19
20
21
22
23
24
25
11
12
13
14
15
16
17

18
19
20
21
22
23
24
25







-
+







**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file began as a set of C functions and procedures used intepret
** This file began as a set of C functions and procedures used to interpret
** CGI environment variables for Fossil web pages that were invoked by
** CGI.  That's where the file name comes from.  But over the years it
** has grown to incorporate lots of related functionality, including:
**
**   *  Interpreting CGI environment variables when Fossil is run as
**      CGI (the original purpose).
**
70
71
72
73
74
75
76

77
78
79
80
81
82
83
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84







+







# endif
# include <winsock2.h>
# include <ws2tcpip.h>
#else
# include <sys/socket.h>
# include <sys/un.h>
# include <netinet/in.h>
# include <netdb.h>
# include <arpa/inet.h>
# include <sys/times.h>
# include <sys/time.h>
# include <sys/wait.h>
# include <sys/select.h>
# include <errno.h>
#endif
101
102
103
104
105
106
107
108
109


110
111
112
113
114
115
116
102
103
104
105
106
107
108


109
110
111
112
113
114
115
116
117







-
-
+
+







#define P(x)          cgi_parameter((x),0)
#define PD(x,y)       cgi_parameter((x),(y))
#define PT(x)         cgi_parameter_trimmed((x),0)
#define PDT(x,y)      cgi_parameter_trimmed((x),(y))
#define PB(x)         cgi_parameter_boolean(x)
#define PCK(x)        cgi_parameter_checked(x,1)
#define PIF(x,y)      cgi_parameter_checked(x,y)
#define P_NoBot(x)    cgi_parameter_nosql((x),0)
#define PD_NoBot(x,y) cgi_parameter_nosql((x),(y))
#define P_NoBot(x)    cgi_parameter_no_attack((x),0)
#define PD_NoBot(x,y) cgi_parameter_no_attack((x),(y))

/*
** Shortcut for the cgi_printf() routine.  Instead of using the
**
**    @ ...
**
** notation provided by the translate.c utility, you can also
143
144
145
146
147
148
149
150

151
152
153
154
155
156
157
144
145
146
147
148
149
150

151
152
153
154
155
156
157
158







-
+







** Blob structures then output sequentially once everything has been
** built.
**
** Do not confuse the content header with the HTTP header. The content header
** is generated by downstream code.  The HTTP header is generated by the
** cgi_reply() routine below.
**
** The content header and contenty body are *approximately* the <head>
** The content header and content body are *approximately* the <head>
** element and the <body> elements for HTML replies.  However this is only
** approximate. The content header also includes parts of <body> that
** show the banner and menu bar at the top of each page.  Also note that
** not all replies are HTML, but there can still be separate header and
** body sections of the content.
**
** The cgi_destination() interface switches between the buffers.
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
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







-
+














+
+
-
+







  cgi_combine_header_and_body();
  return blob_buffer(&cgiContent[0]);
}

/*
** Additional information used to form the HTTP reply
*/
static const char *zContentType = "text/html";   /* Content type of the reply */
static const char *zReplyMimeType = "text/html"; /* Content type of the reply */
static const char *zReplyStatus = "OK";          /* Reply status description */
static int iReplyStatus = 200;               /* Reply status code */
static Blob extraHeader = BLOB_INITIALIZER;  /* Extra header text */
static int rangeStart = 0;                   /* Start of Range: */
static int rangeEnd = 0;                     /* End of Range: plus 1 */

/*
** Set the reply content type.
**
** The reply content type defaults to "text/html".  It only needs to be
** changed (by calling this routine) in the exceptional case where some
** other content type is being returned.
*/
void cgi_set_content_type(const char *zType){
  int i;
  for(i=0; zType[i]>='+' && zType[i]<='z'; i++){}
  zContentType = fossil_strdup(zType);
  zReplyMimeType = fossil_strndup(zType, i);
}

/*
** Erase any existing reply content.  Replace is with a pNewContent.
**
** This routine erases pNewContent.  In other words, it move pNewContent
** into the content buffer.
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
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







-
+

-
-
+
+














-
-
+
+

-
+







*/
static int is_gzippable(void){
  if( g.fNoHttpCompress ) return 0;
  if( strstr(PD("HTTP_ACCEPT_ENCODING", ""), "gzip")==0 ) return 0;
  /* Maintenance note: this oddball structure is intended to make
  ** adding new mimetypes to this list less of a performance hit than
  ** doing a strcmp/glob over a growing set of compressible types. */
  switch(zContentType ? *zContentType : 0){
  switch(zReplyMimeType ? *zReplyMimeType : 0){
    case (int)'a':
      if(0==fossil_strncmp("application/",zContentType,12)){
        const char * z = &zContentType[12];
      if(0==fossil_strncmp("application/",zReplyMimeType,12)){
        const char * z = &zReplyMimeType[12];
        switch(*z){
          case (int)'j':
            return fossil_strcmp("javascript", z)==0
                || fossil_strcmp("json", z)==0;
          case (int)'w': return fossil_strcmp("wasm", z)==0;
          case (int)'x':
            return fossil_strcmp("x-tcl", z)==0
                || fossil_strcmp("x-tar", z)==0;
          default:
            return sqlite3_strglob("*xml", z)==0;
        }
      }
      break;
    case (int)'i':
      return fossil_strcmp(zContentType, "image/svg+xml")==0
        || fossil_strcmp(zContentType, "image/vnd.microsoft.icon")==0;
      return fossil_strcmp(zReplyMimeType, "image/svg+xml")==0
        || fossil_strcmp(zReplyMimeType, "image/vnd.microsoft.icon")==0;
    case (int)'t':
      return fossil_strncmp(zContentType, "text/", 5)==0;
      return fossil_strncmp(zReplyMimeType, "text/", 5)==0;
  }
  return 0;
}


/*
** The following routines read or write content from/to the wire for
399
400
401
402
403
404
405


406
407
408
409
410
411
412
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417







+
+







  if( !g.httpUseSSL ){
    return fread(ptr, 1, nmemb, g.httpIn);
  }
#ifdef FOSSIL_ENABLE_SSL
  return ssl_read_server(g.httpSSLConn, ptr, nmemb, 1);
#else
  fossil_fatal("SSL not available");
  /* NOT REACHED */
  return 0;
#endif
}

/* Works like feof():
**
** Return true if end-of-input has been reached.
*/
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
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







-
+









-
-
+
+







    fflush(g.httpOut);
  }
}

/*
** Given a Content-Type value, returns a string suitable for appending
** to the Content-Type header for adding (or not) the "; charset=..."
** part. It returns an empty string for most types or if zContentType
** part. It returns an empty string for most types or if zReplyMimeType
** is NULL.
**
** See forum post f60dece061c364d1 for the discussions which lead to
** this. Previously we always appended the charset, but WASM loaders
** are pedantic and refuse to load any responses which have a
** charset. Also, adding a charset is not strictly appropriate for
** most types (and not required for many others which may ostensibly
** benefit from one, as detailed in that forum post).
*/
static const char * content_type_charset(const char *zContentType){
  if(0==fossil_strncmp(zContentType,"text/",5)){
static const char * content_type_charset(const char *zReplyMimeType){
  if(0==fossil_strncmp(zReplyMimeType,"text/",5)){
    return "; charset=utf-8";
  }
  return "";
}

/*
** Generate the reply to a web request.  The output might be an
497
498
499
500
501
502
503
504

505
506
507
508
509

510
511
512
513
514
515
516
502
503
504
505
506
507
508

509
510
511
512
513

514
515
516
517
518
519
520
521







-
+




-
+







    blob_appendf(&hdr, "X-UA-Compatible: IE=edge\r\n");
  }else{
    assert( rangeEnd==0 );
    blob_appendf(&hdr, "Status: %d %s\r\n", iReplyStatus, zReplyStatus);
  }
  if( etag_tag()[0]!=0
   && iReplyStatus==200
   && strcmp(zContentType,"text/html")!=0
   && strcmp(zReplyMimeType,"text/html")!=0
  ){
    /* Do not cache HTML replies as those will have been generated and
    ** will likely, therefore, contains a nonce and we want that nonce to
    ** be different every time. */
    blob_appendf(&hdr, "ETag: %s\r\n", etag_tag());
    blob_appendf(&hdr, "ETag: \"%s\"\r\n", etag_tag());
    blob_appendf(&hdr, "Cache-Control: max-age=%d\r\n", etag_maxage());
    if( etag_mtime()>0 ){
      blob_appendf(&hdr, "Last-Modified: %s\r\n",
              cgi_rfc822_datestamp(etag_mtime()));
    }
  }else if( g.isConst ){
    /* isConst means that the reply is guaranteed to be invariant, even
539
540
541
542
543
544
545
546
547
548



549
550
551
552
553
554
555
544
545
546
547
548
549
550



551
552
553
554
555
556
557
558
559
560







-
-
-
+
+
+







  ** highlighter scripts.
  **
  ** These headers are probably best added by the web server hosting fossil as
  ** a CGI script.
  */

  if( iReplyStatus!=304 ) {
    blob_appendf(&hdr, "Content-Type: %s%s\r\n", zContentType,
                 content_type_charset(zContentType));
    if( fossil_strcmp(zContentType,"application/x-fossil")==0 ){
    blob_appendf(&hdr, "Content-Type: %s%s\r\n", zReplyMimeType,
                 content_type_charset(zReplyMimeType));
    if( fossil_strcmp(zReplyMimeType,"application/x-fossil")==0 ){
      cgi_combine_header_and_body();
      blob_compress(&cgiContent[0], &cgiContent[0]);
    }

    if( is_gzippable() && iReplyStatus!=206 ){
      int i;
      gzip_begin(0);
635
636
637
638
639
640
641



642
643
644
645
646
647
648
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656







+
+
+







  cgi_reset_content();
  cgi_printf("<html>\n<p>Redirect to %h</p>\n</html>\n", zLocation);
  cgi_set_status(iStat, zStat);
  free(zLocation);
  cgi_reply();
  fossil_exit(0);
}
NORETURN void cgi_redirect_perm(const char *zURL){
  cgi_redirect_with_status(zURL, 301, "Moved Permanently");
}
NORETURN void cgi_redirect(const char *zURL){
  cgi_redirect_with_status(zURL, 302, "Moved Temporarily");
}
NORETURN void cgi_redirect_with_method(const char *zURL){
  cgi_redirect_with_status(zURL, 307, "Temporary Redirect");
}
NORETURN void cgi_redirectf(const char *zFormat, ...){
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
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







+
+
+
+

-
+

+

+



+
+
+
+
+

-
-
-
+
+
+
+
+
+
+
+
+
+
+
+







  }
  return zRef;
}


/*
** Return true if the current request is coming from the same origin.
**
** If the request comes from a different origin and bErrorLog is true, then
** put a warning message on the error log as this was a possible hack
** attempt.
*/
int cgi_same_origin(void){
int cgi_same_origin(int bErrorLog){
  const char *zRef;
  char *zToFree = 0;
  int nBase;
  int rc;
  if( g.zBaseURL==0 ) return 0;
  zRef = P("HTTP_REFERER");
  if( zRef==0 ) return 0;
  if( strchr(zRef,'%')!=0 ){
    zToFree = strdup(zRef);
    dehttpize(zToFree);
    zRef = zToFree;
  }
  nBase = (int)strlen(g.zBaseURL);
  if( fossil_strncmp(g.zBaseURL,zRef,nBase)!=0 ) return 0;
  if( zRef[nBase]!=0 && zRef[nBase]!='/' ) return 0;
  return 1;
  if( fossil_strncmp(g.zBaseURL,zRef,nBase)!=0 ){
    rc = 0;
  }else if( zRef[nBase]!=0 && zRef[nBase]!='/' ){
    rc = 0;
  }else{
    rc = 1;
  }
  if( rc==0 && bErrorLog && fossil_strcmp(P("REQUST_METHOD"),"POST")==0 ){
    fossil_errorlog("warning: POST from different origin");
  }
  fossil_free(zToFree);
  return rc;
}

/*
** Return true if the current CGI request is a POST request
*/
static int cgi_is_post_request(void){
  const char *zMethod = P("REQUEST_METHOD");
731
732
733
734
735
736
737
738

739
740
741
742
743
744
745
759
760
761
762
763
764
765

766
767
768
769
770
771
772
773







-
+







**     1:   Request comes from the same origin
**     2:   (1) plus it is a POST request
**     3:   (2) plus there is a valid "csrf" token in the request
*/
int cgi_csrf_safe(int securityLevel){
  if( g.okCsrf<0 ) return 0;
  if( g.okCsrf==0 ){
    if( !cgi_same_origin() ){
    if( !cgi_same_origin(1) ){
      g.okCsrf = -1;
    }else{
      g.okCsrf = 1;
      if( cgi_is_post_request() ){
        g.okCsrf = 2;
        if( fossil_strcmp(P("csrf"), g.zCsrfToken)==0 ){
          g.okCsrf = 3;
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
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
1006







-
-
+
+




+
+
+
+
+
+
+
+
+
+
+









-
+










-
+









+
+
+
+

-
+







  for(i=0; i<nUsedQP; i++){
    if( aParamQP[i].isQP && fossil_strcmp(aParamQP[i].zName,"name")!=0 ) cnt++;
  }
  return cnt;
}

/*
** Add an environment varaible value to the parameter set.  The zName
** portion is fixed but a copy is be made of zValue.
** Add an environment variable value to the parameter set.  The zName
** portion is fixed but a copy is made of zValue.
*/
void cgi_setenv(const char *zName, const char *zValue){
  cgi_set_parameter_nocopy(zName, fossil_strdup(zValue), 0);
}

/*
** Returns true if NUL-terminated z contains any non-NUL
** control characters (<0x20, 32d).
*/
static int contains_ctrl(const char *zIn){
  const unsigned char *z = (const unsigned char*)zIn;
  assert(z);
  for( ; *z>=0x20; ++z ){}
  return 0!=*z;
}

/*
** 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
** special character and therefore does not decode it.
**
** If NAME begins with another other than a lower-case letter then
** If NAME begins with something other than a lower-case letter then
** the entire NAME=VALUE term is ignored.  Hence:
**
**      *  cookies and query parameters that have uppercase names
**         are ignored.
**
**      *  it is impossible for a cookie or query parameter to
**         override the value of an environment variable since
**         environment variables always have uppercase names.
**
** 2018-03-29:  Also ignore the entry if NAME that contains any characters
** other than [a-zA-Z0-9_].  There are no known exploits involving unusual
** other than [-a-zA-Z0-9_].  There are no known exploits involving unusual
** names that contain characters outside that set, but it never hurts to
** be extra cautious when sanitizing inputs.
**
** Parameters are separated by the "terminator" character.  Whitespace
** before the NAME is ignored.
**
** The input string "z" is modified but no copies is made.  "z"
** should not be deallocated or changed again after this routine
** returns or it will corrupt the parameter table.
**
** If bPermitCtrl is false and the decoded value of any entry in z
** contains control characters (<0x20, 32d) then that key/value pair
** are skipped.
*/
static void add_param_list(char *z, int terminator){
static void add_param_list(char *z, int terminator, int bPermitCtrl){
  int isQP = terminator=='&';
  while( *z ){
    char *zName;
    char *zValue;
    while( fossil_isspace(*z) ){ z++; }
    zName = z;
    while( *z && *z!='=' && *z!=terminator ){ z++; }
972
973
974
975
976
977
978



979

980
981
982
983
984
985
986
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024

1025
1026
1027
1028
1029
1030
1031
1032







+
+
+
-
+







      }
      dehttpize(zValue);
    }else{
      if( *z ){ *z++ = 0; }
      zValue = "";
    }
    if( zName[0] && fossil_no_strange_characters(zName+1) ){
      if( 0==bPermitCtrl && contains_ctrl(zValue) ){
        continue /* Reject it. An argument could be made
                 ** for break instead of continue. */;
      if( fossil_islower(zName[0]) ){
      }else if( fossil_islower(zName[0]) ){
        cgi_set_parameter_nocopy(zName, zValue, isQP);
      }else if( fossil_isupper(zName[0]) ){
        cgi_set_parameter_nocopy_tolower(zName, zValue, isQP);
      }
    }
#ifdef FOSSIL_ENABLE_JSON
    json_setenv( zName, cson_value_new_string(zValue,strlen(zValue)) );
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265










1266
1267
1268
1269
1270
1271

1272
1273

1274
1275
1276
1277

1278
1279
1280
1281
1282
1283
1284
1285
1286








1287
1288
1289
1290
1291
1292
1293
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
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352







-
-
-
-
-
+
+
+
+
+
+
+
+
+
+





-
+

-
+



-
+









+
+
+
+
+
+
+
+







  fputs(z, pLog);
}

/* Forward declaration */
static NORETURN void malformed_request(const char *zMsg, ...);

/*
** Checks the QUERY_STRING environment variable, sets it up
** via add_param_list() and, if found, applies its "skin"
** setting. Returns 0 if no QUERY_STRING is set, 1 if it is,
** and 2 if it sets the skin (in which case the cookie may
** still need flushing by the page, via cookie_render()).
** Checks the QUERY_STRING environment variable, sets it up via
** add_param_list() and, if found, applies its "skin" setting. Returns
** 0 if no QUERY_STRING is set, else it returns a bitmask of:
**
** 0x01 = QUERY_STRING was set up
** 0x02 = "skin" URL param arg was processed
** 0x04 = "x-f-l-c" cookie arg was processed.
**
*  In the case of the skin, the cookie may still need flushing
** by the page, via cookie_render().
*/
int cgi_setup_query_string(void){
  int rc = 0;
  char * z = (char*)P("QUERY_STRING");
  if( z ){
    ++rc;
    rc = 0x01;
    z = fossil_strdup(z);
    add_param_list(z, '&');
    add_param_list(z, '&', 0);
    z = (char*)P("skin");
    if( z ){
      char *zErr = skin_use_alternative(z, 2, SKIN_FROM_QPARAM);
      ++rc;
      rc |= 0x02;
      if( !zErr && P("once")==0 ){
        cookie_write_parameter("skin","skin",z);
        /* Per /chat discussion, passing ?skin=... without "once"
        ** implies the "udc" argument, so we force that into the
        ** environment here. */
        cgi_set_parameter_nocopy("udc", "1", 1);
      }
      fossil_free(zErr);
    }
  }
  if( !g.syncInfo.zLoginCard && 0!=(z=(char*)P("x-f-l-c")) ){
    /* x-f-l-c (X-Fossil-Login-Card card transmitted via cookie
    ** instead of in the sync payload. */
    rc |= 0x04;
    g.syncInfo.zLoginCard = fossil_strdup(z);
    g.syncInfo.fLoginCardMode |= 0x02;
    cgi_delete_parameter("x-f-l-c");
  }
  return rc;
}

/*
** Initialize the query parameter database.  Information is pulled from
** the QUERY_STRING environment variable (if it exists), from standard
1418
1419
1420
1421
1422
1423
1424
1425

1426
1427
1428
1429
1430
1431
1432
1477
1478
1479
1480
1481
1482
1483

1484
1485
1486
1487
1488
1489
1490
1491







-
+







    assert(!g.json.isJsonMode &&
           "Internal misconfiguration of g.json.isJsonMode");
  }
#endif
  z = (char*)P("HTTP_COOKIE");
  if( z ){
    z = fossil_strdup(z);
    add_param_list(z, ';');
    add_param_list(z, ';', 0);
    z = (char*)cookie_value("skin",0);
    if(z){
      skin_use_alternative(z, 2, SKIN_FROM_COOKIE);
    }
  }

  cgi_setup_query_string();
1481
1482
1483
1484
1485
1486
1487
1488

1489
1490
1491
1492
1493
1494
1495
1540
1541
1542
1543
1544
1545
1546

1547
1548
1549
1550
1551
1552
1553
1554







-
+







  if( len==0 ) return;
  if( fossil_strcmp(g.zContentType,"application/x-www-form-urlencoded")==0
   || fossil_strncmp(g.zContentType,"multipart/form-data",19)==0
  ){
    char *z = blob_str(&g.cgiIn);
    cgi_trace(z);
    if( g.zContentType[0]=='a' ){
      add_param_list(z, '&');
      add_param_list(z, '&', 1);
    }else{
      process_multipart_form_data(z, len);
    }
    blob_init(&g.cgiIn, 0, 0);
  }
}

1573
1574
1575
1576
1577
1578
1579















1580
1581
1582
1583
1584
1585
1586
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







      CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
      return zValue;
    }
  }
  CGIDEBUG(("no-match [%s]\n", zName));
  return zDefault;
}

/*
** Return TRUE if the specific parameter exists and is a query parameter.
** Return FALSE if the parameter is a cookie or environment variable.
*/
int cgi_is_qp(const char *zName){
  int i;
  if( zName==0 || fossil_isupper(zName[0]) ) return 0;
  for(i=0; i<nUsedQP; i++){
    if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){
      return aParamQP[i].isQP;
    }
  }
  return 0;
}

/*
** Renders the "begone, spider" page and exits.
*/
static void cgi_begone_spider(const char *zName){
  Blob content = empty_blob;
  cgi_set_content(&content);
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

1632
1633
1634
1635
1636
1637
1638
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
1711
1712
1713
1714







-
+






-
-
-
-
-
+
+
+
+
+

-
+
+
+


-
+









-
+







  cgi_set_status(418,"I'm a teapot");
  cgi_reply();
  fossil_errorlog("Xpossible hack attempt - 418 response on \"%s\"", zName);
  exit(0);
}

/*
** If looks_like_sql_injection() returns true for the given string, calls
** If looks_like_attack() returns true for the given string, call
** cgi_begone_spider() and does not return, else this function has no
** side effects. The range of checks performed by this function may
** be extended in the future.
**
** Checks are omitted for any logged-in user.
**
** This is NOT a defense against SQL injection.  Fossil should easily be
** proof against SQL injection without this routine.  Rather, this is an
** attempt to avoid denial-of-service caused by persistent spiders that hammer
** the server with dozens or hundreds of SQL injection attempts per second
** against pages (such as /vdiff) that are expensive to compute.  In other
** This is the primary defense against attack.  Fossil should easily be
** proof against SQL injection and XSS attacks even without this
** routine.  Rather, this is an attempt to avoid denial-of-service caused
** by persistent spiders that hammer the server with dozens or hundreds of
** probes per seconds as they look for vulnerabilities. In other
** words, this is an effort to reduce the CPU load imposed by malicious
** spiders.  It is not an effect defense against SQL injection vulnerabilities.
** spiders.  Though those routine might help make attacks harder, it is
** not itself an impenetrably barrier against attack and should not be
** relied upon as the only defense.
*/
void cgi_value_spider_check(const char *zTxt, const char *zName){
  if( g.zLogin==0 && looks_like_sql_injection(zTxt) ){
  if( g.zLogin==0 && looks_like_attack(zTxt) ){
    cgi_begone_spider(zName);
  }
}

/*
** A variant of cgi_parameter() with the same semantics except that if
** cgi_parameter(zName,zDefault) returns a value other than zDefault
** then it passes that value to cgi_value_spider_check().
*/
const char *cgi_parameter_nosql(const char *zName, const char *zDefault){
const char *cgi_parameter_no_attack(const char *zName, const char *zDefault){
  const char *zTxt = cgi_parameter(zName, zDefault);

  if( zTxt!=zDefault ){
    cgi_value_spider_check(zTxt, zName);
  }
  return zTxt;
}
1776
1777
1778
1779
1780
1781
1782
1783

1784
1785
1786
1787
1788
1789
1790
1852
1853
1854
1855
1856
1857
1858

1859
1860
1861
1862
1863
1864
1865
1866







-
+







*/
void cgi_load_environment(void){
  /* The following is a list of environment variables that Fossil considers
  ** to be "relevant". */
  static const char *const azCgiVars[] = {
    "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE", "SCGI",
    "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
    "HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENICATION",
    "HTTP_ACCEPT_LANGUAGE", "HTTP_AUTHENTICATION",
    "HTTP_CONNECTION", "HTTP_HOST",
    "HTTP_IF_NONE_MATCH", "HTTP_IF_MODIFIED_SINCE",
    "HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
    "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",
    "REMOTE_USER", "REQUEST_METHOD", "REQUEST_SCHEME",
    "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_NAME",
    "SERVER_PROTOCOL", "HOME", "FOSSIL_HOME", "USERNAME", "USER",
1965
1966
1967
1968
1969
1970
1971
1972

1973
1974
1975
1976
1977
1978
1979
2041
2042
2043
2044
2045
2046
2047

2048
2049
2050
2051
2052
2053
2054
2055







-
+







static NORETURN void malformed_request(const char *zMsg, ...){
  va_list ap;
  char *z;
  va_start(ap, zMsg);
  z = vmprintf(zMsg, ap);
  va_end(ap);
  cgi_set_status(400, "Bad Request");
  zContentType = "text/plain";
  zReplyMimeType = "text/plain";
  if( g.zReqType==0 ) g.zReqType = "WWW";
  if( g.zReqType[0]=='C' && PD("SERVER_SOFTWARE",0)!=0 ){
    const char *zServer = PD("SERVER_SOFTWARE","");
    cgi_printf("Bad CGI Request from \"%s\": %s\n",zServer,z);
  }else{
    cgi_printf("Bad %s Request: %s\n", g.zReqType, z);
  }
2048
2049
2050
2051
2052
2053
2054











2055
2056
2057
2058
2059
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
2088
2089
2090
2091
2092
2093
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
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
2189
2190
2191
2192
2193

2194
2195
2196
2197
2198
2199
2200
2201







+
+
+
+
+
+
+
+
+
+
+








-
-
+
+
+
-
-
-
-
-
+
+
+
+
+


-
+
-
-
-
-
-
-



















+













-
+







    zInput++;
    while( fossil_isspace(*zInput) ){ zInput++; }
  }
  if( zLeftOver ){ *zLeftOver = zInput; }
  return zResult;
}

/*
** All possible forms of an IP address.  Needed to work around GCC strict
** aliasing rules.
*/
typedef union {
  struct sockaddr sa;              /* Abstract superclass */
  struct sockaddr_in sa4;          /* IPv4 */
  struct sockaddr_in6 sa6;         /* IPv6 */
  struct sockaddr_storage sas;     /* Should be the maximum of the above 3 */
} address;

/*
** Determine the IP address on the other side of a connection.
** Return a pointer to a string.  Or return 0 if unable.
**
** The string is held in a static buffer that is overwritten on
** each call.
*/
char *cgi_remote_ip(int fd){
#if 0
  static char zIp[100];
  address remoteAddr;
  socklen_t size = sizeof(remoteAddr);
  static char zHost[NI_MAXHOST];
  struct sockaddr_in6 addr;
  socklen_t sz = sizeof(addr);
  if( getpeername(fd, &addr, &sz) ) return 0;
  zIp[0] = 0;
  if( inet_ntop(AF_INET6, &addr, zIp, sizeof(zIp))==0 ){
  if( getpeername(0, &remoteAddr.sa, &size) ){
    return 0;
  }
  if( getnameinfo(&remoteAddr.sa, size, zHost, sizeof(zHost), 0, 0,
                  NI_NUMERICHOST) ){
    return 0;
  }
  return zIp;
  return zHost;
#else
  struct sockaddr_in remoteName;
  socklen_t size = sizeof(struct sockaddr_in);
  if( getpeername(fd, (struct sockaddr*)&remoteName, &size) ) return 0;
  return inet_ntoa(remoteName.sin_addr);
#endif
}

/*
** This routine handles a single HTTP request which is coming in on
** g.httpIn and which replies on g.httpOut
**
** The HTTP request is read from g.httpIn and is used to initialize
** entries in the cgi_parameter() hash, as if those entries were
** environment variables.  A call to cgi_init() completes
** the setup.  Once all the setup is finished, this procedure returns
** and subsequent code handles the actual generation of the webpage.
*/
void cgi_handle_http_request(const char *zIpAddr){
  char *z, *zToken;
  int i;
  const char *zScheme = "http";
  char zLine[2000];     /* A single line of input. */
  g.fullHttpReply = 1;
  g.zReqType = "HTTP";

  if( cgi_fgets(zLine, sizeof(zLine))==0 ){
    malformed_request("missing header");
  }
  blob_append(&g.httpHeader, zLine, -1);
  cgi_trace(zLine);
  zToken = extract_token(zLine, &z);
  if( zToken==0 ){
    malformed_request("malformed HTTP header");
  }
  if( fossil_strcmp(zToken,"GET")!=0
   && fossil_strcmp(zToken,"POST")!=0
   && fossil_strcmp(zToken,"HEAD")!=0
  ){
    malformed_request("unsupported HTTP method: \"%s\" - Fossil only supports"
    malformed_request("unsupported HTTP method: \"%s\" - Fossil only supports "
                      "GET, POST, and HEAD", zToken);
  }
  cgi_setenv("GATEWAY_INTERFACE","CGI/1.0");
  cgi_setenv("REQUEST_METHOD",zToken);
  zToken = extract_token(z, &z);
  if( zToken==0 ){
    malformed_request("malformed URI in the HTTP header");
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2209
2210
2211
2212
2213
2214
2215

2216
2217
2218
2219
2220
2221
2222







-







  if( zIpAddr==0 ){
    zIpAddr = cgi_remote_ip(fossil_fileno(g.httpIn));
  }
  if( zIpAddr ){
    cgi_setenv("REMOTE_ADDR", zIpAddr);
    g.zIpAddr = fossil_strdup(zIpAddr);
  }


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

2207
2208
2209
2210
2211
2212
2213
2214
2215



2216
2217
2218
2219
2220
2221
2222
2289
2290
2291
2292
2293
2294
2295


2296
2297
2298
2299
2300
2301
2302
2303
2304
2305







-
-
+
+
+







**
** It is called in a loop so some variables will need to be replaced
*/
void cgi_handle_ssh_http_request(const char *zIpAddr){
  static int nCycles = 0;
  static char *zCmd = 0;
  char *z, *zToken;
  const char *zType = 0;
  int i, content_length = 0;
  char *zMethod;
  int i;
  size_t n;
  char zLine[2000];     /* A single line of input. */

  assert( !g.httpUseSSL );
#ifdef FOSSIL_ENABLE_JSON
  if( nCycles==0 ){ json_bootstrap_early(); }
#endif
  if( zIpAddr ){
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
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







+













+
+
+
+
+
+
+
+
+
+










+


+



















+
-
+
+
+
+

+
-
+
+







    cgi_trace(zLine);
    zToken = extract_token(zLine, &z);
    if( zToken==0 ){
      malformed_request("malformed HTTP header");
    }
  }

  zMethod = fossil_strdup(zToken);
  if( fossil_strcmp(zToken,"GET")!=0 && fossil_strcmp(zToken,"POST")!=0
      && fossil_strcmp(zToken,"HEAD")!=0 ){
    malformed_request("unsupported HTTP method");
  }

  if( nCycles==0 ){
    cgi_setenv("GATEWAY_INTERFACE","CGI/1.0");
    cgi_setenv("REQUEST_METHOD",zToken);
  }

  zToken = extract_token(z, &z);
  if( zToken==0 ){
    malformed_request("malformed URL in HTTP header");
  }
  n = strlen(g.zRepositoryName);
  if( fossil_strncmp(g.zRepositoryName, zToken, n)==0 
   && (zToken[n]=='/' || zToken[n]==0)
   && fossil_strcmp(zMethod,"GET")==0
  ){
    zToken += n;
    if( zToken && strlen(zToken)==0 ){
      malformed_request("malformed URL in HTTP header");
    }
  }
  if( nCycles==0 ){
    cgi_setenv("REQUEST_URI", zToken);
    cgi_setenv("SCRIPT_NAME", "");
  }

  for(i=0; zToken[i] && zToken[i]!='?'; i++){}
  if( zToken[i] ) zToken[i++] = 0;
  if( nCycles==0 ){
    cgi_setenv("PATH_INFO", zToken);
    cgi_setenv("QUERY_STRING",&zToken[i]);
  }else{
    cgi_replace_parameter("PATH_INFO", fossil_strdup(zToken));
    cgi_replace_parameter("QUERY_STRING",fossil_strdup(&zToken[i]));
  }

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

    cgi_trace(zLine);
    zFieldName = extract_token(zLine,&zVal);
    if( zFieldName==0 || *zFieldName==0 ) break;
    while( fossil_isspace(*zVal) ){ zVal++; }
    i = strlen(zVal);
    while( i>0 && fossil_isspace(zVal[i-1]) ){ i--; }
    zVal[i] = 0;
    for(i=0; zFieldName[i]; i++){
      zFieldName[i] = fossil_tolower(zFieldName[i]);
    }
    if( fossil_strcmp(zFieldName,"content-length:")==0 ){
      if( nCycles==0 ){
      content_length = atoi(zVal);
        cgi_setenv("CONTENT_LENGTH", zVal);
      }else{
        cgi_replace_parameter("CONTENT_LENGTH", zVal);
      }
    }else if( fossil_strcmp(zFieldName,"content-type:")==0 ){
      if( nCycles==0 ){
      g.zContentType = zType = fossil_strdup(zVal);
        cgi_setenv("CONTENT_TYPE", zVal);
      }
    }else if( fossil_strcmp(zFieldName,"host:")==0 ){
      if( nCycles==0 ){
        cgi_setenv("HTTP_HOST", zVal);
      }
    }else if( fossil_strcmp(zFieldName,"user-agent:")==0 ){
      if( nCycles==0 ){
        cgi_setenv("HTTP_USER_AGENT", zVal);
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
2436
2437
2438
2439
2440
2441
2442


2443









2444
2445
2446
2447
2448
2449
2450







-
-
+
-
-
-
-
-
-
-
-
-







      cgi_replace_parameter("REMOTE_ADDR", "127.0.0.1");
    }
  }

  cgi_reset_content();
  cgi_destination(CGI_BODY);

  if( content_length>0 && zType ){
    blob_zero(&g.cgiIn);
  cgi_init();
    if( fossil_strcmp(zType, "application/x-fossil")==0 ){
      blob_read_from_channel(&g.cgiIn, g.httpIn, content_length);
      blob_uncompress(&g.cgiIn, &g.cgiIn);
    }else if( fossil_strcmp(zType, "application/x-fossil-debug")==0 ){
      blob_read_from_channel(&g.cgiIn, g.httpIn, content_length);
    }else if( fossil_strcmp(zType, "application/x-fossil-uncompressed")==0 ){
      blob_read_from_channel(&g.cgiIn, g.httpIn, content_length);
    }
  }
  cgi_trace(0);
  nCycles++;
}

/*
** This routine handles the old fossil SSH probes
*/
2460
2461
2462
2463
2464
2465
2466
2467

2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2552
2553
2554
2555
2556
2557
2558

2559
2560
2561

2562
2563
2564
2565
2566
2567
2568







-
+


-







    for(m=n+1; m<nHdr && zHdr[m]; m++){}
    if( m>=nHdr ) malformed_request("SCGI header formatting error");
    cgi_set_parameter(zHdr, zHdr+n+1);
    zHdr += m+1;
    nHdr -= m+1;
  }
  fossil_free(zToFree);
  fgetc(g.httpIn);  /* Read past the "," separating header from content */
  (void)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 */
2507
2508
2509
2510
2511
2512
2513


2514
2515


2516
2517
2518
2519
2520
2521

2522

2523
2524
2525
2526

2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559














































2560
2561
2562
2563
2564
2565
2566
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
2595




















2596





2597
2598
2599
2600

























2601




2602
2603
2604
2605



























2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616









































2617
2618
2619
2620
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
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687













































2688
2689
2690
2691
2692
2693
2694
2695
2598
2599
2600
2601
2602
2603
2604
2605
2606


2607
2608
2609
2610
2611
2612
2613
2614
2615

2616
2617
2618
2619
2620
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
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694




2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724




2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751











2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792









2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847




































2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892

2893
2894
2895
2896
2897
2898
2899







+
+
-
-
+
+






+
-
+




+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

















+
+
+
+
+









-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-







  const char *zIpAddr,      /* Bind to this IP address, if not null */
  int flags                 /* HTTP_SERVER_* flags */
){
#if defined(_WIN32)
  /* Use win32_http_server() instead */
  fossil_exit(1);
#else
  int listen4 = -1;            /* Main socket; IPv4 or unix-domain */
  int listen6 = -1;            /* Aux socket for corresponding IPv6 */
  int listener = -1;           /* The server socket */
  int connection;              /* A socket for each individual connection */
  int mxListen = -1;           /* Maximum of listen4 and listen6 */
  int connection;              /* An incoming connection */
  int nRequest = 0;            /* Number of requests handled so far */
  fd_set readfds;              /* Set of file descriptors for select() */
  socklen_t lenaddr;           /* Length of the inaddr structure */
  int child;                   /* PID of the child process */
  int nchildren = 0;           /* Number of child processes */
  struct timeval delay;        /* How long to wait inside select() */
  struct sockaddr_in6 inaddr6; /* Address for IPv6 */
  struct sockaddr_in inaddr;   /* The socket address */
  struct sockaddr_in inaddr4;  /* Address for IPv4 */
  struct sockaddr_un uxaddr;   /* The address for unix-domain sockets */
  int opt = 1;                 /* setsockopt flag */
  int rc;                      /* Result code from system calls */
  int iPort = mnPort;          /* Port to try to use */
  const char *zRequestType;    /* Type of requests to listen for */

  while( iPort<=mxPort ){
    if( flags & HTTP_SERVER_UNIXSOCKET ){
      /* Initialize a Unix socket named g.zSockName */
      assert( g.zSockName!=0 );
      memset(&uxaddr, 0, sizeof(uxaddr));
      if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){
        fossil_fatal("name of unix socket too big: %s\nmax size: %d\n",
                     g.zSockName, (int)sizeof(uxaddr.sun_path));
      }
      if( file_isdir(g.zSockName, ExtFILE)!=0 ){
        if( !file_issocket(g.zSockName) ){
          fossil_fatal("cannot name socket \"%s\" because another object"
                       " with that name already exists", g.zSockName);
        }else{
          unlink(g.zSockName);
        }
      }
      uxaddr.sun_family = AF_UNIX;
      strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1);
      listener = socket(AF_UNIX, SOCK_STREAM, 0);
      if( listener<0 ){
        fossil_fatal("unable to create a unix socket named %s",
                     g.zSockName);
      }
      /* Set the access permission for the new socket.  Default to 0660.
      ** But use an alternative specified by --socket-mode if available.
      ** Do this before bind() to avoid a race condition. */
      if( g.zSockMode ){
        file_set_mode(g.zSockName, listener, g.zSockMode, 0);
      }else{
        file_set_mode(g.zSockName, listener, "0660", 1);
      }

  if( flags & HTTP_SERVER_SCGI ){
    zRequestType = "SCGI";
  }else if( g.httpUseSSL ){
    zRequestType = "TLS-encrypted HTTPS";
  }else{
    zRequestType = "HTTP";
  }

  if( flags & HTTP_SERVER_UNIXSOCKET ){
    /* CASE 1:  A unix socket named g.zSockName.  After creation, set the
    **          permissions on the new socket to g.zSockMode and make the
    **          owner of the socket be g.zSockOwner.
    */
    assert( g.zSockName!=0 );
    memset(&uxaddr, 0, sizeof(uxaddr));
    if( strlen(g.zSockName)>sizeof(uxaddr.sun_path) ){
      fossil_fatal("name of unix socket too big: %s\nmax size: %d\n",
                   g.zSockName, (int)sizeof(uxaddr.sun_path));
    }
    if( file_isdir(g.zSockName, ExtFILE)!=0 ){
      if( !file_issocket(g.zSockName) ){
        fossil_fatal("cannot name socket \"%s\" because another object"
                     " with that name already exists", g.zSockName);
      }else{
        unlink(g.zSockName);
      }
    }
    uxaddr.sun_family = AF_UNIX;
    strncpy(uxaddr.sun_path, g.zSockName, sizeof(uxaddr.sun_path)-1);
    listen4 = socket(AF_UNIX, SOCK_STREAM, 0);
    if( listen4<0 ){
      fossil_fatal("unable to create a unix socket named %s",
                   g.zSockName);
    }
    mxListen = listen4;
    listen6 = -1;

    /* Set the access permission for the new socket.  Default to 0660.
    ** But use an alternative specified by --socket-mode if available.
    ** Do this before bind() to avoid a race condition. */
    if( g.zSockMode ){
      file_set_mode(g.zSockName, listen4, g.zSockMode, 0);
    }else{
      file_set_mode(g.zSockName, listen4, "0660", 1);
    }
    }else{
      /* Initialize a TCP/IP socket on port iPort */
      memset(&inaddr, 0, sizeof(inaddr));
      inaddr.sin_family = AF_INET;
      if( zIpAddr ){
        inaddr.sin_addr.s_addr = inet_addr(zIpAddr);
        if( inaddr.sin_addr.s_addr == INADDR_NONE ){
          fossil_fatal("not a valid IP address: %s", zIpAddr);
        }
      }else if( flags & HTTP_SERVER_LOCALHOST ){
        inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
      }else{
        inaddr.sin_addr.s_addr = htonl(INADDR_ANY);
      }
      inaddr.sin_port = htons(iPort);
      listener = socket(AF_INET, SOCK_STREAM, 0);
      if( listener<0 ){
        iPort++;
        continue;
      }
    }

    /* if we can't terminate nicely, at least allow the socket to be reused */
    setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
  
    if( flags & HTTP_SERVER_UNIXSOCKET ){
      rc = bind(listener, (struct sockaddr*)&uxaddr, sizeof(uxaddr));
      /* Set the owner of the socket if requested by --socket-owner.  This
      ** must wait until after bind(), after the filesystem object has been
      ** created.  See https://lkml.org/lkml/2004/11/1/84 and
      ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */
      if( g.zSockOwner ){
        file_set_owner(g.zSockName, listener, g.zSockOwner);
      }
    }else{
      rc = bind(listener, (struct sockaddr*)&inaddr, sizeof(inaddr));
    rc = bind(listen4, (struct sockaddr*)&uxaddr, sizeof(uxaddr));
    /* Set the owner of the socket if requested by --socket-owner.  This
    ** must wait until after bind(), after the filesystem object has been
    ** created.  See https://lkml.org/lkml/2004/11/1/84 and
    ** https://fossil-scm.org/forum/forumpost/7517680ef9684c57 */
    if( g.zSockOwner ){
      file_set_owner(g.zSockName, listen4, g.zSockOwner);
    }
    fossil_print("Listening for %s requests on unix socket %s\n",
                 zRequestType, g.zSockName);
    fflush(stdout);
  }else if( zIpAddr && strchr(zIpAddr,':')!=0 ){
    /* CASE 2: TCP on IPv6 IP address specified by zIpAddr and on port iPort.
    */
    assert( mnPort==mxPort );
    memset(&inaddr6, 0, sizeof(inaddr6));
    inaddr6.sin6_family = AF_INET6;
    inaddr6.sin6_port = htons(iPort);
    if( inet_pton(AF_INET6, zIpAddr, &inaddr6.sin6_addr)==0 ){
      fossil_fatal("not a valid IPv6 address: %s", zIpAddr);
    }
    listen6 = socket(AF_INET6, SOCK_STREAM, 0);
    if( listen6>0 ){
      opt = 1;
      setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
      rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
    if( rc<0 ){
      close(listener);
      iPort++;
      continue;
      if( rc<0 ){
        close(listen6);
        listen6 = -1;
      }
    }
    if( listen6<0 ){
      fossil_fatal("cannot open a listening socket on [%s]:%d",
                   zIpAddr, mnPort);
    }
    mxListen = listen6;
    listen4 = -1;
    fossil_print("Listening for %s requests on [%s]:%d\n",
                 zRequestType, zIpAddr, iPort);
    fflush(stdout);
  }else if( zIpAddr && zIpAddr[0] ){
    /* CASE 3: TCP on IPv4 IP address specified by zIpAddr and on port iPort.
    */
    assert( mnPort==mxPort );
    memset(&inaddr4, 0, sizeof(inaddr4));
    inaddr4.sin_family = AF_INET;
    inaddr4.sin_port = htons(iPort);
    if( strcmp(zIpAddr, "localhost")==0 ) zIpAddr = "127.0.0.1";
    inaddr4.sin_addr.s_addr = inet_addr(zIpAddr);
    if( inaddr4.sin_addr.s_addr == INADDR_NONE ){
      fossil_fatal("not a valid IPv4 address: %s", zIpAddr);
    }
    listen4 = socket(AF_INET, SOCK_STREAM, 0);
    if( listen4>0 ){
      setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
      rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
    break;
  }
  if( iPort>mxPort ){
    if( flags & HTTP_SERVER_UNIXSOCKET ){
      if( rc<0 ){
        close(listen4);
        listen4 = -1;
      }
    }
    if( listen4<0 ){
      fossil_fatal("cannot open a listening socket on %s:%d",
                   zIpAddr, mnPort);
    }
    mxListen = listen4;
    listen6 = -1;
    fossil_print("Listening for %s requests on TCP port %s:%d\n",
                 zRequestType, zIpAddr, iPort);
    fflush(stdout);
  }else{
    /* CASE 4: Listen on all available IP addresses, or on only loopback
    **         addresses (if HTTP_SERVER_LOCALHOST).  The TCP port is the
    **         first available in the range of mnPort..mxPort.  Listen
    **         on both IPv4 and IPv6, if possible.  The TCP port scan is done
    **         on IPv4.
    */
    while( iPort<=mxPort ){
      const char *zProto;
      memset(&inaddr4, 0, sizeof(inaddr4));
      inaddr4.sin_family = AF_INET;
      inaddr4.sin_port = htons(iPort);
      if( flags & HTTP_SERVER_LOCALHOST ){
      fossil_fatal("unable to listen on unix socket %s", zIpAddr);
    }else if( mnPort==mxPort ){
      fossil_fatal("unable to open listening socket on port %d", mnPort);
    }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( flags & HTTP_SERVER_UNIXSOCKET ){
        inaddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
      }else{
        inaddr4.sin_addr.s_addr = htonl(INADDR_ANY);
      }
      listen4 = socket(AF_INET, SOCK_STREAM, 0);
      if( listen4>0 ){
        setsockopt(listen4, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        rc = bind(listen4, (struct sockaddr*)&inaddr4, sizeof(inaddr4));
        if( rc<0 ){
          close(listen4);
          listen4 = -1;
        }
      }
      if( listen4<0 ){
        iPort++;
        continue;
      }
      mxListen = listen4;

      /* If we get here, that means we found an open TCP port at iPort for
      ** IPv4.  Try to set up a corresponding IPv6 socket on the same port.
      */
      memset(&inaddr6, 0, sizeof(inaddr6));
      inaddr6.sin6_family = AF_INET6;
      inaddr6.sin6_port = htons(iPort);
      if( flags & HTTP_SERVER_LOCALHOST ){
        inaddr6.sin6_addr = in6addr_loopback;
      }else{
        inaddr6.sin6_addr = in6addr_any;
      }
      listen6 = socket(AF_INET6, SOCK_STREAM, 0);
      if( listen6>0 ){
        setsockopt(listen6, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        setsockopt(listen6, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
        rc = bind(listen6, (struct sockaddr*)&inaddr6, sizeof(inaddr6));
        if( rc<0 ){
          close(listen6);
          listen6 = -1;
        }
      }
      if( listen6<0 ){
    fossil_print("Listening for %s requests on unix socket %s\n",
       (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" :
          g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP",  g.zSockName);
  }else{
    fossil_print("Listening for %s requests on TCP port %d\n",
       (flags & HTTP_SERVER_SCGI)!=0 ? "SCGI" :
          g.httpUseSSL?"TLS-encrypted HTTPS":"HTTP",  iPort);
  }
  fflush(stdout);
        zProto = "IPv4 only";
      }else{
        zProto = "IPv4 and IPv6";
        if( listen6>listen4 ) mxListen = listen6;
      }

      fossil_print("Listening for %s requests on TCP port %s%d, %s\n",
                   zRequestType, 
                   (flags & HTTP_SERVER_LOCALHOST)!=0 ? "localhost:" : "",
                   iPort, zProto);
      fflush(stdout);
      break;
    }
    if( iPort>mxPort ){
      fossil_fatal("no available TCP ports in the range %d..%d",
                   mnPort, mxPort);
    }
  }

  /* If we get to this point, that means there is at least one listening
  ** socket on either listen4 or listen6 and perhaps on both. */
  assert( listen4>0 || listen6>0 );
  if( listen4>0 ) listen(listen4,10);
  if( listen6>0 ) listen(listen6,10);
  if( zBrowser && (flags & HTTP_SERVER_UNIXSOCKET)==0 ){
    assert( strstr(zBrowser,"%d")!=0 );
    zBrowser = mprintf(zBrowser /*works-like:"%d"*/, iPort);
#if defined(__CYGWIN__)
    /* On Cygwin, we can do better than "echo" */
    if( fossil_strncmp(zBrowser, "echo ", 5)==0 ){
      wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5);
      wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */
      if( (size_t)ShellExecuteW(0, L"open", wUrl, 0, 0, 1)<33 ){
        fossil_warning("cannot start browser\n");
      }
    }else
#endif
    if( fossil_system(zBrowser)<0 ){
      fossil_warning("cannot start browser: %s\n", zBrowser);
    }
  }

  /* What for incoming requests.  For each request, fork() a child process
  ** to deal with that request.  The child process returns.  The parent
  ** keeps on listening and never returns.
  */
  while( 1 ){
#if FOSSIL_MAX_CONNECTIONS>0
    while( nchildren>=FOSSIL_MAX_CONNECTIONS ){
      if( wait(0)>=0 ) nchildren--;
    }
#endif
    delay.tv_sec = 0;
    delay.tv_usec = 100000;
    FD_ZERO(&readfds);
    assert( listener>=0 );
    FD_SET( listener, &readfds);
    select( listener+1, &readfds, 0, 0, &delay);
    if( FD_ISSET(listener, &readfds) ){
      lenaddr = sizeof(inaddr);
      connection = accept(listener, (struct sockaddr*)&inaddr, &lenaddr);
      if( connection>=0 ){
        if( flags & HTTP_SERVER_NOFORK ){
          child = 0;
        }else{
          child = fork();
        }
        if( child!=0 ){
          if( child>0 ){
            nchildren++;
            nRequest++;
          }
          close(connection);
        }else{
          int nErr = 0, fd;
          g.zSockName = 0 /* avoid deleting the socket via atexit() */;
          close(0);
          fd = dup(connection);
          if( fd!=0 ) nErr++;
          close(1);
          fd = dup(connection);
          if( fd!=1 ) nErr++;
          if( 0 && !g.fAnyTrace ){
            close(2);
            fd = dup(connection);
            if( fd!=2 ) nErr++;
          }
          close(connection);
          g.nPendingRequest = nchildren+1;
          g.nRequest = nRequest+1;
          return nErr;
    assert( listen4>0 || listen6>0 );
    if( listen4>0 ) FD_SET( listen4, &readfds);
    if( listen6>0 ) FD_SET( listen6, &readfds);
    select( mxListen+1, &readfds, 0, 0, &delay);
    if( listen4>0 && FD_ISSET(listen4, &readfds) ){
      lenaddr = sizeof(inaddr4);
      connection = accept(listen4, (struct sockaddr*)&inaddr4, &lenaddr);
    }else if( listen6>0 && FD_ISSET(listen6, &readfds) ){
      lenaddr = sizeof(inaddr6);
      connection = accept(listen6, (struct sockaddr*)&inaddr6, &lenaddr);
    }else{
      connection = -1;
    }
    if( connection>=0 ){
      if( flags & HTTP_SERVER_NOFORK ){
        child = 0;
      }else{
        child = fork();
      }
      if( child!=0 ){
        if( child>0 ){
          nchildren++;
          nRequest++;
        }
        close(connection);
      }else{
        int nErr = 0, fd;
        g.zSockName = 0 /* avoid deleting the socket via atexit() */;
        close(0);
        fd = dup(connection);
        if( fd!=0 ) nErr++;
        close(1);
        fd = dup(connection);
        if( fd!=1 ) nErr++;
        if( 0 && !g.fAnyTrace ){
          close(2);
          fd = dup(connection);
          if( fd!=2 ) nErr++;
        }
        close(connection);
        if( listen4>0 ) close(listen4);
        if( listen6>0 ) close(listen6);
        g.nPendingRequest = nchildren+1;
        g.nRequest = nRequest+1;
        return nErr;
        }
      }
    }
    /* Bury dead children */
    if( nchildren ){
      while(1){
        int iStatus = 0;
        pid_t x = waitpid(-1, &iStatus, WNOHANG);
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2961
2962
2963
2964
2965
2966
2967










2968
2969
2970
2971
2972
2973
2974







-
-
-
-
-
-
-
-
-
-







  }else{
    return mprintf("%04d-%02d-%02d %02d:%02d:%02d",
                   pTm->tm_year+1900, pTm->tm_mon+1, pTm->tm_mday,
                   pTm->tm_hour, pTm->tm_min, pTm->tm_sec);
  }
}

/*
** COMMAND: test-date
**
** Show the current date and time in both RFC822 and ISO8601.
*/
void test_date(void){
  fossil_print("%z = ", cgi_iso8601_datestamp());
  fossil_print("%z\n", cgi_rfc822_datestamp(time(0)));
}

/*
** Parse an RFC822-formatted timestamp as we'd expect from HTTP and return
** a Unix epoch time. <= zero is returned on failure.
**
** Note that this won't handle all the _allowed_ HTTP formats, just the
** most popular one (the one generated by cgi_rfc822_datestamp(), actually).
*/
Changes to src/chat.c.
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
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







+
+
+
+
+
















-
+
+







  @     <button class='action-close'>Close Search</button>
  @   </div>
  @ </div>
  @ <div id='chat-messages-wrapper' class='chat-view'>
  /* New chat messages get inserted immediately after this element */
  @ <span id='message-inject-point'></span>
  @ </div>
  @ <div id='chat-zoom' class='hidden chat-view'>
  @  <div id='chat-zoom-content'></div>
  @  <div class='button-bar'><button class='action-close'>Close Zoom</button></div>
  @ </div>
  @ <span id='chat-zoom-marker' class='hidden'><!-- placeholder marker for zoomed msg --></span>
  fossil_free(zProjectName);
  fossil_free(zInputPlaceholder0);
  builtin_fossil_js_bundle_or("popupwidget", "storage", "fetch",
                              "pikchr", "confirmer", "copybutton",
                              NULL);
  /* Always in-line the javascript for the chat page */
  @ <script nonce="%h(style_nonce())">/* chat.c:%d(__LINE__) */
  /* We need an onload handler to ensure that window.fossil is
     initialized before the chat init code runs. */
  @ window.addEventListener('load', function(){
  @ document.body.classList.add('chat');
  @ /*^^^for skins which add their own BODY tag */;
  @ window.fossil.config.chat = {
  @   fromcli: %h(PB("cli")?"true":"false"),
  @   alertSound: "%h(zAlert)",
  @   initSize: %d(db_get_int("chat-initial-history",50)),
  @   imagesInline: !!%d(db_get_boolean("chat-inline-images",1))
  @   imagesInline: !!%d(db_get_boolean("chat-inline-images",1)),
  @   pollTimeout: %d(db_get_int("chat-poll-timeout",420))
  @ };
  ajax_emit_js_preview_modes(0);
  chat_emit_alert_list();
  @ }, false);
  @ </script>
  builtin_request_js("fossil.page.chat.js");
  style_finish_page();
288
289
290
291
292
293
294

295
296
297
298
299
300
301
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308







+







** Create or rebuild the /chat search index. Requires that the
** repository.chat table exists. If bForce is true, it will drop the
** chatfts1 table and recreate/reindex it. If bForce is 0, it will
** only index the chat content if the chatfts1 table does not already
** exist.
*/
void chat_rebuild_index(int bForce){
  if( !db_table_exists("repository","chat") ) return;
  if( bForce!=0 ){
    db_multi_exec("DROP TABLE IF EXISTS chatfts1");
  }
  if( bForce!=0 || !db_table_exists("repository", "chatfts1") ){
    const int tokType = search_tokenizer_type(0);
    const char *zTokenizer = search_tokenize_arg_for_type(
      tokType==FTS5TOK_NONE ? FTS5TOK_PORTER : tokType
314
315
316
317
318
319
320
321

322
323
324
325
326
327
328
321
322
323
324
325
326
327

328
329
330
331
332
333
334
335







-
+







}

/*
** Make sure the repository data tables used by chat exist.  Create
** them if they do not. Set up TEMP triggers (if needed) to update the
** chatfts1 table as the chat table is updated.
*/
static void chat_create_tables(void){
void chat_create_tables(void){
  if( !db_table_exists("repository","chat") ){
    db_multi_exec(zChatSchema1/*works-like:""*/);
  }else if( !db_table_has_column("repository","chat","lmtime") ){
    if( !db_table_has_column("repository","chat","mdel") ){
      db_multi_exec("ALTER TABLE chat ADD COLUMN mdel INT");
    }
    db_multi_exec("ALTER TABLE chat ADD COLUMN lmtime TEXT");
386
387
388
389
390
391
392















393
394
395
396
397
398
399
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  if(fAsMessageList){
    CX("}]}");
  }else{
    CX("}");
  }
  fossil_free(zTime);
}

/*
** Like chat_emit_permissions_error() but emits a single
** /chat-message-format JSON object about a CSRF violation.
*/
static void chat_emit_csrf_error(void){
  char * zTime = cgi_iso8601_datestamp();
  cgi_set_content_type("application/json");
  CX("{");
  CX("\"isError\": true, \"xfrom\": null,");
  CX("\"mtime\": %!j, \"lmtime\": %!j,", zTime, zTime);
  CX("\"xmsg\": \"CSRF validation failure.\"");
  CX("}");
  fossil_free(zTime);
}

/*
** WEBPAGE: chat-send hidden loadavg-exempt
**
** This page receives (via XHR) a new chat-message and/or a new file
** to be entered into the chat history.
**
418
419
420
421
422
423
424



425
426
427
428
429
430
431
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456







+
+
+







void chat_send_webpage(void){
  int nByte;
  const char *zMsg;
  const char *zUserName;
  login_check_credentials();
  if( 0==g.perm.Chat ) {
    chat_emit_permissions_error(0);
    return;
  }else if( g.eAuthMethod==AUTH_COOKIE && 0==cgi_csrf_safe(1) ){
    chat_emit_csrf_error();
    return;
  }
  zUserName = (g.zLogin && g.zLogin[0]) ? g.zLogin : "nobody";
  nByte = atoi(PD("file:bytes","0"));
  zMsg = PD("msg","");
  db_begin_write();
  db_unprotect(PROTECT_READONLY);
1200
1201
1202
1203
1204
1205
1206
1207

1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225









1226
1227
1228
1229
1230
1231
1232
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
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266







-
+


















+
+
+
+
+
+
+
+
+







**      Copy chat content from the server down into the local clone,
**      as a backup or archive.  Setup privilege is required on the server.
**
**        --all                  Download all chat content. Normally only
**                               previously undownloaded content is retrieved.
**        --debug                Additional debugging output
**        --out DATABASE         Store CHAT table in separate database file
**                               DATABASE rather that adding to local clone
**                               DATABASE rather than adding to local clone
**        --unsafe               Allow the use of unencrypted http://
**
** > fossil chat send [ARGUMENTS]
**
**      This command sends a new message to the chatroom.  The message
**      to be sent is determined by arguments as follows:
**
**        -f|--file FILENAME     File to attach to the message
**        --as FILENAME2         Causes --file FILENAME to be sent with
**                               the attachment name FILENAME2
**        -m|--message TEXT      Text of the chat message
**        --remote URL           Send to this remote URL
**        --unsafe               Allow the use of unencrypted http://
**
** > fossil chat url
**
**      Show the default URL used to access the chat server.
**
** > fossil chat purge
**
**      Remove chat messages that are older than chat-keep-days and
**      which are not one of the most recent chat-keep-count message.
**
** > fossil chat reindex
**
**      Rebuild the full-text search index for chat
**
** Additional subcommands may be added in the future.
*/
void chat_command(void){
  const char *zUrl = find_option("remote",0,1);
  int urlFlags = 0;
  int isDefaultUrl = 0;
  int i;
1318
1319
1320
1321
1322
1323
1324
1325

1326
1327
1328
1329
1330
1331
1332
1352
1353
1354
1355
1356
1357
1358

1359
1360
1361
1362
1363
1364
1365
1366







-
+







                       "\r\n%z\r\n%s", obscure(zPw), zBoundary);
    }
    if( zMsg && zMsg[0] ){
      blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"msg\"\r\n"
                       "\r\n%s\r\n%s", zMsg, zBoundary);
    }
    if( zFilename && blob_read_from_file(&fcontent, zFilename, ExtFILE)>0 ){
      char *zFN = mprintf("%s", file_tail(zAs ? zAs : zFilename));
      char *zFN = fossil_strdup(file_tail(zAs ? zAs : zFilename));
      int i;
      const char *zMime = mimetype_from_name(zFN);
      for(i=0; zFN[i]; i++){
        char c = zFN[i];
        if( fossil_isalnum(c) ) continue;
        if( c=='.' ) continue;
        if( c=='-' ) continue;
1425
1426
1427
1428
1429
1430
1431









1432
1433
1434
1435
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478







+
+
+
+
+
+
+
+
+




        " SELECT msgid,mtime,lmtime,xfrom,xmsg,fname,fmime,mdel,file"
          " FROM chatbu.chat;"
      );
    }
  }else if( strcmp(g.argv[2],"url")==0 ){
    /* Show the URL to access chat. */
    fossil_print("%s/chat\n", zUrl);
  }else if( strcmp(g.argv[2],"purge")==0 ){
    /* clear out expired chat messages:  chat messages that are older then
    ** chat-keep-days and that are not one or the most recent chat-keep-count
    ** messages. */
    chat_create_tables();
    chat_purge();
  }else if( strcmp(g.argv[2],"reindex")==0 ){
    /* Rebuild the FTS5 index on chat content */
    chat_rebuild_index(1);
  }else{
    fossil_fatal("no such subcommand \"%s\".  Use --help for help", g.argv[2]);
  }
}
Changes to src/checkin.c.
482
483
484
485
486
487
488
489

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

489
490
491
492
493
494
495
496







-
+







    {"classify", C_CLASSIFY},
  }, noFlagDefs[] = {
    {"no-merge", C_MERGE   }, {"no-classify", C_CLASSIFY },
  };

  Blob report = BLOB_INITIALIZER;
  enum {CHANGES, STATUS} command = *g.argv[1]=='s' ? STATUS : CHANGES;
  /* --sha1sum is an undocumented alias for --hash for backwards compatiblity */
  /* --sha1sum is an undocumented alias for --hash for backwards compatibility */
  int useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0;
  int showHdr = command==CHANGES && find_option("header", 0, 0);
  int verboseFlag = command==CHANGES && find_option("verbose", "v", 0);
  const char *zIgnoreFlag = find_option("ignore", 0, 1);
  unsigned scanFlags = 0;
  unsigned flags = 0;
  int vid, i;
565
566
567
568
569
570
571
572

573
574
575
576
577
578
579
565
566
567
568
569
570
571

572
573
574
575
576
577
578
579







-
+







      flags &= ~noFlagDefs[i].mask;
    }
  }

  /* Confirm current working directory is within check-out. */
  db_must_be_within_tree();

  /* Get check-out version. l*/
  /* Get check-out version. */
  vid = db_lget_int("checkout", 0);

  /* Relative path flag determination is done by a shared function. */
  if( determine_cwd_relative_option() ){
    flags |= C_RELPATH;
  }

712
713
714
715
716
717
718
719

720
721
722



723
724
725
726
727
728
729
712
713
714
715
716
717
718

719
720
721
722
723
724
725
726
727
728
729
730
731
732







-
+



+
+
+







    print_filelist_section(zAll, zLast, "", 0);
  }
}

/*
** Take care of -r version of ls command
*/
static void ls_cmd_rev(
void ls_cmd_rev(
  const char *zRev,  /* Revision string given */
  int verboseFlag,   /* Verbose flag given */
  int showAge,       /* Age flag given */
  int showFileHash,  /* Show file hash flag given */
  int showCkinHash,  /* Show check-in hash flag given */
  int showCkinInfo,  /* Show check-in infos */
  int timeOrder,     /* Order by time flag given */
  int treeFmt        /* Show output in the tree format */
){
  Stmt q;
  char *zOrderBy = "pathname COLLATE nocase";
  char *zName;
  Blob where;
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
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
809
810
811
812
813
814
815
816
817
818

819
820
821
822
823
824
825
826
827







+
-
+

+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+










+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+







  }

  if( timeOrder ){
    zOrderBy = "mtime DESC";
  }

  compute_fileage(rid,0);
  if( showCkinInfo ){
  db_prepare(&q,
    db_prepare(&q,
    "SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n"
    "       bfh.size, fileage.uuid, bch.uuid,\n"
    "       coalesce(e.ecomment, e.comment), coalesce(e.euser, e.user)\n"
    "  FROM fileage, blob bfh, blob bch, event e\n"
    " WHERE bfh.rid=fileage.fid AND bch.rid=fileage.mid\n"
    "   AND e.objid = fileage.mid %s\n"
    " ORDER BY %s;",
        blob_sql_text(&where),
        zOrderBy /*safe-for-%s*/
    );
  }else{
    db_prepare(&q,
    "SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n"
    "       blob.size\n"
    "  FROM fileage, blob\n"
    " WHERE blob.rid=fileage.fid %s\n"
    " ORDER BY %s;", blob_sql_text(&where), zOrderBy /*safe-for-%s*/
  );
    "       bfh.size, fileage.uuid %s\n"
    "  FROM fileage, blob bfh %s\n"
    " WHERE bfh.rid=fileage.fid %s %s\n"
    " ORDER BY %s;",
        showCkinHash ? ", bch.uuid" : "",
        showCkinHash ? ", blob bch" : "",
        showCkinHash ? "\n   AND bch.rid=fileage.mid" : "",
        blob_sql_text(&where),
        zOrderBy /*safe-for-%s*/
    );
  }
  blob_reset(&where);
  if( treeFmt ) blob_init(&out, 0, 0);

  while( db_step(&q)==SQLITE_ROW ){
    const char *zTime = db_column_text(&q,0);
    const char *zFile = db_column_text(&q,1);
    int size = db_column_int(&q,2);
    if( treeFmt ){
      blob_appendf(&out, "%s\n", zFile);
    }else if( verboseFlag ){
      if( showCkinInfo ){
        const char *zUuidC = db_column_text(&q,4);
        const char *zComm = db_column_text(&q,5);
        const char *zUser = db_column_text(&q,6);     
        fossil_print("%s  [%S]  %12s  ", zTime, zUuidC, zUser);
        if( showCkinInfo==2 ) fossil_print("%-20.20s  ", zComm);
        fossil_print("%s\n", zFile);
      }else if( showFileHash ){
        const char *zUuidF = db_column_text(&q,3);
        fossil_print("%s  %7d  [%S]  %s\n", zTime, size, zUuidF, zFile);
      }else if( showCkinHash ){
        const char *zUuidC = db_column_text(&q,4);
        fossil_print("%s  %7d  [%S]  %s\n", zTime, size, zUuidC, zFile);
      }else{
      fossil_print("%s  %7d  %s\n", zTime, size, zFile);
        fossil_print("%s  %7d  %s\n", zTime, size, zFile);
      }
    }else if( showAge ){
      fossil_print("%s  %s\n", zTime, zFile);
    }else{
      fossil_print("%s\n", zFile);
    }
  }
  db_finalize(&q);
809
810
811
812
813
814
815
816



817
818
819
820
821
822
823


824
825
826
827
828
829
830
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







-
+
+
+







+
+







** option as well, as explained below.
**
** The --age option displays file commit times.  Like -r, --age has the
** side effect of making -t sort by commit time, not modification time.
**
** The -v option provides extra information about each file.  Without -r,
** -v displays the change status, in the manner of the changes command.
** With -r, -v shows the commit time and size of the checked-in files.
** With -r, -v shows the commit time and size of the checked-in files; in
** this combination, it additionally shows file hashes with -h, or check-in
** hashes with -H (when both are given, file hashes take precedence).
**
** The -t option changes the sort order.  Without -t, files are sorted by
** path and name (case insensitive sort if -r).  If neither --age nor -r
** are used, -t sorts by modification time, otherwise by commit time.
**
** Options:
**   --age                 Show when each file was committed
**   -h                    With -v and -r, show file hashes
**   -H                    With -v and -r, show check-in hashes
**   --hash                With -v, verify file status using hashing
**                         rather than relying on file sizes and mtimes
**   -r VERSION            The specific check-in to list
**   -R|--repository REPO  Extract info from repository REPO
**   -t                    Sort output in time order
**   --tree                Tree format
**   -v|--verbose          Provide extra information about each file
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
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
920
921
922







+
+












+
+
+
+
+









+
-
+







  int showAge;
  int treeFmt;
  int timeOrder;
  char *zOrderBy = "pathname";
  Blob where;
  int i;
  int useHash = 0;
  int showFHash = 0;  /* Show file hash */
  int showCHash = 0;  /* Show check-in hash */
  const char *zName;
  const char *zRev;

  verboseFlag = find_option("verbose","v", 0)!=0;
  if( !verboseFlag ){
    verboseFlag = find_option("l","l", 0)!=0; /* deprecated */
  }
  showAge = find_option("age",0,0)!=0;
  zRev = find_option("r","r",1);
  timeOrder = find_option("t","t",0)!=0;
  if( verboseFlag ){
    useHash = find_option("hash",0,0)!=0;
    showFHash = find_option("h","h",0)!=0;
    showCHash = find_option("H","H",0)!=0;
    if( showFHash ){
      showCHash = 0;  /* file hashes take precedence */
    }
  }
  treeFmt = find_option("tree",0,0)!=0;
  if( treeFmt ){
    if( zRev==0 ) zRev = "current";
  }

  if( zRev!=0 ){
    db_find_and_open_repository(0, 0);
    verify_all_options();
    ls_cmd_rev(zRev, verboseFlag, showAge, showFHash, showCHash, 0, timeOrder,
    ls_cmd_rev(zRev,verboseFlag,showAge,timeOrder,treeFmt);
               treeFmt);
    return;
  }else if( find_option("R",0,1)!=0 ){
    fossil_fatal("the -r is required in addition to -R");
  }

  db_must_be_within_tree();
  vid = db_lget_int("checkout", 0);
982
983
984
985
986
987
988
989

990
991
992
993
994
995
996
1031
1032
1033
1034
1035
1036
1037

1038
1039
1040
1041
1042
1043
1044
1045







-
+







void tree_cmd(void){
  const char *zRev;

  zRev = find_option("r","r",1);
  if( zRev==0 ) zRev = "current";
  db_find_and_open_repository(0, 0);
  verify_all_options();
  ls_cmd_rev(zRev,0,0,0,1);
  ls_cmd_rev(zRev,0,0,0,0,0,0,1);
}

/*
** COMMAND: extras
**
** Usage: %fossil extras ?OPTIONS? ?PATH1 ...?
**
1384
1385
1386
1387
1388
1389
1390
1391

1392
1393
1394
1395
1396
1397
1398
1433
1434
1435
1436
1437
1438
1439

1440
1441
1442
1443
1444
1445
1446
1447







-
+







    blob_zero(&fname);
    if( g.zLocalRoot!=0 ){
      file_relative_name(g.zLocalRoot, &fname, 1);
      zFile = db_text(0, "SELECT '%qci-comment-'||hex(randomblob(6))||'.txt'",
                      blob_str(&fname));
    }else{
      file_tempname(&fname, "ci-comment",0);
      zFile = mprintf("%s", blob_str(&fname));
      zFile = fossil_strdup(blob_str(&fname));
    }
    blob_reset(&fname);
  }
#if defined(_WIN32)
  blob_add_cr(pPrompt);
#endif
  if( blob_size(pPrompt)>0 ) blob_write_to_file(pPrompt, zFile);
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
1492
1493
1494
1514
1515
1516
1517
1518
1519
1520
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







+












-
-
-
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  Blob *pComment,
  char *zInit,
  CheckinInfo *p,
  int parent_rid,
  int dryRunFlag
){
  Blob prompt;
  int wikiFlags;
#if defined(_WIN32) || defined(__CYGWIN__)
  int bomSize;
  const unsigned char *bom = get_utf8_bom(&bomSize);
  blob_init(&prompt, (const char *) bom, bomSize);
  if( zInit && zInit[0]){
    blob_append(&prompt, zInit, -1);
  }
#else
  blob_init(&prompt, zInit, -1);
#endif
  blob_append(&prompt,
    "\n"
    "# Enter a commit message for this check-in."
        " Lines beginning with # are ignored.\n"
    "#\n", -1
    "# Enter the commit message.  Formatting rules:\n"
    "#   *  Lines beginning with # are ignored.\n",
    -1
  );
  wikiFlags = wiki_convert_flags(1);
  if( wikiFlags & WIKI_LINKSONLY ){
    blob_append(&prompt,"#   *  Hyperlinks inside of [...]\n", -1);
    if( wikiFlags & WIKI_NEWLINE ){
      blob_append(&prompt,
        "#   *  Newlines are significant and are displayed as written\n", -1);
    }else{
      blob_append(&prompt,
        "#   *  Newlines are interpreted as ordinary spaces\n",
        -1
      );
    }
    blob_append(&prompt,
        "#   *  All other text will be displayed as written\n", -1);
  }else{
    blob_append(&prompt,
       "#   *  Hyperlinks:   [target]   or   [target|display-text]\n"
       "#   *  Blank lines cause a paragraph break\n"
       "#   *  Other text rendered as if it were HTML\n", -1
    );
  }
  blob_append(&prompt, "#\n", 2);

  if( dryRunFlag ){
    blob_appendf(&prompt, "# DRY-RUN:  This is a test commit.  No changes "
                          "will be made to the repository\n#\n");
  }
  blob_appendf(&prompt, "# user: %s\n",
               p->zUserOvrd ? p->zUserOvrd : login_name());
  if( p->zBranch && p->zBranch[0] ){
1561
1562
1563
1564
1565
1566
1567
1568

1569
1570
1571
1572
1573
1574

1575
1576
1577
1578
1579
1580
1581
1634
1635
1636
1637
1638
1639
1640

1641
1642
1643
1644
1645
1646

1647
1648
1649
1650
1651
1652
1653
1654







-
+





-
+







          diffFiles[0].zName[0] = '.';
          diffFiles[0].zName[1] = 0;
          break;
        }
        diffFiles[i].nName = strlen(diffFiles[i].zName);
        diffFiles[i].nUsed = 0;
      }
      diff_against_disk(0, &DCfg, diffFiles, &prompt);
      diff_version_to_checkout(0, &DCfg, diffFiles, &prompt);
      for( i=0; diffFiles[i].zName; ++i ){
        fossil_free(diffFiles[i].zName);
      }
      fossil_free(diffFiles);
    }else{
      diff_against_disk(0, &DCfg, 0, &prompt);
      diff_version_to_checkout(0, &DCfg, 0, &prompt);
    }
  }
  prompt_for_user_comment(pComment, &prompt);
  blob_reset(&prompt);
}

/*
1591
1592
1593
1594
1595
1596
1597

1598
1599
1600
1601
1602
1603
1604
1605
1606

1607
1608
1609
1610
1611
1612
1613
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679

1680
1681
1682
1683
1684
1685
1686
1687







+








-
+







  int parent_rid,     /* parent check-in */
  Blob *pComment,     /* Check-in comment */
  int dryRunFlag      /* True for a dry-run only */
){
  Blob *pDesc;
  char *zTags;
  char *zFilename;
  const char *zMainBranch = db_main_branch();
  Blob desc;
  blob_init(&desc, 0, 0);
  pDesc = &desc;
  blob_appendf(pDesc, "checkout %s\n", g.zLocalRoot);
  blob_appendf(pDesc, "repository %s\n", g.zRepositoryName);
  blob_appendf(pDesc, "user %s\n",
               p->zUserOvrd ? p->zUserOvrd : login_name());
  blob_appendf(pDesc, "branch %s\n",
    (p->zBranch && p->zBranch[0]) ? p->zBranch : "trunk");
    (p->zBranch && p->zBranch[0]) ? p->zBranch : zMainBranch);
  zTags = info_tags_of_checkin(parent_rid, 1);
  if( zTags || p->azTag ){
    blob_append(pDesc, "tags ", -1);
    if(zTags){
      blob_appendf(pDesc, "%z%s", zTags, p->azTag ? ", " : "");
    }
    if(p->azTag){
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
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
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
2444
2445
2446
2447



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
2480
2481


2482

2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
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
2519
2520
2521
2522
2523


2524
2525
2526
2527
2528

2529



2530



2531
2532
2533
2534
2535
2536
2537







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







-
-
-
+
+
+
+
+
+
+
-
-
-
+
+
-
-
-
-
+
+
+


-
+
-
-
-
-
-
-
-
-
-
-
-





-
+









-
-
-
-




-
-
+
-
+











-
+
+
+

+

-
+
+
+




-
+

-
-
+
+
-
-
+
+






+



-
-
+
+
+
+

-
+
-
-
-
+
-
-
-







*/
static int tagCmp(const void *a, const void *b){
  char **pA = (char**)a;
  char **pB = (char**)b;
  return fossil_strcmp(pA[0], pB[0]);
}

/*
** SETTING: verify-comments                              width=8 default=on
**
** This setting determines how much sanity checking, if any, the 
** "fossil commit" and "fossil amend" commands do against check-in
** comments. Recognized values:
**
**     on         (Default) Check for bad syntax and/or broken hyperlinks
**                in check-in comments and offer the user a chance to
**                continue editing for interactive sessions, or simply
**                abort the commit if the comment was entered using -m or -M
**
**     off        Do not do syntax checking of any kind
**
**     preview    Do all the same checks as "on" but also always preview the
**                check-in comment to the user during interactive sessions
**                even if no obvious errors are found, and provide an
**                opportunity to accept or re-edit
*/

#if INTERFACE
#define COMCK_MARKUP    0x01  /* Check for mistakes */
#define COMCK_PREVIEW   0x02  /* Always preview, even if no issues found */
#endif /* INTERFACE */

/*
** Check for possible formatting errors in the comment string pComment.
**
** If issues are found, write an appropriate error notice, probably also
** including the complete text of the comment formatted to highlight the
** problem, to stdout and return non-zero.  The return value is some
** combination of the COMCK_* flags, depending on what went wrong.
**
** If no issues are seen, do not output anything and return zero.
*/
int verify_comment(Blob *pComment, int mFlags){
  Blob in, html;
  int mResult;
  int rc = mFlags & COMCK_PREVIEW;
  int wFlags;

  if( mFlags==0 ) return 0;
  blob_init(&in, blob_str(pComment), -1);
  blob_init(&html, 0, 0);
  wFlags = wiki_convert_flags(0);
  wFlags &= ~WIKI_NOBADLINKS;
  wFlags |= WIKI_MARK;
  mResult = wiki_convert(&in, &html, wFlags);
  if( mResult & RENDER_ANYERROR ) rc |= COMCK_MARKUP;
  if( rc ){
    int htot = ((wFlags & WIKI_NEWLINE)!=0 ? 0 : HTOT_FLOW)|HTOT_TRIM;
    Blob txt;
    if( terminal_is_vt100() ) htot |= HTOT_VT100;
    blob_init(&txt, 0, 0);
    html_to_plaintext(blob_str(&html), &txt, htot);
    if( rc & COMCK_MARKUP ){
      fossil_print("Possible format errors in the check-in comment:\n\n   ");
    }else{
      fossil_print("Preview of the check-in comment:\n\n   ");
    }
    if( wFlags & WIKI_NEWLINE ){
      Blob line;
      char *zIndent = "";
      while( blob_line(&txt, &line) ){
        fossil_print("%s%b", zIndent, &line);
        zIndent = "   ";
      }
      fossil_print("\n");
    }else{
      comment_print(blob_str(&txt), 0, 3, -1, get_comment_format());
    }
    fossil_print("\n");
    fflush(stdout);
    blob_reset(&txt);
  }
  blob_reset(&html);
  blob_reset(&in);
  return rc;
} 

/*
** COMMAND: ci#
** COMMAND: commit
**
** Usage: %fossil commit ?OPTIONS? ?FILE...?
**    or: %fossil ci ?OPTIONS? ?FILE...?
**
** Create a new version containing all of the changes in the current
** check-out.  You will be prompted to enter a check-in comment unless
** the comment has been specified on the command-line using "-m" or a
** Create a new check-in containing all of the changes in the current
** check-out.  All changes are committed unless some subset of files
** is specified on the command line, in which case only the named files
** become part of the new check-in.
**
** You will be prompted to enter a check-in comment unless the comment
** has been specified on the command-line using "-m" or "-M".  The
** file containing the comment using -M.  The editor defined in the
** "editor" fossil option (see %fossil help set) will be used, or from
** the "VISUAL" or "EDITOR" environment variables (in that order) if
** text editor used is determined by the "editor" setting, or by the
** "VISUAL" or "EDITOR" environment variables.  Commit message text is
** no editor is set.
**
** All files that have changed will be committed unless some subset of
** files is specified on the command line.
** interpreted as fossil-wiki format.  Potentially misformatted check-in
** comment text is detected and reported unless the --no-verify-comment
** option is used.
**
** The --branch option followed by a branch name causes the new
** check-in to be placed in a newly-created branch with the name
** check-in to be placed in a newly-created branch with name specified.
** passed to the --branch option.
**
** Use the --branchcolor option followed by a color name (ex:
** '#ffc0c0') to specify the background color of entries in the new
** branch when shown in the web timeline interface.  The use of
** the --branchcolor option is not recommended.  Instead, let Fossil
** choose the branch color automatically.
**
** The --bgcolor option works like --branchcolor but only sets the
** background color for a single check-in.  Subsequent check-ins revert
** to the default color.
**
** A check-in is not permitted to fork unless the --allow-fork option
** appears.  An empty check-in (i.e. with nothing changed) is not
** allowed unless the --allow-empty option appears.  A check-in may not
** be older than its ancestor unless the --allow-older option appears.
** If any of files in the check-in appear to contain unresolved merge
** If any files in the check-in appear to contain unresolved merge
** conflicts, the check-in will not be allowed unless the
** --allow-conflict option is present.  In addition, the entire
** check-in process may be aborted if a file contains content that
** appears to be binary, Unicode text, or text with CR/LF line endings
** unless the interactive user chooses to proceed.  If there is no
** interactive user or these warnings should be skipped for some other
** reason, the --no-warnings option may be used.  A check-in is not
** allowed against a closed leaf.
**
** If a commit message is blank, you will be prompted:
** ("continue (y/N)?") to confirm you really want to commit with a
** blank commit message.  The default value is "N", do not commit.
**
** The --private option creates a private check-in that is never synced.
** Children of private check-ins are automatically private.
**
** The --tag option applies the symbolic tag name to the check-in.
**
** The --hash option detects edited files by computing each file's
** The --tag option can be repeated to assign multiple tags to a check-in.
** artifact hash rather than just checking for changes to its size or mtime.
** For example: "... --tag release --tag version-1.2.3 ..."
**
** Options:
**    --allow-conflict           Allow unresolved merge conflicts
**    --allow-empty              Allow a commit with no changes
**    --allow-fork               Allow the commit to fork
**    --allow-older              Allow a commit older than its ancestor
**    --baseline                 Use a baseline manifest in the commit process
**    --bgcolor COLOR            Apply COLOR to this one check-in only
**    --branch NEW-BRANCH-NAME   Check in to this new branch
**    --branchcolor COLOR        Apply given COLOR to the branch
**    --close                    Close the branch being committed
**    --date-override DATETIME   DATE to use instead of 'now'
**    --date-override DATETIME   Make DATETIME the time of the check-in.
**                               Useful when importing historical check-ins
**                               from another version control system.
**    --delta                    Use a delta manifest in the commit process
**    --editor NAME              Text editor to use for check-in comment.
**    --hash                     Verify file status using hashing rather
**                               than relying on file mtimes
**                               than relying on filesystem mtimes
**    --if-changes               Make this command a silent no-op if there
**                               are no changes
**    --ignore-clock-skew        If a clock skew is detected, ignore it and
**                               behave as if the user had entered 'yes' to
**                               the question of whether to proceed despite
**                               the skew.
**    --ignore-oversize          Do not warning the user about oversized files
**    --ignore-oversize          Do not warn the user about oversized files
**    --integrate                Close all merged-in branches
**    -m|--comment COMMENT-TEXT  Use COMMENT-TEXT as commit comment
**    -M|--message-file FILE     Read the commit comment from given file
**    -m|--comment COMMENT-TEXT  Use COMMENT-TEXT as the check-in comment
**    -M|--message-file FILE     Read the check-in comment from FILE
**    --mimetype MIMETYPE        Mimetype of check-in comment
**    -n|--dry-run               If given, display instead of run actions
**    -n|--dry-run               Do not actually create a new check-in. Just
**                               show what would have happened. For debugging.
**    -v|--verbose               Show a diff in the commit message prompt
**    --no-prompt                This option disables prompting the user for
**                               input and assumes an answer of 'No' for every
**                               question.
**    --no-warnings              Omit all warnings about file contents
**    --no-verify                Do not run before-commit hooks
**    --no-verify-comment        Do not validate the check-in comment
**    --nosign                   Do not attempt to sign this commit with gpg
**    --nosync                   Do not auto-sync prior to committing
**    --override-lock            Allow a check-in even though parent is locked
**    --private                  Do not sync changes and their descendants
**    --tag TAG-NAME             Assign given tag TAG-NAME to the check-in
**    --private                  Never sync the resulting check-in and make
**                               all descendants private too.
**    --proxy PROXY              Use PROXY as http proxy during sync operation
**    --tag TAG-NAME             Add TAG-NAME to the check-in. May be repeated.
**    --trace                    Debug tracing
**    --user-override USER       USER to use instead of the current default
**    --user-override USER       Record USER as the login that created the
**
** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in
** year-month-day form, it may be truncated, the "T" may be replaced by
**                               new check-in, rather that the current user.
** a space, and it may also name a timezone offset from UTC as "-HH:MM"
** (westward) or "+HH:MM" (eastward). Either no timezone suffix or "Z"
** means UTC.
**
** See also: [[branch]], [[changes]], [[update]], [[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 */
2407
2408
2409
2410
2411
2412
2413

2414
2415

2416
2417
2418
2419
2420
2421
2422
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567







+


+







  int bTrace = 0;        /* Debug tracing */
  int noPrompt = 0;      /* True if skipping all prompts */
  int forceFlag = 0;     /* Undocumented: Disables all checks */
  int forceDelta = 0;    /* Force a delta-manifest */
  int forceBaseline = 0; /* Force a baseline-manifest */
  int allowConflict = 0; /* Allow unresolve merge conflicts */
  int allowEmpty = 0;    /* Allow a commit with no changes */
  int onlyIfChanges = 0; /* No-op if there are no changes */
  int allowFork = 0;     /* Allow the commit to fork */
  int allowOlder = 0;    /* Allow a commit older than its ancestor */
  int noVerifyCom = 0;   /* Allow suspicious check-in comments */
  char *zManifestFile;   /* Name of the manifest file */
  int useCksum;          /* True if checksums should be computed and verified */
  int outputManifest;    /* True to output "manifest" and "manifest.uuid" */
  int dryRunFlag;        /* True for a test run.  Debugging only */
  CheckinInfo sCiInfo;   /* Information about this check-in */
  const char *zComFile;  /* Read commit message from this file */
  int nTag = 0;          /* Number of --tag arguments */
2432
2433
2434
2435
2436
2437
2438



2439
2440
2441
2442

2443
2444
2445
2446
2447
2448
2449
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589

2590
2591
2592
2593
2594
2595
2596
2597







+
+
+



-
+







  int nConflict = 0;     /* Number of unresolved merge conflicts */
  int abortCommit = 0;   /* Abort the commit due to text format conversions */
  Blob ans;              /* Answer to continuation prompts */
  char cReply;           /* First character of ans */
  int bRecheck = 0;      /* Repeat fork and closed-branch checks*/
  int bIgnoreSkew = 0;   /* --ignore-clock-skew flag */
  int mxSize;
  char *zCurBranch = 0;  /* The current branch name of checkout */
  char *zNewBranch = 0;  /* The branch name after update */
  int ckComFlgs;         /* Flags passed to verify_comment() */

  memset(&sCiInfo, 0, sizeof(sCiInfo));
  url_proxy_options();
  /* --sha1sum is an undocumented alias for --hash for backwards compatiblity */
  /* --sha1sum is an undocumented alias for --hash for backwards compatibility */
  useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0;
  noSign = find_option("nosign",0,0)!=0;
  if( find_option("nosync",0,0) ) g.fNoSync = 1;
  privateFlag = find_option("private",0,0)!=0;
  forceDelta = find_option("delta",0,0)!=0;
  forceBaseline = find_option("baseline",0,0)!=0;
  db_must_be_within_tree();
2462
2463
2464
2465
2466
2467
2468


2469
2470
2471
2472
2473
2474
2475
2476




2477
2478




2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
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
2519
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
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
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726







+
+








+
+
+
+
-
-
+
+
+
+


-















-
+


+

+
+
+
+
+
+






-
+





+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  if( !dryRunFlag ){
    dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
  }
  zComment = find_option("comment","m",1);
  forceFlag = find_option("force", "f", 0)!=0;
  allowConflict = find_option("allow-conflict",0,0)!=0;
  allowEmpty = find_option("allow-empty",0,0)!=0;
  noVerifyCom = find_option("no-verify-comment",0,0)!=0;
  onlyIfChanges = find_option("if-changes",0,0)!=0;
  allowFork = find_option("allow-fork",0,0)!=0;
  if( find_option("override-lock",0,0)!=0 ) allowFork = 1;
  allowOlder = find_option("allow-older",0,0)!=0;
  noPrompt = find_option("no-prompt", 0, 0)!=0;
  noWarningFlag = find_option("no-warnings", 0, 0)!=0;
  noVerify = find_option("no-verify",0,0)!=0;
  bTrace = find_option("trace",0,0)!=0;
  sCiInfo.zBranch = find_option("branch","b",1);

  /* NB: the --bgcolor and --branchcolor flags still work, but are
  ** now undocumented, to discourage their use. --mimetype has never
  ** been used for anything, so also leave it undocumented */
  sCiInfo.zColor = find_option("bgcolor",0,1);
  sCiInfo.zBrClr = find_option("branchcolor",0,1);
  sCiInfo.zColor = find_option("bgcolor",0,1);     /* Deprecated, undocumented*/
  sCiInfo.zBrClr = find_option("branchcolor",0,1); /* Deprecated, undocumented*/
  sCiInfo.zMimetype = find_option("mimetype",0,1); /* Deprecated, undocumented*/

  sCiInfo.closeFlag = find_option("close",0,0)!=0;
  sCiInfo.integrateFlag = find_option("integrate",0,0)!=0;
  sCiInfo.zMimetype = find_option("mimetype",0,1);
  sCiInfo.verboseFlag = find_option("verbose", "v", 0)!=0;
  while( (zTag = find_option("tag",0,1))!=0 ){
    if( zTag[0]==0 ) continue;
    sCiInfo.azTag = fossil_realloc((void*)sCiInfo.azTag,
                                    sizeof(char*)*(nTag+2));
    sCiInfo.azTag[nTag++] = zTag;
    sCiInfo.azTag[nTag] = 0;
  }
  zComFile = find_option("message-file", "M", 1);
  sCiInfo.zDateOvrd = find_option("date-override",0,1);
  sCiInfo.zUserOvrd = find_option("user-override",0,1);
  noSign = db_get_boolean("omitsign", 0)|noSign;
  if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; }
  useCksum = db_get_boolean("repo-cksum", 1);
  bIgnoreSkew = find_option("ignore-clock-skew",0,0)!=0;
  outputManifest = db_get_manifest_setting();
  outputManifest = db_get_manifest_setting(0);
  mxSize = db_large_file_size();
  if( find_option("ignore-oversize",0,0)!=0 ) mxSize = 0;
  (void)fossil_text_editor();
  verify_all_options();

  /* The --no-warnings flag and the --force flag each imply
  ** the --no-verify-comment flag */
  if( noWarningFlag || forceFlag ){
    noVerifyCom = 1;
  }

  /* Get the ID of the parent manifest artifact */
  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    useCksum = 1;
    if( privateFlag==0 && sCiInfo.zBranch==0 ) {
      sCiInfo.zBranch=db_get("main-branch", 0);
      sCiInfo.zBranch = db_main_branch();
    }
  }else{
    privateParent = content_is_private(vid);
  }

  user_select();
  /*
  ** Check that the user exists.
  */
  if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){
    fossil_fatal("no such user: %s", g.zLogin);
  }

  /*
  ** Detect if the branch name has changed from the parent check-in
  ** and prompt if necessary
  **/
  zCurBranch = db_text(0,
      " SELECT value FROM tagxref AS tx"
      "  WHERE rid=(SELECT pid"
      "               FROM tagxref LEFT JOIN event ON srcid=objid"
      "          LEFT JOIN plink ON rid=cid"
      "              WHERE rid=%d AND tagxref.tagid=%d"
      "                AND srcid!=origid"
      "                AND tagtype=2 AND coalesce(euser,user)!=%Q)"
      "   AND tx.tagid=%d",
      vid, TAG_BRANCH, g.zLogin, TAG_BRANCH
  );
  if( zCurBranch!=0 && zCurBranch[0]!=0
   && forceFlag==0
   && noPrompt==0
  ){
    zNewBranch = branch_of_rid(vid);
    fossil_warning(
      "WARNING: The parent check-in [%.10s] has been moved from branch\n"
      "         '%s' over to branch '%s'.",
      rid_to_uuid(vid), zCurBranch, zNewBranch
   );
    prompt_user("Commit anyway? (y/N) ", &ans);
    cReply = blob_str(&ans)[0];
    blob_reset(&ans);
    if( cReply!='y' && cReply!='Y' ){
      fossil_fatal("Abandoning commit because branch has changed");
    }
    fossil_free(zNewBranch);
    fossil_free(zCurBranch);
    zCurBranch = branch_of_rid(vid);
  }
  if( zCurBranch==0 ) zCurBranch = branch_of_rid(vid);

  /* Track the "private" status */
  g.markPrivate = privateFlag || privateParent;
  if( privateFlag && !privateParent ){
    /* Apply default branch name ("private") and color ("orange") if not
    ** specified otherwise on the command-line, and if the parent is not
    ** already private. */
    if( sCiInfo.zBranch==0 ) sCiInfo.zBranch = "private";
2532
2533
2534
2535
2536
2537
2538






2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554



2555
2556
2557
2558
2559


2560
2561
2562

2563
2564

2565
2566
2567

2568
2569
2570
2571

2572
2573
2574
2575
2576

2577
2578
2579
2580
2581
2582
2583
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766

2767
2768
2769
2770
2771
2772
2773

2774
2775
2776
2777

2778
2779

2780
2781
2782

2783
2784
2785
2786

2787
2788
2789

2790

2791
2792
2793
2794
2795
2796
2797
2798







+
+
+
+
+
+















-
+
+
+




-
+
+


-
+

-
+


-
+



-
+


-

-
+








  /* Escape special characters in tags and put all tags in sorted order */
  if( nTag ){
    int i;
    for(i=0; i<nTag; i++) sCiInfo.azTag[i] = mprintf("%F", sCiInfo.azTag[i]);
    qsort((void*)sCiInfo.azTag, nTag, sizeof(sCiInfo.azTag[0]), tagCmp);
  }

  hasChanges = unsaved_changes(useHash ? CKSIG_HASH : 0);
  if( hasChanges==0 && onlyIfChanges ){
    /* "fossil commit --if-changes" is a no-op if there are no changes. */
    return;
  }

  /*
  ** Autosync if autosync is enabled and this is not a private check-in.
  */
  if( !g.markPrivate ){
    int syncFlags = SYNC_PULL;
    if( vid!=0 && !allowFork && !forceFlag ){
      syncFlags |= SYNC_CKIN_LOCK;
    }
    if( autosync_loop(syncFlags, 1, "commit") ){
      fossil_exit(1);
    }
  }

  /* So that older versions of Fossil (that do not understand delta-
  ** manifest) can continue to use this repository, do not create a new
  ** manifest) can continue to use this repository, and because
  ** delta manifests are usually a bad idea unless the repository
  ** has a really large number of files, do not create a new
  ** delta-manifest unless this repository already contains one or more
  ** delta-manifests, or unless the delta-manifest is explicitly requested
  ** by the --delta option.
  **
  ** The forbid-delta-manifests setting prevents new delta manifests.
  ** The forbid-delta-manifests setting prevents new delta manifests,
  ** even if the --delta option is used.
  **
  ** If the remote repository sent an avoid-delta-manifests pragma on
  ** the autosync above, then also try to avoid deltas, unless the
  ** the autosync above, then also forbid delta manifests, even if the
  ** --delta option is specified.  The remote repo will send the
  ** avoid-delta-manifests pragma if it has its "forbid-delta-manifests"
  ** avoid-delta-manifests pragma if its "forbid-delta-manifests"
  ** setting is enabled.
  */
  if( !db_get_boolean("seen-delta-manifest",0)
  if( !(forceDelta || db_get_boolean("seen-delta-manifest",0))
   || db_get_boolean("forbid-delta-manifests",0)
   || g.bAvoidDeltaManifests
  ){
    if( !forceDelta ) forceBaseline = 1;
    forceBaseline = 1;
  }


  /* Require confirmation to continue with the check-in if there is
  ** clock skew
  ** clock skew.  This helps to prevent timewarps.
  */
  if( g.clockSkewSeen ){
    if( bIgnoreSkew!=0 ){
      cReply = 'y';
      fossil_warning("Clock skew ignored due to --ignore-clock-skew.");
    }else if( !noPrompt ){
      prompt_user("continue in spite of time skew (y/N)? ", &ans);
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2852
2853
2854
2855
2856
2857
2858









2859
2860
2861
2862
2863
2864
2865







-
-
-
-
-
-
-
-
-







      const char *zTo = db_column_text(&q, 1);
      fossil_fatal("cannot do a partial commit of '%s' without '%s' because "
                   "'%s' was renamed to '%s'", zFrom, zTo, zFrom, zTo);
    }
    db_finalize(&q);
  }

  user_select();
  /*
  ** Check that the user exists.
  */
  if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){
    fossil_fatal("no such user: %s", g.zLogin);
  }

  hasChanges = unsaved_changes(useHash ? CKSIG_HASH : 0);
  db_begin_transaction();
  db_record_repository_filename(0);
  if( hasChanges==0 && !isAMerge && !allowEmpty && !forceFlag ){
    fossil_fatal("nothing has changed; use --allow-empty to override");
  }

  /* If none of the files that were named on the command line have
2711
2712
2713
2714
2715
2716
2717
2718






















2719
2720
2721














2722
2723
2724
2725
2726
2727
2728
2729





2730
2731
2732
2733





2734



2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
































2747
2748
2749
2750
2751
2752
2753
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989












2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028








+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



+
+
+
+
+
+
+
+
+
+
+
+
+
+








+
+
+
+
+




+
+
+
+
+

+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







     && (sCiInfo.zBranch==0
         || db_exists("SELECT 1 FROM tagxref"
                      " WHERE tagid=%d AND rid=%d AND tagtype>0"
                      "   AND value=%Q", TAG_BRANCH, vid, sCiInfo.zBranch))
    ){
      fossil_fatal("cannot commit against a closed leaf");
    }

    /* Require confirmation to continue with the check-in if the branch
    ** has changed and the committer did not provide the same branch
    */
    zNewBranch = branch_of_rid(vid);
    if( fossil_strcmp(zCurBranch, zNewBranch)!=0
     && fossil_strcmp(sCiInfo.zBranch, zNewBranch)!=0
     && forceFlag==0
     && noPrompt==0
    ){
      fossil_warning("parent check-in [%.10s] branch changed from '%s' to '%s'",
                     rid_to_uuid(vid), zCurBranch, zNewBranch);
      prompt_user("continue (y/N)? ", &ans);
      cReply = blob_str(&ans)[0];
      blob_reset(&ans);
      if( cReply!='y' && cReply!='Y' ){
        fossil_fatal("Abandoning commit because branch has changed");
      }
      fossil_free(zCurBranch);
      zCurBranch = branch_of_rid(vid);
    }
    fossil_free(zNewBranch);

    /* Always exit the loop on the second pass */
    if( bRecheck ) break;


    /* Figure out how much comment verification is requested */
    if( noVerifyCom ){
      ckComFlgs = 0;
    }else{
      const char *zVerComs = db_get("verify-comments","on");
      if( is_false(zVerComs) ){
        ckComFlgs = 0;
      }else if( strcmp(zVerComs,"preview")==0 ){
        ckComFlgs = COMCK_PREVIEW | COMCK_MARKUP;
      }else{
        ckComFlgs = COMCK_MARKUP;
      }
    }

    /* Get the check-in comment.  This might involve prompting the
    ** user for the check-in comment, in which case we should resync
    ** to renew the check-in lock and repeat the checks for conflicts.
    */
    if( zComment ){
      blob_zero(&comment);
      blob_append(&comment, zComment, -1);
      ckComFlgs &= ~COMCK_PREVIEW;
      if( verify_comment(&comment, ckComFlgs) ){
        fossil_fatal("Commit aborted; "
                     "use --no-verify-comment to override");
      }
    }else if( zComFile ){
      blob_zero(&comment);
      blob_read_from_file(&comment, zComFile, ExtFILE);
      blob_to_utf8_no_bom(&comment, 1);
      ckComFlgs &= ~COMCK_PREVIEW;
      if( verify_comment(&comment, ckComFlgs) ){
        fossil_fatal("Commit aborted; "
                     "use --no-verify-comment to override");
      }
    }else if( !noPrompt ){
      while(  1/*exit-by-break*/ ){
        int rc;
        char *zInit;
      char *zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'");
      prepare_commit_comment(&comment, zInit, &sCiInfo, vid, dryRunFlag);
      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];
        blob_reset(&ans);
        if( cReply!='y' && cReply!='Y' ){
          fossil_fatal("Commit aborted.");
        }
      }
      free(zInit);
      db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment);
        zInit = db_text(0,"SELECT value FROM vvar WHERE name='ci-comment'");
        prepare_commit_comment(&comment, zInit, &sCiInfo, vid, dryRunFlag);
        db_multi_exec("REPLACE INTO vvar VALUES('ci-comment',%B)", &comment);
        if( (rc = verify_comment(&comment, ckComFlgs))!=0 ){
          if( rc==COMCK_PREVIEW ){
            prompt_user("Continue, abort, or edit? (C/a/e)? ", &ans);
          }else{
            prompt_user("Edit, abort, or continue (E/a/c)? ", &ans);
          }
          cReply = blob_str(&ans)[0];
          cReply = fossil_tolower(cReply);
          blob_reset(&ans);
          if( cReply=='a' ){
            fossil_fatal("Commit aborted.");
          }
          if( cReply=='e' || (cReply!='c' && rc!=COMCK_PREVIEW) ){
            fossil_free(zInit);
            continue;
          }
        }
        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];
          blob_reset(&ans);
          if( cReply!='y' && cReply!='Y' ){
            fossil_fatal("Commit aborted.");
          }
        }
        fossil_free(zInit);
        break;
      }

      db_end_transaction(0);
      db_begin_transaction();
      if( !g.markPrivate && vid!=0 && !allowFork && !forceFlag ){
        /* Do another auto-pull, renewing the check-in lock.  Then set
        ** bRecheck so that we loop back above to verify that the check-in
        ** is still not against a closed branch and still won't fork. */
        int syncFlags = SYNC_PULL|SYNC_CKIN_LOCK;
Changes to src/checkout.c.
17
18
19
20
21
22
23

24
25
26
27
28
29
30
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31







+







**
** This file contains code used to check-out versions of the project
** from the local repository.
*/
#include "config.h"
#include "checkout.h"
#include <assert.h>
#include <zlib.h>

/*
** Check to see if there is an existing check-out that has been
** modified.  Return values:
**
**     0:   There is an existing check-out but it is unmodified
**     1:   There is a modified check-out - there are unsaved changes
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
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







-
-


-
+


-
+





+







** the text of the manifest and the artifact ID of the manifest.
** If the manifest setting is set, but is not a boolean value, then treat
** each character as a flag to enable writing "manifest", "manifest.uuid" or
** "manifest.tags".
*/
void manifest_to_disk(int vid){
  char *zManFile;
  Blob manifest;
  Blob taglist;
  int flg;

  flg = db_get_manifest_setting();
  flg = db_get_manifest_setting(0);

  if( flg & MFESTFLG_RAW ){
    blob_zero(&manifest);
    Blob manifest = BLOB_INITIALIZER;
    content_get(vid, &manifest);
    sterilize_manifest(&manifest, CFTYPE_MANIFEST);
    zManFile = mprintf("%smanifest", g.zLocalRoot);
    blob_write_to_file(&manifest, zManFile);
    free(zManFile);
    blob_reset(&manifest);
  }else{
    if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest'") ){
      zManFile = mprintf("%smanifest", g.zLocalRoot);
      file_delete(zManFile);
      free(zManFile);
    }
  }
205
206
207
208
209
210
211
212

213
214
215
216
217
218
219
205
206
207
208
209
210
211

212
213
214
215
216
217
218
219







-
+







    if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest.uuid'") ){
      zManFile = mprintf("%smanifest.uuid", g.zLocalRoot);
      file_delete(zManFile);
      free(zManFile);
    }
  }
  if( flg & MFESTFLG_TAGS ){
    blob_zero(&taglist);
    Blob taglist = BLOB_INITIALIZER;
    zManFile = mprintf("%smanifest.tags", g.zLocalRoot);
    get_checkin_taglist(vid, &taglist);
    blob_write_to_file(&taglist, zManFile);
    free(zManFile);
    blob_reset(&taglist);
  }else{
    if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest.tags'") ){
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
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







-
-
+
+

+



















-

-
+
+



+
+
+
+
+







** leaves files on disk unchanged, except the manifest and manifest.uuid
** files.
**
** The --latest flag can be used in place of VERSION to check-out the
** latest version in the repository.
**
** Options:
**    --force           Ignore edited files in the current check-out
**    --keep            Only update the manifest file(s)
**    -f|--force        Ignore edited files in the current check-out
**    -k|--keep         Only update the manifest file(s)
**    --force-missing   Force check-out even if content is missing
**    --prompt          Prompt before overwriting when --force is used
**    --setmtime        Set timestamps of all files to match their SCM-side
**                      times (the timestamp of the last check-in which modified
**                      them)
**
** See also: [[update]]
*/
void checkout_cmd(void){
  int forceFlag;                 /* Force check-out even if edits exist */
  int forceMissingFlag;          /* Force check-out even if missing content */
  int keepFlag;                  /* Do not change any files on disk */
  int latestFlag;                /* Check out the latest version */
  char *zVers;                   /* Version to check out */
  int promptFlag;                /* True to prompt before overwriting */
  int vid, prior;
  int setmtimeFlag;              /* --setmtime.  Set mtimes on files */
  Blob cksum1, cksum1b, cksum2;

  db_must_be_within_tree();
  db_begin_transaction();
  forceFlag = find_option("force","f",0)!=0;
  forceMissingFlag = find_option("force-missing",0,0)!=0;
  keepFlag = find_option("keep",0,0)!=0;
  keepFlag = find_option("keep","k",0)!=0;
  forceFlag = find_option("force","f",0)!=0;
  latestFlag = find_option("latest",0,0)!=0;
  promptFlag = find_option("prompt",0,0)!=0 || forceFlag==0;
  setmtimeFlag = find_option("setmtime",0,0)!=0;

  if( keepFlag != 0 ){
    /* After flag collection, in order not to affect promptFlag */
    forceFlag=1;
  }

  /* We should be done with options.. */
  verify_all_options();

  if( (latestFlag!=0 && g.argc!=2) || (latestFlag==0 && g.argc!=3) ){
     usage("VERSION|--latest ?--force? ?--keep?");
  }
422
423
424
425
426
427
428






























































































































































































































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
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
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
  if( db_is_writeable("repository") ){
    db_unset_mprintf(1, "ckout:%q", g.zLocalRoot);
  }
  unlink_local_database(1);
  db_close(1);
  unlink_local_database(0);
}


/*
** COMMAND: get
**
** Usage: %fossil get URL ?VERSION? ?OPTIONS?
**
** Download a single check-in from a remote repository named URL and
** unpack all of the files into a subdirectory.  The specific check-in
** to download is identified by VERSION.  If VERSION is omitted, the
** latest trunk check-in is used.  The URL can be a traditional "https:",
** "ssh:", or "file:" URL similar to the examples shown below, or it can
** be the name of a local repository/
**
**    *   https://domain.com/project
**    *   ssh://my-server/project.fossil
**    *   file:/home/user/Fossils/project.fossil
**
** This command works by downloading an SQL archive of the requested
** check-in and then extracting all the files from the archive.
**
** Options:
**   --dest DIRECTORY   Extract files into DIRECTORY.  Use "--dest ." to
**                      extract into the local directory.  If this option is
**                      omitted, Fossil invents a subdirectory name derived
**                      from base filename in the URL and from the VERSION.
**   -f|--force         Overwrite existing files
**   --list             List all the files that would have been checked
**                      out but do not actually write anything to the
**                      filesystem.
**   --sqlar ARCHIVE    Leave the check-out in an SQL-archive named ARCHIVE
**                      rather than unpacking into separate files.
**   -v|--verbose       Show all files as they are extracted
*/
void get_cmd(void){
  int forceFlag = find_option("force","f",0)!=0;
  int bVerbose = find_option("verbose","v",0)!=0;
  int bQuiet = g.fQuiet;
  int bDebug = find_option("debug",0,0)!=0;
  int bList = find_option("list",0,0)!=0;
  const char *zSqlArchive = find_option("sqlar",0,1);
  const char *z;
  char *zDest = 0;        /* Where to store results */
  char *zSql;             /* SQL used to query the results */
  const char *zUrl;       /* Url to get */
  const char *zVers;      /* Version name to get */
  unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS;
  Blob in, out;           /* I/O for the HTTP request */
  Blob file;              /* A file to extract */
  sqlite3 *db;            /* Database containing downloaded sqlar */
  sqlite3_stmt *pStmt;    /* Statement for querying the database */
  int rc;                 /* Result of subroutine calls */
  int nFile = 0;          /* Number of files written */
  int nDir = 0;           /* Number of directories written */
  i64 nByte = 0;          /* Number of bytes written */

  z = find_option("dest",0,1);
  if( z ) zDest = fossil_strdup(z);
  verify_all_options();
  if( g.argc<3 || g.argc>4 ){
    usage("URL ?VERSION? ?OPTIONS?");
  }
  zUrl = g.argv[2];
  zVers = g.argc==4 ? g.argv[3] : db_main_branch();

  /* Parse the URL of the repository */
  url_parse(zUrl, 0);

  /* Construct an appropriate name for the destination directory */
  if( zDest==0 ){
    int i;
    const char *zTail;
    const char *zDot;
    int n;
    if( g.url.isFile ){
      zTail = file_tail(g.url.name);
    }else{
      zTail = file_tail(g.url.path);
    }
    zDot = strchr(zTail,'.');
    if( zDot==0 ) zDot = zTail+strlen(zTail);
    n = (int)(zDot - zTail);
    zDest = mprintf("%.*s-%s", n, zTail, zVers);
    for(i=0; zDest[i]; i++){
      char c = zDest[i];
      if( !fossil_isalnum(c) && c!='-' && c!='^' && c!='~' && c!='_' ){
        zDest[i] = '-';
      }
    }
  }
  if( bDebug ){
    fossil_print("dest            = %s\n", zDest);
  }

  /* Error checking */
  if( zDest!=file_tail(zDest) ){
    fossil_fatal("--dest must be a simple directory name, not a path");
  }
  if( zVers!=file_tail(zVers) ){
    fossil_fatal("The \"fossil get\" command does not currently work with"
                 " version names that contain \"/\". This will be fixed in"
                 " a future release.");
  }
  /* To relax the restrictions above, change the subpath URL formula below
  ** to use query parameters.  Ex:  /sqlar?r=%t&name=%t */

  if( !forceFlag ){
    if( zSqlArchive ){
      if( file_isdir(zSqlArchive, ExtFILE)>0 ){
        fossil_fatal("file already exists: \"%s\"", zSqlArchive);
      }
    }else if( file_isdir(zDest, ExtFILE)>0 ){
      if( fossil_strcmp(zDest,".")==0 ){
        if( file_directory_list(zDest,0,1,1,0) ){
          fossil_fatal("current directory is not empty");
        }
      }else{
        fossil_fatal("\"%s\" already exists", zDest);
      }
    }
  }

  /* Construct a subpath on the URL if necessary */
  if( g.url.isFile ){
    g.url.subpath = mprintf("/sqlar/%t/%t.sqlar", zVers, zDest);
  }else{
    g.url.subpath = mprintf("%s/sqlar/%t/%t.sqlar", g.url.path, zVers, zDest);
  }

  if( bDebug ){
    urlparse_print(0);
  }

  /* Fetch the ZIP archive for the requested check-in */
  blob_init(&in, 0, 0);
  blob_init(&out, 0, 0);
  if( bDebug ) mHttpFlags |= HTTP_VERBOSE;
  if( bQuiet ) mHttpFlags |= HTTP_QUIET;
  rc = http_exchange(&in, &out, mHttpFlags, 4, 0);
  if( rc 
   || out.nUsed<512
   || (out.nUsed%512)!=0
   || memcmp(out.aData,"SQLite format 3",16)!=0
  ){
    fossil_fatal("Server did not return the requested check-in.");
  }

  if( zSqlArchive ){
    blob_write_to_file(&out, zSqlArchive);
    if( bVerbose ) fossil_print("%s\n", zSqlArchive);
    return;
  }

  rc = sqlite3_open(":memory:", &db);
  if( rc==SQLITE_OK ){
    int sz = blob_size(&out);
    rc = sqlite3_deserialize(db, 0, (unsigned char*)blob_buffer(&out), sz, sz,
                             SQLITE_DESERIALIZE_READONLY);
  }
  if( rc!=SQLITE_OK ){
    fossil_fatal("Cannot create an in-memory database: %s",
                 sqlite3_errmsg(db));
  }
  zSql = mprintf("SELECT name, mode, sz, data, mtime FROM sqlar"
                 " WHERE name GLOB '%q*'", zDest);
  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
  fossil_free(zSql);
  if( rc!=0 ){
    fossil_fatal("SQL error: %s\n", sqlite3_errmsg(db));
  }
  blob_init(&file, 0, 0);
  while( sqlite3_step(pStmt)==SQLITE_ROW ){
    const char *zFilename = (const char*)sqlite3_column_text(pStmt, 0);
    int mode = sqlite3_column_int(pStmt, 1);
    int sz = sqlite3_column_int(pStmt, 2);
    if( bList ){
      fossil_print("%s\n", zFilename);
    }else  if( mode & 0x4000 ){
      /* A directory name */
      nDir++;
      file_mkdir(zFilename, ExtFILE, 1);
    }else{
      /* A file */
      unsigned char *inBuf = (unsigned char*)sqlite3_column_blob(pStmt,3);
      unsigned int nIn = (unsigned int)sqlite3_column_bytes(pStmt,3);
      unsigned long int nOut2 = (unsigned long int)sz;
      nFile++;
      nByte += sz;
      blob_resize(&file, sz);
      if( nIn<sz ){
        rc = uncompress((unsigned char*)blob_buffer(&file), &nOut2,
                        inBuf, nIn);
        if( rc!=Z_OK ){
          fossil_fatal("Failed to uncompress file %s", zFilename);
        }
      }else{
        memcpy(blob_buffer(&file), inBuf, sz);
      }
      blob_write_to_file(&file, zFilename);
      if( mode & 0x40 ){
        file_setexe(zFilename, 1);
      }
      blob_zero(&file);
      file_set_mtime(zFilename, sqlite3_column_int64(pStmt,4));
      if( bVerbose ){
        fossil_print("%s\n", zFilename);
      }
    }
  }
  sqlite3_finalize(pStmt);
  sqlite3_close(db);
  blob_zero(&out);
  if( !bVerbose && !bQuiet && nFile>0 && zDest ){
    fossil_print("%d files (%,lld bytes) written into %s",
                 nFile, nByte, zDest);
    if( nDir>1 ){
      fossil_print(" and %d subdirectories\n", nDir-1);
    }else{
      fossil_print("\n");
    }
  }
}
Changes to src/clearsign.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
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







+







-

+
+
+
+
+
+
-
+
+







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+







** pOut.
*/
int clearsign(Blob *pIn, Blob *pOut){
  char *zRand;
  char *zIn;
  char *zOut;
  char *zBase = db_get("pgp-command", "gpg --clearsign -o ");
  int useSsh = 0;
  char *zCmd;
  int rc;
  if( is_false(zBase) ){
    return 0;
  }
  zRand = db_text(0, "SELECT hex(randomblob(10))");
  zOut = mprintf("out-%s", zRand);
  zIn = mprintf("in-%z", zRand);
  blob_write_to_file(pIn, zOut);
  useSsh = (fossil_strncmp(command_basename(zBase), "ssh", 3)==0);
  if( useSsh ){
    zIn = mprintf("out-%s.sig", zRand);
    zCmd = mprintf("%s %s", zBase, zOut);
  }else{
    zIn = mprintf("in-%z", zRand);
  zCmd = mprintf("%s %s %s", zBase, zIn, zOut);
    zCmd = mprintf("%s %s %s", zBase, zIn, zOut);
  }
  rc = fossil_system(zCmd);
  free(zCmd);
  if( rc==0 ){
    if( pOut==pIn ){
      blob_reset(pIn);
    }
    blob_zero(pOut);
    if( useSsh ){
        /* As of 2025, SSH cannot create non-detached SSH signatures */
        /* We put one together */
        Blob tmpBlob;
        blob_zero(&tmpBlob);
        blob_read_from_file(&tmpBlob, zOut, ExtFILE);
        /* Add armor header line and manifest */
        blob_appendf(pOut, "%s", "-----BEGIN SSH SIGNED MESSAGE-----\n\n");
        blob_appendf(pOut, "%s", blob_str(&tmpBlob));
        blob_zero(&tmpBlob);
        blob_read_from_file(&tmpBlob, zIn, ExtFILE);
        /* Add signature - already armored by SSH */
        blob_appendb(pOut, &tmpBlob);
    }else{
      /* Assume that the external command creates non-detached signatures */
    blob_read_from_file(pOut, zIn, ExtFILE);
      blob_read_from_file(pOut, zIn, ExtFILE);
    }
  }else{
    if( pOut!=pIn ){
      blob_copy(pOut, pIn);
    }
  }
  file_delete(zOut);
  file_delete(zIn);
Changes to src/clone.c.
127
128
129
130
131
132
133

134
135
136
137
138
139
140
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141







+







**    -B|--httpauth USER:PASS    Add HTTP Basic Authorization to requests
**    --nested                   Allow opening a repository inside an opened
**                               check-out
**    --nocompress               Omit extra delta compression
**    --no-open                  Clone only.  Do not open a check-out.
**    --once                     Don't remember the URI.
**    --private                  Also clone private branches
**    --proxy PROXY              Use the specified HTTP proxy
**    --save-http-password       Remember the HTTP password without asking
**    -c|--ssh-command SSH       Use SSH as the "ssh" command
**    --ssl-identity FILENAME    Use the SSL identity if requested by the server
**    --transport-command CMD    Use CMD to move messages to the server and back
**    -u|--unversioned           Also sync unversioned content
**    -v|--verbose               Show more statistics in output
**    --workdir DIR              Also open a check-out in DIR
345
346
347
348
349
350
351
352

353
354
355
356
357
358
359
346
347
348
349
350
351
352

353
354
355
356
357
358
359
360







-
+







*/
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 */
){
  if( zHttpAuth && zHttpAuth[0] ){
    g.zHttpAuth = mprintf("%s", zHttpAuth);
    g.zHttpAuth = fossil_strdup(zHttpAuth);
  }
  if( fRemember ){
    if( g.zHttpAuth && g.zHttpAuth[0] ){
      set_httpauth(g.zHttpAuth);
    }else if( zUrl && zUrl[0] ){
      db_unset_mprintf(0, "http-auth:%s", g.url.canonical);
    }else{
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
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
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
425
426
427
428

429
430
431
432
433
434
435
436
437







-
+
















-
+

-
-
+

-
+


-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-











-
+



+
+



** 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);
  if( zSshCmd && zSshCmd[0] ){
    g.zSshCmd = mprintf("%s", zSshCmd);
    g.zSshCmd = fossil_strdup(zSshCmd);
  }
}

/*
** Set SSH options discovered in global variables (set from command line
** options).
*/
void clone_ssh_db_set_options(void){
  if( g.zSshCmd && g.zSshCmd[0] ){
    db_unprotect(PROTECT_ALL);
    db_set("ssh-command", g.zSshCmd, 0);
    db_protect_pop();
  }
}

/*
** WEBPAGE: download
** WEBPAGE: howtoclone
**
** Provide a simple page that enables newbies to download the latest tarball or
** ZIP archive, and provides instructions on how to clone.
** Provide instructions on how to clone this repository.
*/
void download_page(void){
void howtoclone_page(void){
  login_check_credentials();
  cgi_check_for_malice();
  style_header("Download Page");
  style_header("How To Clone This Repository");
  if( !g.perm.Zip ){
    @ <p>Bummer.  You do not have permission to download.
    if( g.zLogin==0 || g.zLogin[0]==0 ){
      @ Maybe it would work better if you
      @ %z(href("%R/login"))logged in</a>.
    }else{
      @ Contact the site administrator and ask them to give
      @ you "Download Zip" privileges.
    }
  }else{
    const char *zDLTag = db_get("download-tag","trunk");
    const char *zNm = db_get("short-project-name","download");
    char *zUrl = href("%R/zip/%t/%t.zip", zDLTag, zNm);
    @ <p>ZIP Archive: %z(zUrl)%h(zNm).zip</a>
    zUrl = href("%R/tarball/%t/%t.tar.gz", zDLTag, zNm);
    @ <p>Tarball: %z(zUrl)%h(zNm).tar.gz</a>
    zUrl = href("%R/sqlar/%t/%t.sqlar", zDLTag, zNm);
    @ <p>SQLite Archive: %z(zUrl)%h(zNm).sqlar</a>
  }
  if( !g.perm.Clone ){
    @ <p>You are not authorized to clone this repository.
    if( g.zLogin==0 || g.zLogin[0]==0 ){
      @ Maybe you would be able to clone if you
      @ %z(href("%R/login"))logged in</a>.
    }else{
      @ Contact the site administrator and ask them to give
      @ you "Clone" privileges in order to clone.
    }
  }else{
    const char *zNm = db_get("short-project-name","clone");
    @ <p>Clone the repository using this command:
    @ <p>Clone this repository by running a command like the following:
    @ <blockquote><pre>
    @ fossil  clone  %s(g.zBaseURL)  %h(zNm).fossil
    @ </pre></blockquote>
    @ <p>Do a web search for "fossil clone" or similar to find additional
    @ information about using a cloned Fossil repository.
  }
  style_finish_page();
}
Changes to src/color.c.
18
19
20
21
22
23
24















































































































































































































































































25
26
27
28
29
30
31
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
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







** This file contains code used to select colors based on branch and
** user names.
**
*/
#include "config.h"
#include <string.h>
#include "color.h"

/*
** 140 standard CSS color names and their corresponding RGB values,
** in alphabetical order by name so that we can do a binary search
** for lookup.
*/
static const struct CssColors {
  const char *zName;     /* CSS Color name, lower case */
  unsigned int iRGB;     /* Corresponding RGB value */
} aCssColors[] = {
  { "aliceblue",            0xf0f8ff },
  { "antiquewhite",         0xfaebd7 },
  { "aqua",                 0x00ffff },
  { "aquamarine",           0x7fffd4 },
  { "azure",                0xf0ffff },
  { "beige",                0xf5f5dc },
  { "bisque",               0xffe4c4 },
  { "black",                0x000000 },
  { "blanchedalmond",       0xffebcd },
  { "blue",                 0x0000ff },
  { "blueviolet",           0x8a2be2 },
  { "brown",                0xa52a2a },
  { "burlywood",            0xdeb887 },
  { "cadetblue",            0x5f9ea0 },
  { "chartreuse",           0x7fff00 },
  { "chocolate",            0xd2691e },
  { "coral",                0xff7f50 },
  { "cornflowerblue",       0x6495ed },
  { "cornsilk",             0xfff8dc },
  { "crimson",              0xdc143c },
  { "cyan",                 0x00ffff },
  { "darkblue",             0x00008b },
  { "darkcyan",             0x008b8b },
  { "darkgoldenrod",        0xb8860b },
  { "darkgray",             0xa9a9a9 },
  { "darkgreen",            0x006400 },
  { "darkkhaki",            0xbdb76b },
  { "darkmagenta",          0x8b008b },
  { "darkolivegreen",       0x556b2f },
  { "darkorange",           0xff8c00 },
  { "darkorchid",           0x9932cc },
  { "darkred",              0x8b0000 },
  { "darksalmon",           0xe9967a },
  { "darkseagreen",         0x8fbc8f },
  { "darkslateblue",        0x483d8b },
  { "darkslategray",        0x2f4f4f },
  { "darkturquoise",        0x00ced1 },
  { "darkviolet",           0x9400d3 },
  { "deeppink",             0xff1493 },
  { "deepskyblue",          0x00bfff },
  { "dimgray",              0x696969 },
  { "dodgerblue",           0x1e90ff },
  { "firebrick",            0xb22222 },
  { "floralwhite",          0xfffaf0 },
  { "forestgreen",          0x228b22 },
  { "fuchsia",              0xff00ff },
  { "gainsboro",            0xdcdcdc },
  { "ghostwhite",           0xf8f8ff },
  { "gold",                 0xffd700 },
  { "goldenrod",            0xdaa520 },
  { "gray",                 0x808080 },
  { "green",                0x008000 },
  { "greenyellow",          0xadff2f },
  { "honeydew",             0xf0fff0 },
  { "hotpink",              0xff69b4 },
  { "indianred",            0xcd5c5c },
  { "indigo",               0x4b0082 },
  { "ivory",                0xfffff0 },
  { "khaki",                0xf0e68c },
  { "lavender",             0xe6e6fa },
  { "lavenderblush",        0xfff0f5 },
  { "lawngreen",            0x7cfc00 },
  { "lemonchiffon",         0xfffacd },
  { "lightblue",            0xadd8e6 },
  { "lightcoral",           0xf08080 },
  { "lightcyan",            0xe0ffff },
  { "lightgoldenrodyellow", 0xfafad2 },
  { "lightgrey",            0xd3d3d3 },
  { "lightgreen",           0x90ee90 },
  { "lightpink",            0xffb6c1 },
  { "lightsalmon",          0xffa07a },
  { "lightseagreen",        0x20b2aa },
  { "lightskyblue",         0x87cefa },
  { "lightslategray",       0x778899 },
  { "lightsteelblue",       0xb0c4de },
  { "lightyellow",          0xffffe0 },
  { "lime",                 0x00ff00 },
  { "limegreen",            0x32cd32 },
  { "linen",                0xfaf0e6 },
  { "magenta",              0xff00ff },
  { "maroon",               0x800000 },
  { "mediumaquamarine",     0x66cdaa },
  { "mediumblue",           0x0000cd },
  { "mediumorchid",         0xba55d3 },
  { "mediumpurple",         0x9370d8 },
  { "mediumseagreen",       0x3cb371 },
  { "mediumslateblue",      0x7b68ee },
  { "mediumspringgreen",    0x00fa9a },
  { "mediumturquoise",      0x48d1cc },
  { "mediumvioletred",      0xc71585 },
  { "midnightblue",         0x191970 },
  { "mintcream",            0xf5fffa },
  { "mistyrose",            0xffe4e1 },
  { "moccasin",             0xffe4b5 },
  { "navajowhite",          0xffdead },
  { "navy",                 0x000080 },
  { "oldlace",              0xfdf5e6 },
  { "olive",                0x808000 },
  { "olivedrab",            0x6b8e23 },
  { "orange",               0xffa500 },
  { "orangered",            0xff4500 },
  { "orchid",               0xda70d6 },
  { "palegoldenrod",        0xeee8aa },
  { "palegreen",            0x98fb98 },
  { "paleturquoise",        0xafeeee },
  { "palevioletred",        0xd87093 },
  { "papayawhip",           0xffefd5 },
  { "peachpuff",            0xffdab9 },
  { "peru",                 0xcd853f },
  { "pink",                 0xffc0cb },
  { "plum",                 0xdda0dd },
  { "powderblue",           0xb0e0e6 },
  { "purple",               0x800080 },
  { "red",                  0xff0000 },
  { "rosybrown",            0xbc8f8f },
  { "royalblue",            0x4169e1 },
  { "saddlebrown",          0x8b4513 },
  { "salmon",               0xfa8072 },
  { "sandybrown",           0xf4a460 },
  { "seagreen",             0x2e8b57 },
  { "seashell",             0xfff5ee },
  { "sienna",               0xa0522d },
  { "silver",               0xc0c0c0 },
  { "skyblue",              0x87ceeb },
  { "slateblue",            0x6a5acd },
  { "slategray",            0x708090 },
  { "snow",                 0xfffafa },
  { "springgreen",          0x00ff7f },
  { "steelblue",            0x4682b4 },
  { "tan",                  0xd2b48c },
  { "teal",                 0x008080 },
  { "thistle",              0xd8bfd8 },
  { "tomato",               0xff6347 },
  { "turquoise",            0x40e0d0 },
  { "violet",               0xee82ee },
  { "wheat",                0xf5deb3 },
  { "white",                0xffffff },
  { "whitesmoke",           0xf5f5f5 },
  { "yellow",               0xffff00 },
  { "yellowgreen",          0x9acd32 },
};

/*
** Attempt to translate a CSS color name into an integer that
** represents the equivalent RGB value.  Ignore alpha if provided.
** If the name cannot be translated, return -1.
*/
int color_name_to_rgb(const char *zName){
  if( zName==0 || zName[0]==0 ) return -1;
  if( zName[0]=='#' ){
    int i, v = 0;
    for(i=1; i<=6 && fossil_isxdigit(zName[i]); i++){
      v = v*16 + fossil_hexvalue(zName[i]);
    }
    if( i==4 ){
      v = fossil_hexvalue(zName[1])*0x110000 +
          fossil_hexvalue(zName[2])*0x1100 +
          fossil_hexvalue(zName[3])*0x11;
      return v;
    }
    if( i==7 ){
      return v;
    }
    return -1;
  }else{
    int iMin = 0;
    int iMax = count(aCssColors)-1;
    while( iMin<=iMax ){
      int iMid = (iMin+iMax)/2;
      int c = sqlite3_stricmp(aCssColors[iMid].zName, zName);
      if( c==0 ) return aCssColors[iMid].iRGB;
      if( c<0 ){
        iMin = iMid+1;
      }else{
        iMax = iMid-1;
      }
    }
    return -1;
  }
}

/*
** SETTING: raw-bgcolor                  boolean default=off
**
** Fossil usually tries to adjust user-specified background colors
** for check-ins so that the text is readable and so that the color
** is not too garish. This setting disables that filter.  When
** this setting is on, the user-selected background colors are shown
** exactly as requested.
*/

/*
** Shift a color provided by the user so that it is suitable
** for use as a background color in the current skin.
**
** The return value is a #HHHHHH color name contained in
** static space that is overwritten on the next call.
**
** If we cannot make sense of the background color recommendation
** that is the input, then return NULL.
**
** The iFgClr parameter is normally 0.  But for testing purposes, set
** it to 1 for a black foregrounds and 2 for a white foreground.
*/
const char *reasonable_bg_color(const char *zRequested, int iFgClr){
  int iRGB = color_name_to_rgb(zRequested);
  int r, g, b;               /* RGB components of requested color */
  static int systemFg = 0;   /* 1==black-foreground 2==white-foreground */
  int fg;                    /* Foreground color to actually use */
  static char zColor[10];    /* Return value */

  if( iFgClr ){
    fg = iFgClr;
  }else if( systemFg==0 ){
    if( db_get_boolean("raw-bgcolor",0) ){
      fg = systemFg = 3;
    }else{
      fg = systemFg = skin_detail_boolean("white-foreground") ? 2 : 1;
    }
  }else{
    fg = systemFg;
  }
  if( fg>=3 ) return zRequested;

  if( iRGB<0 ) return 0;
  r = (iRGB>>16) & 0xff;
  g = (iRGB>>8) & 0xff;
  b = iRGB & 0xff;
  if( fg==1 ){
    /* Dark text on a light background.  Adjust so that
    ** no color component is less than 255-K, resulting in
    ** a pastel background color.  Color adjustment is quadratic
    ** so that colors that are further out of range have a greater
    ** adjustment. */
    const int K = 79;
    int k, x, m;
    m = r<g ? r : g;
    if( m>b ) m = b;
    k = (m*m)/255 + K;
    x = 255 - k;
    r = (k*r)/255 + x;
    g = (k*g)/255 + x;
    b = (k*b)/255 + x;
  }else{
    /* Light text on a dark background.  Adjust so that 
    ** no color component is greater than K, resulting in
    ** a low-intensity, low-saturation background color.
    ** The color adjustment is quadratic so that colors that
    ** are further out of range have a greater adjustment. */
    const int K = 112;
    int k, m;
    m = r>g ? r : g;
    if( m<b ) m = b;
    k = 255 - (255-K)*(m*m)/65025;
    r = (k*r)/255;
    g = (k*g)/255;
    b = (k*b)/255;
  }
  sqlite3_snprintf(8, zColor, "#%02x%02x%02x", r,g,b);
  return zColor;
}

/*
** Compute a hash on a branch or user name
*/
static unsigned int hash_of_name(const char *z){
  unsigned int h = 0;
  int i;
183
184
185
186
187
188
189


















































































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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
    @ <input type="text" size="30" name='%s(zNm)' value='%h(PD(zNm,""))'><br>
  }
  @ <input type="submit" value="Submit">
  @ <input type="submit" name="rand" value="Random">
  @ </form>
  style_finish_page();
}

/*
** WEBPAGE: test-bgcolor
**
** Show how user-specified background colors will be rendered
** using the reasonable_bg_color() algorithm.
*/
void test_bgcolor_page(void){
  const char *zReq;      /* Requested color name */
  const char *zBG;       /* Actual color provided */
  const char *zBg1;
  char zNm[10];
  static const char *azDflt[] = {
    "red", "orange", "yellow", "green", "blue", "indigo", "violet",
    "tan", "brown", "gray",
  };
  const int N = count(azDflt);
  int i, cnt, iClr, r, g, b;
  char *zFg;
  login_check_credentials();
  style_set_current_feature("test");
  style_header("Background Color Test");
  for(i=cnt=0; i<N; i++){
    sqlite3_snprintf(sizeof(zNm),zNm,"b%c",'a'+i);
    zReq = PD(zNm,azDflt[i]);
    if( zReq==0 || zReq[0]==0 ) continue;
    if( cnt==0 ){
      @ <table border="1" cellspacing="0" cellpadding="10">
      @ <tr>
      @ <th>Requested Background
      @ <th>Light mode
      @ <th>Dark mode
      @ </tr>
    }
    cnt++;
    zBG = reasonable_bg_color(zReq, 0);
    if( zBG==0 ){
      @ <tr><td colspan="3" align="center">\
      @ "%h(zReq)" is not a recognized color name</td></tr>
      continue;
    }
    iClr = color_name_to_rgb(zReq);
    r = (iClr>>16) & 0xff;
    g = (iClr>>8) & 0xff;
    b = iClr & 0xff;
    if( 3*r + 7*g + b > 6*255 ){
      zFg = "black";
    }else{
      zFg = "white";
    }
    if( zReq[0]!='#' ){
      char zReqRGB[12];
      sqlite3_snprintf(sizeof(zReqRGB),zReqRGB,"#%06x",color_name_to_rgb(zReq));
      @ <tr><td style='color:%h(zFg);background-color:%h(zReq);'>\
      @ Requested color "%h(zReq)" (%h(zReqRGB))</td>
    }else{
      @ <tr><td style='color:%h(zFg);background-color:%s(zReq);'>\
      @ Requested color "%h(zReq)"</td>
    }
    zBg1 = reasonable_bg_color(zReq,1);
    @ <td style='color:black;background-color:%h(zBg1);'>\
    @ Background color for dark text: %h(zBg1)</td>
    zBg1 = reasonable_bg_color(zReq,2);
    @ <td style='color:white;background-color:%h(zBg1);'>\
    @ Background color for light text: %h(zBg1)</td></tr>
  }
  if( cnt ){
    @ </table>
    @ <hr>
  }
  @ <form method="POST">
  @ <p>Enter CSS color names below and see them shifted into corresponding
  @ background colors above.</p>
  for(i=0; i<N; i++){
    sqlite3_snprintf(sizeof(zNm),zNm,"b%c",'a'+i);
    @ <input type="text" size="30" name='%s(zNm)' \
    @ value='%h(PD(zNm,azDflt[i]))'><br>
  }
  @ <input type="submit" value="Submit">
  @ </form>
  style_finish_page();
}
Changes to src/comformat.c.
19
20
21
22
23
24
25
26
27








28
29
30
31
32
33
34
35
36
37
38
39
40
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







-
-
+
+
+
+
+
+
+
+




-
-







** text on a TTY.
*/
#include "config.h"
#include "comformat.h"
#include <assert.h>

#if INTERFACE
#define COMMENT_PRINT_NONE       ((u32)0x00000000) /* No flags = non-legacy. */
#define COMMENT_PRINT_LEGACY     ((u32)0x00000001) /* Use legacy algorithm. */
#define COMMENT_PRINT_NONE       ((u32)0x00000000) /* No flags */
#define COMMENT_PRINT_CANONICAL  ((u32)0x00000001) /* Use canonical algorithm */
#define COMMENT_PRINT_DEFAULT    COMMENT_PRINT_CANONICAL  /* Default */
#define COMMENT_PRINT_UNSET      (-1)              /* Not initialized */

/* The canonical comment printing algorithm is recommended.  We make
** no promise of on-going support for any of the following flags:
*/
#define COMMENT_PRINT_TRIM_CRLF  ((u32)0x00000002) /* Trim leading CR/LF. */
#define COMMENT_PRINT_TRIM_SPACE ((u32)0x00000004) /* Trim leading/trailing. */
#define COMMENT_PRINT_WORD_BREAK ((u32)0x00000008) /* Break lines on words. */
#define COMMENT_PRINT_ORIG_BREAK ((u32)0x00000010) /* Break before original. */
#define COMMENT_PRINT_DEFAULT    (COMMENT_PRINT_LEGACY) /* Defaults. */
#define COMMENT_PRINT_UNSET      (-1)              /* Not initialized. */
#endif

/********* Code copied from SQLite src/shell.c.in on 2024-09-30 **********/
/* Lookup table to estimate the number of columns consumed by a Unicode
** character.
*/
static const struct {
72
73
74
75
76
77
78
79

80
81
82
83
84
85
86
76
77
78
79
80
81
82

83
84
85
86
87
88
89
90







-
+







  {1, 0x00e3b},  {0, 0x00e47},  {1, 0x00e4f},  {0, 0x00eb1},  {1, 0x00eb2},
  {0, 0x00eb4},  {1, 0x00eba},  {0, 0x00ebb},  {1, 0x00ebd},  {0, 0x00ec8},
  {1, 0x00ece},  {0, 0x00f18},  {1, 0x00f1a},  {0, 0x00f35},  {1, 0x00f36},
  {0, 0x00f37},  {1, 0x00f38},  {0, 0x00f39},  {1, 0x00f3a},  {0, 0x00f71},
  {1, 0x00f7f},  {0, 0x00f80},  {1, 0x00f85},  {0, 0x00f86},  {1, 0x00f88},
  {0, 0x00f90},  {1, 0x00f98},  {0, 0x00f99},  {1, 0x00fbd},  {0, 0x00fc6},
  {1, 0x00fc7},  {0, 0x0102d},  {1, 0x01031},  {0, 0x01032},  {1, 0x01033},
  {0, 0x01036},  {1, 0x01038},  {0, 0x01039},  {1, 0x0103a},  {0, 0x01058},
  {0, 0x01036},  {1, 0x0103b},  {0, 0x01058},
  {1, 0x0105a},  {2, 0x01100},  {0, 0x01160},  {1, 0x01200},  {0, 0x0135f},
  {1, 0x01360},  {0, 0x01712},  {1, 0x01715},  {0, 0x01732},  {1, 0x01735},
  {0, 0x01752},  {1, 0x01754},  {0, 0x01772},  {1, 0x01774},  {0, 0x017b4},
  {1, 0x017b6},  {0, 0x017b7},  {1, 0x017be},  {0, 0x017c6},  {1, 0x017c7},
  {0, 0x017c9},  {1, 0x017d4},  {0, 0x017dd},  {1, 0x017de},  {0, 0x0180b},
  {1, 0x0180e},  {0, 0x018a9},  {1, 0x018aa},  {0, 0x01920},  {1, 0x01923},
  {0, 0x01927},  {1, 0x01929},  {0, 0x01932},  {1, 0x01933},  {0, 0x01939},
115
116
117
118
119
120
121
122

123
124
125
126
127
128
129
119
120
121
122
123
124
125

126
127
128
129
130
131
132
133







-
+







** Inaccuracies in the width estimates might cause columns to be misaligned.
** Unfortunately, there is nothing we can do about that.
*/
static int cli_wcwidth(int c){
  int iFirst, iLast;

  /* Fast path for common characters */
  if( c<=0x300 ) return 1;
  if( c<0x300 ) return 1;

  /* The general case */
  iFirst = 0;
  iLast = sizeof(aUWidth)/sizeof(aUWidth[0]) - 1;
  while( iFirst<iLast-1 ){
    int iMid = (iFirst+iLast)/2;
    int cMid = aUWidth[iMid].iFirst;
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
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







+














-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
-
-
+
+
+





+
+
+
+
+
+
+
+
+
+







  const char *zLine, /* [in] The comment line being printed. */
  int index,         /* [in] The current character index being handled. */
  int maxChars,      /* [in] Optimization hint to abort before space found. */
  int *sumWidth      /* [out] Summated width of all characters to next space. */
){
  int cchUTF8, utf32, wcwidth = 0;
  int nextIndex = index;
  if( zLine[index]==0 ) return index;
  for(;;){
    char_info_utf8(&zLine[nextIndex],&cchUTF8,&utf32);
    nextIndex += cchUTF8;
    wcwidth += cli_wcwidth(utf32);
    if( zLine[nextIndex]==0 || fossil_isspace(zLine[nextIndex]) ||
        wcwidth>maxChars ){
      *sumWidth = wcwidth;
      return nextIndex;
    }
  }
  return 0; /* NOT REACHED */
}

/*
** Return information about the next (single- or multi-byte) character in the
** specified UTF-8 string: The number of UTF-8 code units (in this case: bytes)
** and the decoded UTF-32 code point. Incomplete, ill-formed and overlong
** sequences are consumed together as one invalid code point. The invalid lead
** bytes 0xC0 to 0xC1 and 0xF5 to 0xF7 are allowed to initiate (ill-formed) 2-
** and 4-byte sequences, respectively, the other invalid lead bytes 0xF8 to 0xFF
** are treated as invalid 1-byte sequences (as lone trail bytes), all resulting
** in one invalid code point. Invalid UTF-8 sequences encoding a non-scalar code
** point (UTF-16 surrogates U+D800 to U+DFFF) are allowed.
** Return information about the next (single- or multi-byte) character in
** z[0].  Two values are computed:
**
**     *   The number of bytes needed to represent the character.
**     *   The UTF code point value.
**
** Incomplete, ill-formed and overlong sequences are consumed together as
** one invalid code point. The invalid lead bytes 0xC0 to 0xC1 and 0xF5 to
** 0xF7 are allowed to initiate (ill-formed) 2- and 4-byte sequences,
** respectively, the other invalid lead bytes 0xF8 to 0xFF are treated
** as invalid 1-byte sequences (as lone trail bytes), all resulting
** in one invalid code point. Invalid UTF-8 sequences encoding a
** non-scalar code point (UTF-16 surrogates U+D800 to U+DFFF) are allowed.
**
** ANSI escape sequences of the form "\033[...X" are interpreted as a
** zero-width character.
*/
void char_info_utf8(
  const char *z,
  int *pCchUTF8,
  int *pUtf32
  const char *z,       /* The character to be analyzed */
  int *pCchUTF8,       /* OUT: The number of bytes used by this character */
  int *pUtf32          /* OUT: The UTF8 code point (used to determine width) */
){
  int i = 0;                              /* Counted bytes. */
  int cchUTF8 = 1;                        /* Code units consumed. */
  int maxUTF8 = 1;                        /* Expected sequence length. */
  char c = z[i++];
  if( c==0x1b && z[i]=='[' ){
    i++;
    while( z[i]>=0x30 && z[i]<=0x3f ){ i++; }
    while( z[i]>=0x20 && z[i]<=0x2f ){ i++; }
    if( z[i]>=0x40 && z[i]<=0x7e ){
      *pCchUTF8 = i+1;
      *pUtf32 = 0x301;  /* A zero-width character */
      return;
    }
  }
  if( (c&0x80)==0x00 ){                   /* 7-bit ASCII character. */
    *pCchUTF8 = 1;
    *pUtf32 = (int)z[0];
    return;
  }
  else if( (c&0xe0)==0xc0 ) maxUTF8 = 2;  /* UTF-8 lead byte 110vvvvv */
  else if( (c&0xf0)==0xe0 ) maxUTF8 = 3;  /* UTF-8 lead byte 1110vvvv */
279
280
281
282
283
284
285
286

287
288
289
290
291
292
293
301
302
303
304
305
306
307

308
309
310
311
312
313
314
315







-
+







  }
  switch( cchUTF8 ){
    case 4:
      *pUtf32 =
        ( (z[0] & 0x0f)<<18 ) |
        ( (z[1] & 0x3f)<<12 ) |
        ( (z[2] & 0x3f)<< 6 ) |
        ( (z[4] & 0x3f)<< 0 ) ;
        ( (z[3] & 0x3f)<< 0 ) ;
      break;
    case 3:
      *pUtf32 =
        ( (z[0] & 0x0f)<<12 ) | 
        ( (z[1] & 0x3f)<< 6 ) | 
        ( (z[2] & 0x3f)<< 0 ) ;
      break;
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
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







-
-
+
+
+


-
+



+
+
+
+
+
+
+


-
+







  }
  if( pzLine ){
    *pzLine = zLine + index;
  }
}

/*
** This is the legacy comment printing algorithm.  It is being retained
** for backward compatibility.
** This is the canonical comment printing algorithm.  This is the algorithm
** that is recommended and that is used unless the administrator has made
** special arrangements to use a customized algorithm.
**
** Given a comment string, format that string for printing on a TTY.
** Assume that the output cursors is indent spaces from the left margin
** Assume that the output cursor is indent spaces from the left margin
** and that a single line can contain no more than 'width' characters.
** Indent all subsequent lines by 'indent'.
**
** Formatting features:
**
**   *  Leading whitespace is removed.
**   *  Internal whitespace sequences are changed into a single space (0x20)
**      character.
**   *  Lines are broken at a space, or at a hyphen ("-") whenever possible.
**
** Returns the number of new lines emitted.
*/
static int comment_print_legacy(
static int comment_print_canonical(
  const char *zText, /* The comment text to be printed. */
  int indent,        /* Number of spaces to indent each non-initial line. */
  int width          /* Maximum number of characters per line. */
){
  int maxChars = width - indent;
  int si, sk, i, k, kc;
  int doIndent = 0;
562
563
564
565
566
567
568
569
570
571








572
573
574
575
576
577
578
592
593
594
595
596
597
598



599
600
601
602
603
604
605
606
607
608
609
610
611
612
613







-
-
-
+
+
+
+
+
+
+
+








/*
** This is the comment printing function.  The comment printing algorithm
** contained within it attempts to preserve the formatting present within
** the comment string itself while honoring line width limitations.  There
** are several flags that modify the default behavior of this function:
**
**         COMMENT_PRINT_LEGACY: Forces use of the legacy comment printing
**                               algorithm.  For backward compatibility,
**                               this is the default.
**      COMMENT_PRINT_CANONICAL: Use the canonical printing algorithm:
**                                  *  Omit leading and trailing whitespace
**                                  *  Collapse internal whitespace into a
**                                     single space (0x20) character.
**                                  *  Attempt to break lines at whitespace
**                                     or hyphens.
**                               This is the recommended algorithm and is
**                               used in most cases.
**
**      COMMENT_PRINT_TRIM_CRLF: Trims leading and trailing carriage-returns
**                               and line-feeds where they do not materially
**                               impact pre-existing formatting (i.e. at the
**                               start of the comment string -AND- right
**                               before line indentation).  This flag does
**                               not apply to the legacy comment printing
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
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







+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+






-
-
-
-
+
+
+







  const char *zText,     /* The comment text to be printed. */
  const char *zOrigText, /* Original comment text ONLY, may be NULL. */
  int indent,            /* Spaces to indent each non-initial line. */
  int width,             /* Maximum number of characters per line. */
  int flags              /* Zero or more "COMMENT_PRINT_*" flags. */
){
  int maxChars = width - indent;

  int legacy = flags & COMMENT_PRINT_LEGACY;
  int trimCrLf = flags & COMMENT_PRINT_TRIM_CRLF;
  int trimSpace = flags & COMMENT_PRINT_TRIM_SPACE;
  int wordBreak = flags & COMMENT_PRINT_WORD_BREAK;
  int origBreak = flags & COMMENT_PRINT_ORIG_BREAK;
  int lineCnt = 0;
  const char *zLine;
  if( flags & COMMENT_PRINT_CANONICAL ){
    /* Use the canonical algorithm.  This is what happens in almost
    ** all cases. */
    return comment_print_canonical(zText, indent, width);
  }else{
    /* The remaining is a more complex formatting algorithm that is very
    ** seldom used and is considered deprecated.
    */
    int trimCrLf = flags & COMMENT_PRINT_TRIM_CRLF;
    int trimSpace = flags & COMMENT_PRINT_TRIM_SPACE;
    int wordBreak = flags & COMMENT_PRINT_WORD_BREAK;
    int origBreak = flags & COMMENT_PRINT_ORIG_BREAK;
    int lineCnt = 0;
    const char *zLine;

  if( legacy ){
    return comment_print_legacy(zText, indent, width);
  }
  if( width<0 ){
    comment_set_maxchars(indent, &maxChars);
  }
  if( zText==0 ) zText = "(NULL)";
  if( maxChars<=0 ){
    maxChars = strlen(zText);
  }
  if( trimSpace ){
    while( fossil_isspace(zText[0]) ){ zText++; }
  }
  if( zText[0]==0 ){
    fossil_print("\n");
    lineCnt++;
    return lineCnt;
  }
  zLine = zText;
  for(;;){
    comment_print_line(zOrigText, zLine, indent, zLine>zText ? indent : 0,
                       maxChars, trimCrLf, trimSpace, wordBreak, origBreak,
                       &lineCnt, &zLine);
    if( zLine==0 ) break;
    while( fossil_isspace(zLine[0]) ) zLine++;
    if( zLine[0]==0 ) break;
  }
  return lineCnt;
    if( width<0 ){
      comment_set_maxchars(indent, &maxChars);
    }
    if( zText==0 ) zText = "(NULL)";
    if( maxChars<=0 ){
      maxChars = strlen(zText);
    }
    if( trimSpace ){
      while( fossil_isspace(zText[0]) ){ zText++; }
    }
    if( zText[0]==0 ){
      fossil_print("\n");
      lineCnt++;
      return lineCnt;
    }
    zLine = zText;
    for(;;){
      comment_print_line(zOrigText, zLine, indent, zLine>zText ? indent : 0,
                         maxChars, trimCrLf, trimSpace, wordBreak, origBreak,
                         &lineCnt, &zLine);
      if( zLine==0 ) break;
      while( fossil_isspace(zLine[0]) ) zLine++;
      if( zLine[0]==0 ) break;
    }
    return lineCnt;
  }
}

/*
** Return the "COMMENT_PRINT_*" flags specified by the following sources,
** evaluated in the following cascading order:
**
**    1. The global --comfmtflags (alias --comment-format) command-line option.
**    2. The local (per-repository) "comment-format" setting.
**    3. The global (all-repositories) "comment-format" setting.
**    4. The default value COMMENT_PRINT_DEFAULT.
**    1. The local (per-repository) "comment-format" setting.
**    2. The global (all-repositories) "comment-format" setting.
**    3. The default value COMMENT_PRINT_DEFAULT.
*/
int get_comment_format(){
  int comFmtFlags;

  /* We must cache this result, else running the timeline can end up
  ** querying the comment-format setting from the global db once per
  ** timeline entry, which brings it to a crawl if that db is
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
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

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
843
844
845
846
847
848
849
850
851
852


853







-
+



+
+
+
+
+
+
+
+
+
+

-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+




-
-
-
+
+
+

+
-
+

-
-
-
+
+
+
+


-
+



+



+



+














-
-
-
-
-
-
-
-
-
+
-


-
-
+
+

+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+









+








-
-

  return g.comFmtFlags;
}

/*
**
** COMMAND: test-comment-format
**
** Usage: %fossil test-comment-format ?OPTIONS? PREFIX TEXT ?ORIGTEXT?
** Usage: %fossil test-comment-format [OPTIONS] TEXT [PREFIX] [ORIGTEXT]
**
** Test comment formatting and printing.  Use for testing only.
**
** The default (canonical) formatting algorithm is:
**
**    *  Omit leading/trailing whitespace
**    *  Collapse internal whitespace into a single space character.
**    *  Attempt to break lines at whitespace or at a hyphen.
**
** Use --whitespace, --origbreak, --trimcrlf, --trimspace,
** and/or --wordbreak to disable the canonical processing and do
** the special processing specified by those other options.
**
** Options:
**   --file           The comment text is really just a file name to
**                    read it from
**   --decode         Decode the text using the same method used when
**                    handling the value of a C-card from a manifest.
**   --decode           Decode the text using the same method used when
**                      handling the value of a C-card from a manifest.
**   --legacy         Use the legacy comment printing algorithm
**   --trimcrlf       Enable trimming of leading/trailing CR/LF
**   --trimspace      Enable trimming of leading/trailing spaces
**   --wordbreak      Attempt to break lines on word boundaries
**   --origbreak      Attempt to break when the original comment text
**                    is detected
**   --indent         Number of spaces to indent (default (-1) is to
**                    auto-detect).  Zero means no indent.
**   -W|--width NUM   Width of lines (default (-1) is to auto-detect).
**                    Zero means no limit.
**   --file FILE        Omit the TEXT argument and read the comment text
**                      from FILE.
**   --indent           Number of spaces to indent (default (-1) is to
**                      auto-detect).  Zero means no indent.
**   --orig FILE        Take the value for the ORIGTEXT argument from FILE.
**   --origbreak        Attempt to break when the original comment text
**                      is detected.
**   --trimcrlf         Enable trimming of leading/trailing CR/LF.
**   --trimspace        Enable trimming of leading/trailing spaces.
**   --whitespace       Keep all internal whitespace.
**   --wordbreak        Attempt to break lines on word boundaries.
**   -W|--width NUM     Width of lines (default (-1) is to auto-detect).
**                      Zero means no limit.
*/
void test_comment_format(void){
  const char *zWidth;
  const char *zIndent;
  const char *zPrefix;
  char *zText;
  char *zOrigText;
  const char *zPrefix = 0;
  char *zText = 0;
  char *zOrigText = 0;
  int indent, width;
  int i;
  int fromFile = find_option("file", 0, 0)!=0;
  const char *fromFile = find_option("file", 0, 1);
  int decode = find_option("decode", 0, 0)!=0;
  int flags = COMMENT_PRINT_NONE;
  if( find_option("legacy", 0, 0) ){
    flags |= COMMENT_PRINT_LEGACY;
  int flags = COMMENT_PRINT_CANONICAL;
  const char *fromOrig = find_option("orig", 0, 1);
  if( find_option("whitespace",0,0) ){
    flags = 0;
  }
  if( find_option("trimcrlf", 0, 0) ){
    flags |= COMMENT_PRINT_TRIM_CRLF;
    flags = COMMENT_PRINT_TRIM_CRLF;
  }
  if( find_option("trimspace", 0, 0) ){
    flags |= COMMENT_PRINT_TRIM_SPACE;
    flags &= COMMENT_PRINT_CANONICAL;
  }
  if( find_option("wordbreak", 0, 0) ){
    flags |= COMMENT_PRINT_WORD_BREAK;
    flags &= COMMENT_PRINT_CANONICAL;
  }
  if( find_option("origbreak", 0, 0) ){
    flags |= COMMENT_PRINT_ORIG_BREAK;
    flags &= COMMENT_PRINT_CANONICAL;
  }
  zWidth = find_option("width","W",1);
  if( zWidth ){
    width = atoi(zWidth);
  }else{
    width = -1; /* automatic */
  }
  zIndent = find_option("indent",0,1);
  if( zIndent ){
    indent = atoi(zIndent);
  }else{
    indent = -1; /* automatic */
  }
  verify_all_options();
  if( g.argc!=4 && g.argc!=5 ){
    usage("?OPTIONS? PREFIX TEXT ?ORIGTEXT?");
  }
  zPrefix = g.argv[2];
  zText = g.argv[3];
  if( g.argc==5 ){
    zOrigText = g.argv[4];
  }else{
    zOrigText = 0;
  zPrefix = zText = zOrigText = 0;
  }
  if( fromFile ){
    Blob fileData;
    blob_read_from_file(&fileData, zText, ExtFILE);
    zText = mprintf("%s", blob_str(&fileData));
    blob_read_from_file(&fileData, fromFile, ExtFILE);
    zText = fossil_strdup(blob_str(&fileData));
    blob_reset(&fileData);
  }
    if( zOrigText ){
      blob_read_from_file(&fileData, zOrigText, ExtFILE);
      zOrigText = mprintf("%s", blob_str(&fileData));
      blob_reset(&fileData);
    }
  if( fromOrig ){
    Blob fileData;
    blob_read_from_file(&fileData, fromOrig, ExtFILE);
    zOrigText = fossil_strdup(blob_str(&fileData));
    blob_reset(&fileData);
  }
  for(i=2; i<g.argc; i++){
    if( zText==0 ){
      zText = g.argv[i];
      continue;
    }
    if( zPrefix==0 ){
      zPrefix = g.argv[i];
      continue;
    }
    if( zOrigText==0 ){
      zOrigText = g.argv[i];
      continue;
    }
    usage("[OPTIONS] TEXT [PREFIX] [ORIGTEXT]");
  }
  if( decode ){
    zText = mprintf(fromFile?"%z":"%s" /*works-like:"%s"*/, zText);
    defossilize(zText);
    if( zOrigText ){
      zOrigText = mprintf(fromFile?"%z":"%s" /*works-like:"%s"*/, zOrigText);
      defossilize(zOrigText);
    }
  }
  if( zPrefix==0 ) zPrefix = "00:00:00 ";
  if( indent<0 ){
    indent = strlen(zPrefix);
  }
  if( zPrefix && *zPrefix ){
    fossil_print("%s", zPrefix);
  }
  fossil_print("(%d lines output)\n",
               comment_print(zText, zOrigText, indent, width, flags));
  if( zOrigText && zOrigText!=g.argv[4] ) fossil_free(zOrigText);
  if( zText && zText!=g.argv[3] ) fossil_free(zText);
}
Changes to src/config.h.
37
38
39
40
41
42
43
44

45
46
47
48
49
50
51
37
38
39
40
41
42
43

44
45
46
47
48
49
50
51







-
+







#  define _USE_32BIT_TIME_T
#endif

#ifdef HAVE_AUTOCONFIG_H
#include "autoconfig.h"
#endif

/* Enable the hardened SHA1 implemenation by default */
/* Enable the hardened SHA1 implementation by default */
#ifndef FOSSIL_HARDENED_SHA1
# define FOSSIL_HARDENED_SHA1 1
#endif

#ifndef _RC_COMPILE_

/*
Changes to src/configure.c.
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
98
99
100
101
102
103
104

105
106
107
108
109
110
111







-







  { "default-skin",           CONFIGSET_SKIN },
  { "logo-mimetype",          CONFIGSET_SKIN },
  { "logo-image",             CONFIGSET_SKIN },
  { "background-mimetype",    CONFIGSET_SKIN },
  { "background-image",       CONFIGSET_SKIN },
  { "icon-mimetype",          CONFIGSET_SKIN },
  { "icon-image",             CONFIGSET_SKIN },
  { "timeline-block-markup",  CONFIGSET_SKIN },
  { "timeline-date-format",   CONFIGSET_SKIN },
  { "timeline-default-style", CONFIGSET_SKIN },
  { "timeline-dwelltime",     CONFIGSET_SKIN },
  { "timeline-closetime",     CONFIGSET_SKIN },
  { "timeline-hard-newlines", CONFIGSET_SKIN },
  { "timeline-max-comment",   CONFIGSET_SKIN },
  { "timeline-plaintext",     CONFIGSET_SKIN },
369
370
371
372
373
374
375




376
377
378

379
380
381
382
383
384
385
368
369
370
371
372
373
374
375
376
377
378
379
380

381
382
383
384
385
386
387
388







+
+
+
+


-
+







**    -------     -----------------------------------------------------------
**    /config     $MTIME $NAME value $VALUE
**    /user       $MTIME $LOGIN pw $VALUE cap $VALUE info $VALUE photo $VALUE
**    /shun       $MTIME $UUID scom $VALUE
**    /reportfmt  $MTIME $TITLE owner $VALUE cols $VALUE sqlcode $VALUE jx $JSON
**    /concealed  $MTIME $HASH content $VALUE
**    /subscriber $SMTIME $SEMAIL suname $V ...
**
** NAME-specific notes:
**
**  - /reportftm's $MTIME is in Julian, not the Unix epoch.
*/
void configure_receive(const char *zName, Blob *pContent, int groupMask){
  int checkMask;   /* Masks for which we must first check existance of tables */
  int checkMask;   /* Masks for which we must first check existence of tables */

  checkMask = CONFIGSET_SCRIBER;
  if( zName[0]=='/' ){
    /* The new format */
    char *azToken[24];
    int nToken = 0;
    int ii, jj;
815
816
817
818
819
820
821


822
823
824
825
826
827
828
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833







+
+







**
** >  fossil configuration sync AREA ?URL?
**
**         Synchronize configuration changes in the local repository with
**         the remote repository at URL.
**
** Options:
**    --proxy PROXY              Use PROXY as http proxy during sync operation
**                               (used by pull, push and sync subcommands)
**    -R|--repository REPO       Affect repository REPO with changes
**
** See also: [[settings]], [[unset]]
*/
void configuration_cmd(void){
  int n;
  const char *zMethod;
887
888
889
890
891
892
893

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







+







    if( g.argc==5 ){
      zServer = g.argv[4];
    }
    url_parse(zServer, URL_PROMPT_PW|URL_USE_CONFIG);
    if( g.url.protocol==0 ) fossil_fatal("no server URL specified");
    user_select();
    url_enable_proxy("via proxy: ");
    g.zHttpAuth = get_httpauth();
    if( overwriteFlag ) mask |= CONFIGSET_OVERWRITE;
    if( strncmp(zMethod, "push", n)==0 ){
      client_sync(0,0,(unsigned)mask,0,0);
    }else if( strncmp(zMethod, "pull", n)==0 ){
      if( overwriteFlag ) db_unprotect(PROTECT_USER);
      client_sync(0,(unsigned)mask,0,0,0);
      if( overwriteFlag ) db_protect_pop();
Changes to src/content.c.
494
495
496
497
498
499
500
501

502
503
504
505
506
507
508
494
495
496
497
498
499
500

501
502
503
504
505
506
507
508







-
+







** 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,
** and zUuid is zero then the correct zUuid is computed from pBlob.
**
** If the record already exists but is a phantom, the pBlob content
** is inserted and the phatom becomes a real record.
** is inserted and the phantom becomes a real record.
**
** The original content of pBlob is not disturbed.  The caller continues
** to be responsible for pBlob.  This routine does *not* take over
** responsibility for freeing pBlob.
*/
int content_put_ex(
  Blob *pBlob,              /* Content to add to the repository */
598
599
600
601
602
603
604

605
606
607
608
609
610
611
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612







+







      "VALUES(%d,%d,'%q',:data)",
       g.rcvid, size, blob_str(&hash)
    );
    db_bind_blob(&s1, ":data", &cmpr);
    db_exec(&s1);
    rid = db_last_insert_rowid();
    if( !pBlob ){
      assert(!"cannot happen: pBlob is always non-NULL");
      db_multi_exec("INSERT OR IGNORE INTO phantom VALUES(%d)", rid);
    }
  }
  if( g.markPrivate || isPrivate ){
    db_multi_exec("INSERT OR IGNORE INTO private VALUES(%d)", rid);
    markAsUnclustered = 0;
  }
648
649
650
651
652
653
654
655

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

656
657
658
659
660
661
662
663







-
+







** repository.  pBlob is the content to be inserted.
**
** pBlob is uncompressed and is not deltaed.  It is exactly the content
** to be inserted.
**
** The original content of pBlob is not disturbed.  The caller continues
** to be responsible for pBlob.  This routine does *not* take over
** responsiblity for freeing pBlob.
** responsibility for freeing pBlob.
*/
int content_put(Blob *pBlob){
  return content_put_ex(pBlob, 0, 0, 0, 0);
}


/*
790
791
792
793
794
795
796
797

798
799
800
801
802
803
804
791
792
793
794
795
796
797

798
799
800
801
802
803
804
805







-
+







  db_bind_int(&s1, ":rid", rid);
  db_exec(&s1);
}

/*
** Try to change the storage of rid so that it is a delta from one
** of the artifacts given in aSrc[0]..aSrc[nSrc-1].  The aSrc[*] that
** gives the smallest delta is choosen.
** gives the smallest delta is chosen.
**
** If rid is already a delta from some other place then no
** conversion occurs and this is a no-op unless force==1.  If force==1,
** then nSrc must also be 1.
**
** If rid refers to a phantom, no delta is created.
**
861
862
863
864
865
866
867
868

869
870
871
872
873
874
875
862
863
864
865
866
867
868

869
870
871
872
873
874
875
876







-
+







  /* Loop over all candidate delta sources */
  for(i=0; i<nSrc; i++){
    int srcid = aSrc[i];
    if( srcid==rid ) continue;
    if( content_is_private(srcid) && !content_is_private(rid) ) continue;

    /* Compute all ancestors of srcid and make sure rid is not one of them.
    ** If rid is an ancestor of srcid, then making rid a decendent of srcid
    ** If rid is an ancestor of srcid, then making rid a descendant of srcid
    ** would create a delta loop. */
    s = srcid;
    while( (s = delta_source_rid(s))>0 ){
      if( s==rid ){
        content_undelta(srcid);
        break;
      }
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
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







+

















-
+













-
+







** Return true if Blob p looks like it might be a parsable control artifact.
*/
static int looks_like_control_artifact(Blob *p){
  const char *z = blob_buffer(p);
  int n = blob_size(p);
  if( n<10 ) return 0;
  if( strncmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)==0 ) return 1;
  if( strncmp(z, "-----BEGIN SSH SIGNED MESSAGE-----", 34)==0 ) return 1;
  if( z[0]<'A' || z[0]>'Z' || z[1]!=' ' || z[0]=='I' ) return 0;
  if( z[n-1]!='\n' ) return 0;
  return 1;
}

/*
** COMMAND: test-integrity
**
** Verify that all content can be extracted from the BLOB table correctly.
** If the BLOB table is correct, then the repository can always be
** successfully reconstructed using "fossil rebuild".
**
** Options:
**    -d|--db-only       Run "PRAGMA integrity_check" on the database only.
**                       No other validation is performed.
**    --parse            Parse all manifests, wikis, tickets, events, and
**                       so forth, reporting any errors found.
**    -q|--quick         Run "PRAGMA quick_check" on the database only.
**    --quick            Run "PRAGMA quick_check" on the database only.
**                       No other validation is performed.
*/
void test_integrity(void){
  Stmt q;
  Blob content;
  int n1 = 0;
  int n2 = 0;
  int nErr = 0;
  int total;
  int nCA = 0;
  int anCA[10];
  int bParse = find_option("parse",0,0)!=0;
  int bDbOnly = find_option("db-only","d",0)!=0;
  int bQuick = find_option("quick","q",0)!=0;
  int bQuick = find_option("quick",0,0)!=0;
  db_find_and_open_repository(OPEN_ANY_SCHEMA, 2);
  if( bDbOnly || bQuick ){
    const char *zType = bQuick ? "quick" : "integrity";
    char *zRes;
    zRes = db_text(0,"PRAGMA repository.%s_check", zType/*safe-for-%s*/);
    if( fossil_strcmp(zRes,"ok")!=0 ){
      fossil_print("%s_check failed!\n", zType);
1195
1196
1197
1198
1199
1200
1201
1202

1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215

1216
1217
1218
1219
1220
1221
1222
1197
1198
1199
1200
1201
1202
1203

1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216

1217
1218
1219
1220
1221
1222
1223
1224







-
+












-
+







**
** Look at every artifact in the repository and verify that
** all references are satisfied.  Report any referenced artifacts
** that are missing or shunned.
**
** Options:
**    --notshunned          Do not report shunned artifacts
**    --quiet               Only show output if there are errors
**    -q|--quiet            Only show output if there are errors
*/
void test_missing(void){
  Stmt q;
  Blob content;
  int nErr = 0;
  int nArtifact = 0;
  int i;
  Manifest *p;
  unsigned flags = 0;
  int quietFlag;

  if( find_option("notshunned", 0, 0)!=0 ) flags |= MISSING_SHUNNED;
  quietFlag = find_option("quiet","q",0)!=0;
  quietFlag = g.fQuiet;
  db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
  db_prepare(&q,
     "SELECT mid FROM mlink UNION "
     "SELECT srcid FROM tagxref WHERE srcid>0 UNION "
     "SELECT rid FROM tagxref UNION "
     "SELECT rid FROM attachment JOIN blob ON src=uuid UNION "
     "SELECT objid FROM event");
Changes to src/cookies.c.
84
85
86
87
88
89
90
91

92
93
94
95
96
97
98
84
85
86
87
88
89
90

91
92
93
94
95
96
97
98







-
+







** by DISPLAY_SETTINGS_COOKIE
*/
void cookie_parse(void){
  char *z;
  if( cookies.bIsInit ) return;
  z = (char*)P(DISPLAY_SETTINGS_COOKIE);
  if( z==0 ) z = "";
  cookies.zCookieValue = z = mprintf("%s", z);
  cookies.zCookieValue = z = fossil_strdup(z);
  cookies.bIsInit = 1;
  while( cookies.nParam<COOKIE_NPARAM ){
    while( fossil_isspace(z[0]) ) z++;
    if( z[0]==0 ) break;
    cookies.aParam[cookies.nParam].zPName = z;
    while( *z && *z!='=' && *z!=',' ){ z++; }
    if( *z=='=' ){
254
255
256
257
258
259
260


261

262
263
264
265
266
267
268
254
255
256
257
258
259
260
261
262

263
264
265
266
267
268
269
270







+
+
-
+







  for(i=0; cgi_param_info(i, &zName, &zValue, &isQP); i++){
    char *zDel;
    if( isQP ) continue;
    if( fossil_isupper(zName[0]) ) continue;
    if( bFDSonly && strcmp(zName, "fossil_display_settings")!=0 ) continue;
    zDel = mprintf("del%s",zName);
    if( P(zDel)!=0 ){
      const char *zPath = fossil_strcmp(ROBOT_COOKIE,zName)==0
        ? "/" : 0;
      cgi_set_cookie(zName, "", 0, -1);
      cgi_set_cookie(zName, "", zPath, -1);
      cgi_redirect(g.zPath);
    }
    nCookie++;
    @ <li><p><b>%h(zName)</b>: %h(zValue)
    @ <input type="submit" name="%h(zDel)" value="Delete">
    if( fossil_strcmp(zName, DISPLAY_SETTINGS_COOKIE)==0  && cookies.nParam>0 ){
      int j;
280
281
282
283
284
285
286




287
288
289
290
291
292
293
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299







+
+
+
+







    if( fossil_strncmp(zName, "fossil-", 7)==0
     && strlen(zName)==23
     && hex_prefix_length(&zName[7])==16
     && hex_prefix_length(zValue)>24
    ){
      @ <p>This appears to be a login cookie for another Fossil repository
      @ in the same website.
    }else
    if( fossil_strcmp(zName, ROBOT_COOKIE)==0 ){
      @ <p>This cookie shows that your web-browser has been tested is
      @ believed to be operated by a human, not a robot.
    }
    else {
      @ <p>This cookie was not generated by Fossil.  It might be something
      @ from another program on the same website.
    }
    fossil_free(zDel);
  }
Changes to src/copybtn.js.
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
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



-
-
-
+
+
+
-
-
-
-



-
+
+
+
+
+








-
-
+
+
+
+


-
+













-
-



+
+
+
+
+












-
+
-
-
-










-
-
-
-
-







/* Manage "Copy Buttons" linked to target elements, to copy the text (or, parts
** thereof) of the target elements to the clipboard.
**
** Newly created buttons are <span> elements with an SVG background icon,
** defined by the "copy-button" class in the default CSS style sheet, and are
** assigned the element ID "copy-<idTarget>".
** Newly created buttons are <button> elements plus a nested <span> element with
** an SVG background icon, defined by the "copy-button" class in the default CSS
** style sheet, and are assigned the element ID "copy-<idTarget>".
**
** To simplify customization, the only properties modified for HTML-defined
** buttons are the "onclick" handler, and the "transition" and "opacity" styles
** (used for animation).
**
** For HTML-defined buttons, either initCopyButtonById(), or initCopyButton(),
** needs to be called to attach the "onclick" handler (done automatically from
** a handler attached to the "DOMContentLoaded" event).
** a handler attached to the "DOMContentLoaded" event). These functions create
** the nested <span> element if the <button> element has no child nodes. Using
** static HTML for the <span> element ensures the buttons are visible if there
** are script errors, which may be useful for Fossil JS hackers (as good parts
** of the Fossil web UI come down on JS errors, anyway).
**
** The initialization functions do not overwrite the "data-copytarget" and
** "data-copylength" attributes with empty or null values for <idTarget> and
** <cchLength>, respectively. Set <cchLength> to "-1" to explicitly remove the
** previous copy length limit.
**
** HTML snippet for statically created buttons:
**
**    <span class="copy-button" id="copy-<idTarget>"
**      data-copytarget="<idTarget>" data-copylength="<cchLength>"></span>
**    <button class="copy-button" id="copy-<idTarget>"
**        data-copytarget="<idTarget>" data-copylength="<cchLength>">
**      <span></span>
**    </button>
*/
function makeCopyButton(idTarget,bFlipped,cchLength){
  var elButton = document.createElement("span");
  var elButton = document.createElement("button");
  elButton.className = "copy-button";
  if( bFlipped ) elButton.className += " copy-button-flipped";
  elButton.id = "copy-" + idTarget;
  initCopyButton(elButton,idTarget,cchLength);
  return elButton;
}
function initCopyButtonById(idButton,idTarget,cchLength){
  idButton = idButton || "copy-" + idTarget;
  var elButton = document.getElementById(idButton);
  if( elButton ) initCopyButton(elButton,idTarget,cchLength);
  return elButton;
}
function initCopyButton(elButton,idTarget,cchLength){
  elButton.style.transition = "";
  elButton.style.opacity = 1;
  if( idTarget ) elButton.setAttribute("data-copytarget",idTarget);
  if( cchLength ) elButton.setAttribute("data-copylength",cchLength);
  elButton.onclick = clickCopyButton;
  /* Make sure the <button> contains a single nested <span>. */
  if( elButton.childElementCount!=1 || elButton.firstChild.tagName!="SPAN" ){
    while( elButton.firstChild ) elButton.removeChild(elButton.lastChild);
    elButton.appendChild(document.createElement("span"));
  }
  return elButton;
}
setTimeout(function(){
  var elButtons = document.getElementsByClassName("copy-button");
  for ( var i=0; i<elButtons.length; i++ ){
    initCopyButton(elButtons[i],0,0);
  }
},1);
/* The onclick handler for the "Copy Button". */
function clickCopyButton(e){
  e.preventDefault();   /* Mandatory for <a> and <button>. */
  e.stopPropagation();
  if( this.getAttribute("data-copylocked") ) return;
  if( this.disabled ) return;   /* This check is probably redundant. */
  this.setAttribute("data-copylocked","1");
  this.style.transition = "opacity 400ms ease-in-out";
  this.style.opacity = 0;
  var idTarget = this.getAttribute("data-copytarget");
  var elTarget = document.getElementById(idTarget);
  if( elTarget ){
    var text = elTarget.innerText.replace(/^\s+|\s+$/g,"");
    var cchLength = parseInt(this.getAttribute("data-copylength"));
    if( !isNaN(cchLength) && cchLength>0 ){
      text = text.slice(0,cchLength);   /* Assume single-byte chars. */
    }
    copyTextToClipboard(text);
  }
  setTimeout(function(){
    this.style.transition = "";
    this.style.opacity = 1;
    this.removeAttribute("data-copylocked");
  }.bind(this),400);
}
/* Create a temporary <textarea> element and copy the contents to clipboard. */
function copyTextToClipboard(text){
  if( window.clipboardData && window.clipboardData.setData ){
    window.clipboardData.setData("Text",text);
  }else{
    var elTextarea = document.createElement("textarea");
Changes to src/db.c.
123
124
125
126
127
128
129










130
131
132
133
134
135
136
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146







+
+
+
+
+
+
+
+
+
+







  }
  else
#endif /* FOSSIL_ENABLE_JSON */
  if( g.xferPanic && g.cgiOutput==1 ){
    cgi_reset_content();
    @ error Database\serror:\s%F(z)
    cgi_reply();
  }
  if( strstr(z,"attempt to write a readonly database") ){
    static const char *azDbNames[] = { "repository", "localdb", "configdb" };
    int i;
    for(i=0; i<3; i++){
      if( sqlite3_db_readonly(g.db, azDbNames[i])==1 ){
        z = mprintf("\"%s\" is readonly.\n%s", 
                     sqlite3_db_filename(g.db,azDbNames[i]), z);
      }
    }
  }
  fossil_fatal("Database error: %s", z);
}

/*
** Check a result code.  If it is not SQLITE_OK, print the
** corresponding error message and exit.
168
169
170
171
172
173
174


175
176
177
178
179
180
181
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193







+
+







  int iStartLine;           /* Line of zStartFile where transaction started */
  int (*xAuth)(void*,int,const char*,const char*,const char*,const char*);
  void *pAuthArg;           /* Argument to the authorizer */
  const char *zAuthName;    /* Name of the authorizer */
  int bProtectTriggers;     /* True if protection triggers already exist */
  int nProtect;             /* Slots of aProtect used */
  unsigned aProtect[12];    /* Saved values of protectMask */
  int pauseDmlLog;          /* Ignore pDmlLog if positive */
  Blob *pDmlLog;            /* Append DML statements here, of not NULL */
} db = {
  PROTECT_USER|PROTECT_CONFIG|PROTECT_BASELINE,  /* protectMask */
  0, 0, 0, 0, 0, 0, 0, {{0}}, {0}, {0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0}};

/*
** Arrange for the given file to be deleted on a failure.
*/
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+















+







/*
** Possible flags to db_vprepare
*/
#define DB_PREPARE_IGNORE_ERROR  0x001  /* Suppress errors */
#define DB_PREPARE_PERSISTENT    0x002  /* Stmt will stick around for a while */
#endif

/*
** If zSql is a DML statement, append it db.pDmlLog.
*/
static void db_append_dml(const char *zSql){
  size_t nSql;
  if( db.pDmlLog==0 ) return;
  if( db.pauseDmlLog ) return;
  if( zSql==0 ) return;
  nSql = strlen(zSql);
  while( nSql>0 && fossil_isspace(zSql[0]) ){ nSql--; zSql++; }
  while( nSql>0 && fossil_isspace(zSql[nSql-1]) ) nSql--;
  if( nSql<6 ) return;
  if( fossil_strnicmp(zSql, "SELECT", 6)==0 ) return;
  if( fossil_strnicmp(zSql, "PRAGMA", 6)==0 ) return;
  blob_append(db.pDmlLog, zSql, nSql);
  if( zSql[nSql-1]!=';' ) blob_append_char(db.pDmlLog, ';');
  blob_append_char(db.pDmlLog, '\n');
}

/*
** Set the Blob to which DML statement text should be appended.  Set it
** to zero to stop appending DML statement text.
*/
void db_append_dml_to_blob(Blob *pBlob){
  db.pDmlLog = pBlob;
}

/*
** Pause or unpause the DML log
*/
void db_pause_dml_log(void){    db.pauseDmlLog++; }
void db_unpause_dml_log(void){  db.pauseDmlLog--; }

/*
** Prepare a Stmt.  Assume that the Stmt is previously uninitialized.
** If the input string contains multiple SQL statements, only the first
** one is processed.  All statements beyond the first are silently ignored.
*/
int db_vprepare(Stmt *pStmt, int flags, const char *zFormat, va_list ap){
  int rc;
  int prepFlags = 0;
  char *zSql;
  const char *zExtra = 0;
  blob_zero(&pStmt->sql);
  blob_vappendf(&pStmt->sql, zFormat, ap);
  va_end(ap);
  zSql = blob_str(&pStmt->sql);
  db.nPrepare++;
  db_append_dml(zSql);
  if( flags & DB_PREPARE_PERSISTENT ){
    prepFlags = SQLITE_PREPARE_PERSISTENT;
  }
  rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra);
  if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
    db_err("%s\n%s", sqlite3_errmsg(g.db), zSql);
  }else if( zExtra && !fossil_all_whitespace(zExtra) ){
1045
1046
1047
1048
1049
1050
1051

1052
1053
1054
1055
1056
1057
1058
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105







+







  while( rc==SQLITE_OK && z[0] ){
    pStmt = 0;
    rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
    if( rc ){
      db_err("%s: {%s}", sqlite3_errmsg(g.db), z);
    }else if( pStmt ){
      db.nPrepare++;
      db_append_dml(sqlite3_sql(pStmt));
      while( sqlite3_step(pStmt)==SQLITE_ROW ){}
      rc = sqlite3_finalize(pStmt);
      if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z);
    }
    z = zEnd;
  }
  return rc;
1179
1180
1181
1182
1183
1184
1185
1186
1187


1188
1189
1190
1191
1192
1193
1194
1226
1227
1228
1229
1230
1231
1232


1233
1234
1235
1236
1237
1238
1239
1240
1241







-
-
+
+







  }
  db_finalize(&s);
}

/*
** Execute a query.  Return the first column of the first row
** of the result set as a string.  Space to hold the string is
** obtained from malloc().  If the result set is empty, return
** zDefault instead.
** obtained from fossil_strdup() and should be freed using fossil_free().
** If the result set is empty, return a copy of zDefault instead.
*/
char *db_text(const char *zDefault, const char *zSql, ...){
  va_list ap;
  Stmt s;
  char *z;
  va_start(ap, zSql);
  db_vprepare(&s, 0, zSql, ap);
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
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1357
1358
1359
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
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
1423
1424
1425
1426







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+















-
+
-
-
-
-
-
-
-







      sqlite3_result_null(context);
    }else{
      sqlite3_result_int64(context, rid);
    }
  }
}


/*
** SETTING: timeline-utc      boolean default=on
**
** If the timeline-utc setting is true, then Fossil tries to understand
** and display all time values using UTC.  If this setting is false, Fossil
** tries to understand and display time values using the local timezone.
**
** The word "timeline" in the name of this setting is historical.
** This setting applies to all user interfaces of Fossil,
** not just the timeline.
**
** Note that when accessing Fossil using the web interface, the localtime
** used is the localtime on the server, not on the client.
*/
/*
** Return true if Fossil is set to display times as UTC.  Return false
** if it wants to display times using the local timezone.
**
** False is returned if display is set to localtime even if the localtime
** happens to be the same as UTC.
*/
int fossil_ui_utctime(void){
  if( g.fTimeFormat==0 ){
    if( db_get_int("timeline-utc", 1) ){
      g.fTimeFormat = 1; /* UTC */
    }else{
      g.fTimeFormat = 2; /* Localtime */
    }
  }
  return g.fTimeFormat==1;
}

/*
** Return true if Fossil is set to display times using the local timezone.
*/
int fossil_ui_localtime(void){
  return fossil_ui_utctime()==0;
}

/*
** The toLocal() SQL function returns a string that is an argument to a
** date/time function that is appropriate for modifying the time for display.
** If UTC time display is selected, no modification occurs.  If local time
** display is selected, the time is adjusted appropriately.
**
** Example usage:
**
**         SELECT datetime('now',toLocal());
*/
void db_tolocal_function(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  if( g.fTimeFormat==0 ){
  if( fossil_ui_utctime() ){
    if( db_get_int("timeline-utc", 1) ){
      g.fTimeFormat = 1;
    }else{
      g.fTimeFormat = 2;
    }
  }
  if( g.fTimeFormat==1 ){
    sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC);
  }else{
    sqlite3_result_text(context, "localtime", -1, SQLITE_STATIC);
  }
}

/*
1354
1355
1356
1357
1358
1359
1360
1361

1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1434
1435
1436
1437
1438
1439
1440

1441







1442
1443
1444
1445
1446
1447
1448







-
+
-
-
-
-
-
-
-







**         SELECT julianday(:user_input,fromLocal());
*/
void db_fromlocal_function(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  if( g.fTimeFormat==0 ){
  if( fossil_ui_utctime() ){
    if( db_get_int("timeline-utc", 1) ){
      g.fTimeFormat = 1;
    }else{
      g.fTimeFormat = 2;
    }
  }
  if( g.fTimeFormat==1 ){
    sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC);
  }else{
    sqlite3_result_text(context, "utc", -1, SQLITE_STATIC);
  }
}

/*
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
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
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652







-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
+
+
+
+
+
+
+
+
+



















+
+
+
+








/*
** Register the SQL functions that are useful both to the internal
** representation and to the "fossil sql" command.
*/
void db_add_aux_functions(sqlite3 *db){
  sqlite3_create_collation(db, "uintnocase", SQLITE_UTF8,0,uintNocaseCollFunc);
  sqlite3_create_function(db, "checkin_mtime", 2, SQLITE_UTF8, 0,
                          db_checkin_mtime_function, 0, 0);
  sqlite3_create_function(db, "symbolic_name_to_rid", 1, SQLITE_UTF8, 0,
                          db_sym2rid_function, 0, 0);
  sqlite3_create_function(db, "symbolic_name_to_rid", 2, SQLITE_UTF8, 0,
                          db_sym2rid_function, 0, 0);
  sqlite3_create_function(db, "now", 0, SQLITE_UTF8, 0,
  sqlite3_create_function(db, "checkin_mtime", 2,
                          SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
                          0, db_checkin_mtime_function, 0, 0);
  sqlite3_create_function(db, "symbolic_name_to_rid", 1,
                          SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
                          0, db_sym2rid_function, 0, 0);
  sqlite3_create_function(db, "symbolic_name_to_rid", 2,
                          SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
                          0, db_sym2rid_function, 0, 0);
  sqlite3_create_function(db, "now", 0,
                          SQLITE_UTF8|SQLITE_INNOCUOUS, 0,
                          db_now_function, 0, 0);
  sqlite3_create_function(db, "toLocal", 0, SQLITE_UTF8, 0,
                          db_tolocal_function, 0, 0);
  sqlite3_create_function(db, "fromLocal", 0, SQLITE_UTF8, 0,
                          db_fromlocal_function, 0, 0);
  sqlite3_create_function(db, "hextoblob", 1, SQLITE_UTF8, 0,
                          db_hextoblob, 0, 0);
  sqlite3_create_function(db, "toLocal", 0,
                          SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
                          0, db_tolocal_function, 0, 0);
  sqlite3_create_function(db, "fromLocal", 0,
                          SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
                          0, db_fromlocal_function, 0, 0);
  sqlite3_create_function(db, "hextoblob", 1,
                          SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
                          0, db_hextoblob, 0, 0);
  sqlite3_create_function(db, "capunion", 1, SQLITE_UTF8, 0,
                          0, capability_union_step, capability_union_finalize);
  sqlite3_create_function(db, "fullcap", 1, SQLITE_UTF8, 0,
                          capability_fullcap, 0, 0);
  sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0,
                          alert_find_emailaddr_func, 0, 0);
  sqlite3_create_function(db, "display_name", 1, SQLITE_UTF8, 0,
                          alert_display_name_func, 0, 0);
  sqlite3_create_function(db, "obscure", 1, SQLITE_UTF8, 0,
                          db_obscure, 0, 0);
  sqlite3_create_function(db, "protected_setting", 1, SQLITE_UTF8, 0,
                          db_protected_setting_func, 0, 0);
  sqlite3_create_function(db, "win_reserved", 1, SQLITE_UTF8, 0,
                          db_win_reserved_func,0,0);
  sqlite3_create_function(db, "url_nouser", 1, SQLITE_UTF8, 0,
                          url_nouser_func,0,0);
  sqlite3_create_function(db, "chat_msg_from_event", 4,
        SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
        chat_msg_from_event, 0, 0);
  sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
                          file_inode_sql_func,0,0);
  sqlite3_create_function(db, "artifact_to_json", 1, SQLITE_UTF8, 0,
                          artifact_to_json_sql_func,0,0);

}

#if USE_SEE
/*
** This is a pointer to the saved database encryption key string.
*/
1632
1633
1634
1635
1636
1637
1638
1639

1640
1641
1642
1643
1644
1645
1646
1716
1717
1718
1719
1720
1721
1722

1723
1724
1725
1726
1727
1728
1729
1730







-
+







  fossil_setenv("FOSSIL_SEE_PID_KEY", blob_str(&pidKey));
  zSavedKey = p;
  savedKeySize = n;
}

/*
** This function arranges for the database encryption key to be securely
** saved in non-pagable memory (on platforms where this is possible).
** saved in non-pageable memory (on platforms where this is possible).
*/
static void db_save_encryption_key(
  Blob *pKey
){
  void *p = NULL;
  size_t n = 0;
  size_t pageSize = 0;
2101
2102
2103
2104
2105
2106
2107
2108

2109
2110
2111
2112
2113
2114
2115
2185
2186
2187
2188
2189
2190
2191

2192
2193
2194
2195
2196
2197
2198
2199







-
+







  if( strcmp(blob_str(&bNameCheck), g.nameOfExe)==0 ){
    extern int sqlite3_appendvfs_init(
      sqlite3 *, char **, const sqlite3_api_routines *
    );
    sqlite3_appendvfs_init(0,0,0);
    g.zVfsName = "apndvfs";
  }
  blob_zero(&bNameCheck);
  blob_reset(&bNameCheck);
  rc = sqlite3_open_v2(
       zDbName, &db,
       SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
       g.zVfsName
  );
  if( rc!=SQLITE_OK ){
    db_err("[%s]: %s", zDbName, sqlite3_errmsg(db));
2445
2446
2447
2448
2449
2450
2451
2452

2453
2454
2455
2456
2457
2458
2459
2529
2530
2531
2532
2533
2534
2535

2536
2537
2538
2539
2540
2541
2542
2543







-
+







  i64 lsize;

  if( file_access(zDbName, F_OK) ) return 0;
  lsize = file_size(zDbName, ExtFILE);
  if( lsize%1024!=0 || lsize<4096 ) return 0;
  db_open_or_attach(zDbName, "localdb");

  /* Check to see if the check-out database has the lastest schema changes.
  /* Check to see if the check-out database has the latest schema changes.
  ** The most recent schema change (2019-01-19) is the addition of the
  ** vmerge.mhash and vfile.mhash fields.  If the schema has the vmerge.mhash
  ** column, assume everything else is up-to-date.
  */
  if( db_table_has_column("localdb","vmerge","mhash") ){
    return 1;   /* This is a check-out database with the latest schema */
  }
2621
2622
2623
2624
2625
2626
2627




2628
2629
2630

2631
2632
2633
2634
2635
2636
2637
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726







+
+
+
+



+







  sqlite3_finalize(pStmt);
  sqlite3_close(db);
  return res;
}

/*
** COMMAND: test-is-repo
** Usage: %fossil test-is-repo FILENAME...
**
** Test whether the specified files look like a SQLite database
** containing a Fossil repository schema.
*/
void test_is_repo(void){
  int i;
  verify_all_options();
  for(i=2; i<g.argc; i++){
    fossil_print("%s: %s\n",
       db_looks_like_a_repository(g.argv[i]) ? "yes" : " no",
       g.argv[i]
    );
  }
}
2693
2694
2695
2696
2697
2698
2699
2700

2701
2702
2703
2704
2705
2706
2707
2782
2783
2784
2785
2786
2787
2788

2789
2790
2791
2792
2793
2794
2795
2796







-
+







  rebuild_schema_update_2_0();   /* Do the Fossil-2.0 schema updates */
#endif

  /* Additional checks that occur when opening the check-out database */
  if( g.localOpen ){

    /* If the repository database that was just opened has been
    ** eplaced by a clone of the same project, with different RID
    ** replaced by a clone of the same project, with different RID
    ** values, then renumber the RID values stored in various tables
    ** of the check-out database, so that the repository and check-out
    ** databases align.
    */
    if( !db_fingerprint_ok() ){
      if( find_option("no-rid-adjust",0,0)!=0 ){
        /* The --no-rid-adjust command-line option bypasses the RID value
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3214
3215
3216
3217
3218
3219
3220


3221
3222
3223
3224
3225
3226





3227
3228
3229
3230
3231
3232
3233







-
-






-
-
-
-
-







  Blob hash;
  Blob manifest;

  db_unprotect(PROTECT_ALL);
  db_set("content-schema", CONTENT_SCHEMA, 0);
  db_set("aux-schema", AUX_SCHEMA_MAX, 0);
  db_set("rebuilt", get_version(), 0);
  db_set("admin-log", "1", 0);
  db_set("access-log", "1", 0);
  db_multi_exec(
      "INSERT INTO config(name,value,mtime)"
      " VALUES('server-code', lower(hex(randomblob(20))),now());"
      "INSERT INTO config(name,value,mtime)"
      " VALUES('project-code', lower(hex(randomblob(20))),now());"
  );
  if( !db_is_global("autosync") ) db_set_int("autosync", 1, 0);
  if( !db_is_global("localauth") ) db_set_int("localauth", 0, 0);
  if( !db_is_global("timeline-plaintext") ){
    db_set_int("timeline-plaintext", 1, 0);
  }
  db_create_default_users(0, zDefaultUser);
  if( zDefaultUser ) g.zLogin = zDefaultUser;
  user_select();

  if( zTemplate ){
    /*
    ** Copy all settings from the supplied template repository.
3295
3296
3297
3298
3299
3300
3301
3302

3303
3304
3305
3306
3307
3308
3309
3377
3378
3379
3380
3381
3382
3383

3384
3385
3386
3387
3388
3389
3390
3391







-
+







  db_end_transaction(0);
  if( zTemplate ) db_detach("settingSrc");
  if( zProjectName ) fossil_print("project-name: %s\n", zProjectName);
  if( zProjectDesc ) fossil_print("project-description: %s\n", zProjectDesc);
  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",
  fossil_print("admin-user: %s (initial remote-access password is \"%s\")\n",
               g.zLogin, zPassword);
  hash_user_password(g.zLogin);
}

/*
** SQL functions for debugging.
**
3515
3516
3517
3518
3519
3520
3521
3522

3523
3524
3525
3526
3527

3528
3529
3530
3531
3532
3533
3534
3597
3598
3599
3600
3601
3602
3603

3604
3605
3606
3607
3608

3609
3610
3611
3612
3613
3614
3615
3616







-
+




-
+







  return zOut;
}

/*
** Return true if the string zVal represents "true" (or "false").
*/
int is_truth(const char *zVal){
  static const char *const azOn[] = { "on", "yes", "true", "1" };
  static const char *const azOn[] = { "on", "yes", "true" };
  int i;
  for(i=0; i<count(azOn); i++){
    if( fossil_stricmp(zVal,azOn[i])==0 ) return 1;
  }
  return 0;
  return atoi(zVal);
}
int is_false(const char *zVal){
  static const char *const azOff[] = { "off", "no", "false", "0" };
  int i;
  for(i=0; i<count(azOff); i++){
    if( fossil_stricmp(zVal,azOff[i])==0 ) return 1;
  }
3568
3569
3570
3571
3572
3573
3574







3575
3576





3577
3578
3579
3580
3581
3582
3583
3584
3585
3586




3587

3588
3589
3590
3591
3592
3593
3594
3595










3596
3597
3598
3599
3600
3601


3602
3603
3604

3605
3606
3607
3608
3609





3610
3611
3612
3613
3614



3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630












3631
3632
3633
3634
3635
3636
3637
3638
3639
3640

3641

3642
3643
3644
3645
3646
3647








3648
3649
3650





3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662

3663
3664
3665
3666
3667
3668
3669
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664

3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678

3679
3680
3681
3682
3683
3684








3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698


3699
3700



3701
3702
3703



3704
3705
3706
3707
3708
3709
3710



3711
3712
3713
















3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738






3739
3740
3741
3742
3743
3744
3745
3746
3747


3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772







+
+
+
+
+
+
+

-
+
+
+
+
+









-
+
+
+
+

+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+




-
-
+
+
-
-
-
+


-
-
-
+
+
+
+
+


-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+










+

+
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
+
+
+
+
+












+







** Return the text of the string if it is found.  Return NULL if not
** found.
**
** If the zNonVersionedSetting parameter is not NULL then it holds the
** non-versioned value for this setting.  If both a versioned and a
** non-versioned value exist and are not equal, then a warning message
** might be generated.
**
** zCkin is normally NULL.  In that case, the versioned setting is
** take from the local check-out, if a local checkout exists, or from
** check-in named by the g.zOpenRevision global variable.  If zCkin is
** not NULL, then zCkin is the name of the specific check-in from which
** versioned setting value is taken.  When zCkin is not NULL, the cache
** is bypassed.
*/
char *db_get_versioned(const char *zName, char *zNonVersionedSetting){
char *db_get_versioned(
  const char *zName,
  char *zNonVersionedSetting,
  const char *zCkin
){
  char *zVersionedSetting = 0;
  int noWarn = 0;
  int found = 0;
  struct _cacheEntry {
    struct _cacheEntry *next;
    const char *zName, *zValue;
  } *cacheEntry = 0;
  static struct _cacheEntry *cache = 0;

  if( !g.localOpen && g.zOpenRevision==0 ) return zNonVersionedSetting;
  if( !g.localOpen && g.zOpenRevision==0 && zCkin==0 ){
    return zNonVersionedSetting;
  }

  /* Look up name in cache */
  if( zCkin==0 ){
  cacheEntry = cache;
  while( cacheEntry!=0 ){
    if( fossil_strcmp(cacheEntry->zName, zName)==0 ){
      zVersionedSetting = fossil_strdup(cacheEntry->zValue);
      break;
    }
    cacheEntry = cacheEntry->next;
  }
    cacheEntry = cache;
    while( cacheEntry!=0 ){
      if( fossil_strcmp(cacheEntry->zName, zName)==0 ){
        zVersionedSetting = fossil_strdup(cacheEntry->zValue);
        break;
      }
      cacheEntry = cacheEntry->next;
    }
  }

  /* Attempt to read value from file in check-out if there wasn't a cache hit.*/
  if( cacheEntry==0 ){
    Blob versionedPathname;
    Blob setting;
    blob_zero(&versionedPathname);
    blob_zero(&setting);
    blob_init(&versionedPathname, 0, 0);
    blob_init(&setting, 0, 0);
    blob_appendf(&versionedPathname, "%s.fossil-settings/%s",
                 g.zLocalRoot, zName);
    if( !g.localOpen ){
    if( !g.localOpen || zCkin!=0 ){
      /* Repository is in the process of being opened, but files have not been
       * written to disk. Load from the database. */
      Blob noWarnFile;
      if( historical_blob(g.zOpenRevision, blob_str(&versionedPathname),
          &setting, 0) ){
      blob_appendf(&versionedPathname, ".fossil-settings/%s", zName);
      if( historical_blob(zCkin ? zCkin : g.zOpenRevision,
                          blob_str(&versionedPathname),
                          &setting, 0)
      ){
        found = 1;
      }
      /* See if there's a no-warn flag */
      blob_append(&versionedPathname, ".no-warn", -1);
      blob_zero(&noWarnFile);
    }else{
      blob_appendf(&versionedPathname, "%s.fossil-settings/%s",
                   g.zLocalRoot, zName);
      if( historical_blob(g.zOpenRevision, blob_str(&versionedPathname),
          &noWarnFile, 0) ){
        noWarn = 1;
      }
      blob_reset(&noWarnFile);
    }else if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
      /* File exists, and contains the value for this setting. Load from
      ** the file. */
      const char *zFile = blob_str(&versionedPathname);
      if( blob_read_from_file(&setting, zFile, ExtFILE)>=0 ){
        found = 1;
      }
      /* See if there's a no-warn flag */
      blob_append(&versionedPathname, ".no-warn", -1);
      if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
        noWarn = 1;
      if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
        /* File exists, and contains the value for this setting. Load from
        ** the file. */
        const char *zFile = blob_str(&versionedPathname);
        if( blob_read_from_file(&setting, zFile, ExtFILE)>=0 ){
          found = 1;
        }
        /* See if there's a no-warn flag */
        blob_append(&versionedPathname, ".no-warn", -1);
        if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
          noWarn = 1;
        }
      }
    }
    blob_reset(&versionedPathname);
    if( found ){
      blob_strip_comment_lines(&setting, &setting);
      blob_trim(&setting); /* Avoid non-obvious problems with line endings
                           ** on boolean properties */
      zVersionedSetting = fossil_strdup(blob_str(&setting));
    }
    blob_reset(&setting);

    /* Store result in cache, which can be the value or 0 if not found */
    if( zCkin==0 ){
    cacheEntry = (struct _cacheEntry*)fossil_malloc(sizeof(struct _cacheEntry));
    cacheEntry->next = cache;
    cacheEntry->zName = zName;
    cacheEntry->zValue = fossil_strdup(zVersionedSetting);
    cache = cacheEntry;
  }
      cacheEntry = (struct _cacheEntry*)fossil_malloc(sizeof(*cacheEntry));
      cacheEntry->next = cache;
      cacheEntry->zName = zName;
      cacheEntry->zValue = fossil_strdup(zVersionedSetting);
      cache = cacheEntry;
    }
  }

  /* Display a warning? */
  if( zVersionedSetting!=0 && zNonVersionedSetting!=0
   && zNonVersionedSetting[0]!='\0' && !noWarn
  if( zVersionedSetting!=0
   && zNonVersionedSetting!=0
   && zNonVersionedSetting[0]!='\0'
   && zCkin==0
   && !noWarn
  ){
    /* There's a versioned setting, and a non-versioned setting. Tell
    ** the user about the conflict */
    fossil_warning(
        "setting %s has both versioned and non-versioned values: using "
        "versioned value from file \"%/.fossil-settings/%s\" (to silence "
        "this warning, either create an empty file named "
        "\"%/.fossil-settings/%s.no-warn\" in the check-out root, or delete "
        "the non-versioned setting with \"fossil unset %s\")", zName,
        g.zLocalRoot, zName, g.zLocalRoot, zName, zName
    );
  }

  /* Prefer the versioned setting */
  return ( zVersionedSetting!=0 ) ? zVersionedSetting : zNonVersionedSetting;
}


/*
** Get and set values from the CONFIG, GLOBAL_CONFIG and VVAR table in the
3699
3700
3701
3702
3703
3704
3705
3706

3707
3708
3709
3710
3711
3712
3713
3802
3803
3804
3805
3806
3807
3808

3809
3810
3811
3812
3813
3814
3815
3816







-
+







    }
    db_reset(&q2);
  }
  if( pSetting!=0 && pSetting->versionable ){
    /* This is a versionable setting, try and get the info from a
    ** checked-out file */
    char * zZ = z;
    z = db_get_versioned(zName, z);
    z = db_get_versioned(zName, z, 0);
    if(zZ != z){
      fossil_free(zZ);
    }
  }
  if( z==0 ){
    if( zDefault==0 && pSetting && pSetting->def[0] ){
      z = fossil_strdup(pSetting->def);
3840
3841
3842
3843
3844
3845
3846
3847

3848
3849
3850
3851
3852
3853
3854
3943
3944
3945
3946
3947
3948
3949

3950
3951
3952
3953
3954
3955
3956
3957







-
+







  }else if( is_false(zVal) ){
    dflt = 0;
  }
  fossil_free(zVal);
  return dflt;
}
int db_get_versioned_boolean(const char *zName, int dflt){
  char *zVal = db_get_versioned(zName, 0);
  char *zVal = db_get_versioned(zName, 0, 0);
  if( zVal==0 ) return dflt;
  if( is_truth(zVal) ) return 1;
  if( is_false(zVal) ) return 0;
  return dflt;
}
char *db_lget(const char *zName, const char *zDefault){
  return db_text(zDefault,
3969
3970
3971
3972
3973
3974
3975








3976
3977

3978
3979









3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995



























3996
3997
3998
3999
4000
4001
4002
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087

4088
4089

4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148







+
+
+
+
+
+
+
+

-
+

-
+
+
+
+
+
+
+
+
+
















+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








/*
** Get the manifest setting.  For backwards compatibility first check if the
** value is a boolean.  If it's not a boolean, treat each character as a flag
** to enable a manifest type.  This system puts certain boundary conditions on
** which letters can be used to represent flags (any permutation of flags must
** not be able to fully form one of the boolean values).
**
** "manifest" is a versionable setting.  But we do not issue a warning
** if there is a conflict.  Instead, the value returned is the value for
** the versioned setting if the versioned setting exists, or the ordinary
** setting otherwise.
**
** The argument zCkin is the specific check-in for which we want the
** manifest setting.
*/
int db_get_manifest_setting(void){
int db_get_manifest_setting(const char *zCkin){
  int flg;
  char *zVal = db_get("manifest", 0);
  char *zVal;
  
  /* Look for the versioned setting first */
  zVal = db_get_versioned("manifest", 0, zCkin);

  if( zVal==0 && g.repositoryOpen ){
    /* No versioned setting, look for the repository setting second */
    zVal = db_text(0, "SELECT value FROM config WHERE name='manifest'");
  }
  if( zVal==0 || is_false(zVal) ){
    return 0;
  }else if( is_truth(zVal) ){
    return MFESTFLG_RAW|MFESTFLG_UUID;
  }
  flg = 0;
  while( *zVal ){
    switch( *zVal ){
      case 'r': flg |= MFESTFLG_RAW;  break;
      case 'u': flg |= MFESTFLG_UUID; break;
      case 't': flg |= MFESTFLG_TAGS; break;
    }
    zVal++;
  }
  return flg;
}

/*
** COMMAND: test-manifest-setting
**
** Usage: %fossil test-manifest-setting VERSION VERSION ...
**
** Display the value for the "manifest" setting for various versions
** of the repository.
*/
void test_manfest_setting_cmd(void){
  int i;
  db_find_and_open_repository(0, 0);
  for(i=2; i<g.argc; i++){
    int m = db_get_manifest_setting(g.argv[i]);
    fossil_print("%s:\n", g.argv[i]);
    fossil_print("   flags = 0x%02x\n", m);
    if( m & MFESTFLG_RAW ){
      fossil_print("   manifest\n");
    }
    if( m & MFESTFLG_UUID ){
      fossil_print("   manifest.uuid\n");
    }
    if( m & MFESTFLG_TAGS ){
      fossil_print("   manifest.tags\n");
    }
  }
}


/*
** Record the name of a local repository in the global_config() database.
** The repository filename %s is recorded as an entry with a "name" field
** of the following form:
**
4078
4079
4080
4081
4082
4083
4084
4085

4086
4087
4088
4089
4090
4091
4092
4224
4225
4226
4227
4228
4229
4230

4231
4232
4233
4234
4235
4236
4237
4238







-
+







** for the repository is created with its root at the current working
** directory, or in DIR if the "--workdir DIR" is used.  If VERSION is
** specified then that version is checked out.  Otherwise the most recent
** check-in on the main branch (usually "trunk") is used.
**
** REPOSITORY can be the filename for a repository that already exists on the
** local machine or it can be a URI for a remote repository.  If REPOSITORY
** is a URI in one of the formats recognized by the [[clone]] command, then
** is a URI in one of the formats recognized by the [[clone]] command, the
** remote repo is first cloned, then the clone is opened. The clone will be
** stored in the current directory, or in DIR if the "--repodir DIR" option
** is used. The name of the clone will be taken from the last term of the URI.
** For "http:" and "https:" URIs, you can append an extra term to the end of
** the URI to get any repository name you like. For example:
**
**     fossil open https://fossil-scm.org/home/new-name
4102
4103
4104
4105
4106
4107
4108

4109
4110
4111
4112
4113
4114
4115
4116
4117



4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131

4132
4133
4134
4135








4136
4137
4138
4139
4140
4141
4142
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301







+









+
+
+














+




+
+
+
+
+
+
+
+







**   -f|--force        Continue with the open even if the working directory is
**                     not empty, or if auto-sync fails.
**   --force-missing   Force opening a repository with missing content
**   -k|--keep         Only modify the manifest file(s)
**   --nested          Allow opening a repository inside an opened check-out
**   --nosync          Do not auto-sync the repository prior to opening even
**                     if the autosync setting is on.
**   --proxy PROXY     Use PROXY as http proxy during sync operation
**   --repodir DIR     If REPOSITORY is a URI that will be cloned, store
**                     the clone in DIR rather than in "."
**   --setmtime        Set timestamps of all files to match their SCM-side
**                     times (the timestamp of the last check-in which modified
**                     them).
**   --verbose         If passed a URI then this flag is passed on to the clone
**                     operation, otherwise it has no effect
**   --workdir DIR     Use DIR as the working directory instead of ".". The DIR
**                     directory is created if it does not exist.
**   --reopen REPOFILE Changes the repository file used by the current checkout
**                     to REPOFILE. Use this after moving a checkout's
**                     repository. This may lose stash and bisect history.
**
** See also: [[close]], [[clone]]
*/
void cmd_open(void){
  int emptyFlag;
  int keepFlag;
  int forceMissingFlag;
  int allowNested;
  int setmtimeFlag;              /* --setmtime.  Set mtimes on files */
  int bForce = 0;                /* --force.  Open even if non-empty dir */
  static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 };
  const char *zWorkDir;          /* --workdir value */
  const char *zRepo = 0;         /* Name of the repository file */
  const char *zRepoDir = 0;      /* --repodir value */
  const char *zReopen = 0;       /* --reopen REPOFILE */
  char *zPwd;                    /* Initial working directory */
  int isUri = 0;                 /* True if REPOSITORY is a URI */
  int nLocal;                    /* Number of preexisting files in cwd */
  int bVerbose = 0;              /* --verbose option for clone */

  zReopen = find_option("reopen",0,1);
  if( 0!=zReopen ){
    g.argc = 3;
    g.argv[2] = (char*)zReopen;
    move_repo_cmd();
    return;
  }

  url_proxy_options();
  emptyFlag = find_option("empty",0,0)!=0;
  keepFlag = find_option("keep","k",0)!=0;
  forceMissingFlag = find_option("force-missing",0,0)!=0;
  allowNested = find_option("nested",0,0)!=0;
  setmtimeFlag = find_option("setmtime",0,0)!=0;
4178
4179
4180
4181
4182
4183
4184
4185

4186
4187
4188
4189
4190
4191
4192
4337
4338
4339
4340
4341
4342
4343

4344
4345
4346
4347
4348
4349
4350
4351







-
+







    }
    if( file_chdir(zWorkDir, 0) ){
      fossil_fatal("unable to make %s the working directory", zWorkDir);
    }
  }
  if( keepFlag==0
   && bForce==0
   && (nLocal = file_directory_size(".", 0, 1))>0
   && (nLocal = file_directory_list(".", 0, 1, 2, 0))>0
   && (nLocal>1 || isUri || !file_in_cwd(zRepo))
  ){
    fossil_fatal("directory %s is not empty\n"
                 "use the -f (--force) option to override\n"
                 "or the -k (--keep) option to keep local files unchanged",
                 file_getcwd(0,0));
  }
4242
4243
4244
4245
4246
4247
4248
4249

4250
4251
4252
4253
4254
4255
4256
4401
4402
4403
4404
4405
4406
4407

4408
4409
4410
4411
4412
4413
4414
4415







-
+







  db_open_repository(zRepo);

  /* Figure out which revision to open. */
  if( !emptyFlag ){
    if( g.argc==4 ){
      g.zOpenRevision = g.argv[3];
    }else if( db_exists("SELECT 1 FROM event WHERE type='ci'") ){
      g.zOpenRevision = db_get("main-branch", 0);
      g.zOpenRevision = fossil_strdup(db_main_branch());
    }
    if( autosync_loop(SYNC_PULL, !bForce, "open") && !bForce ){
      fossil_fatal("unable to auto-sync the repository");
    }
  }


4291
4292
4293
4294
4295
4296
4297
















4298
4299
4300
4301





4302
4303

4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317


4318




4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334






4335

4336
4337
4338
4339
4340
























4341
4342
4343
4344

4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362

4363
4364
4365
4366
4367
4368
4369
4370
4371
4372
4373
4374
4375
4376
4377
4378
4379
4380

4381
4382
4383
4384

4385
4386
4387
4388
4389
4390
4391

4392
4393
4394
4395
4396
4397
4398
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482

4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499

4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525

4526
4527
4528



4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555

4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573

4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596

4597
4598
4599
4600
4601
4602
4603

4604
4605
4606
4607
4608
4609
4610
4611







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




+
+
+
+
+

-
+














+
+
-
+
+
+
+
















+
+
+
+
+
+
-
+


-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



-
+

















-
+


















+



-
+






-
+







    if(vid!=0){
      vfile_check_signature(vid, CKSIG_SETMTIME);
    }
  }
  g.argc = 2;
  info_cmd();
}

/*
** Return true if pSetting has its default value assuming its
** current value is zVal.
*/
int setting_has_default_value(const Setting *pSetting, const char *zVal){
  if( zVal==0 ) return 1;
  if( pSetting->def==0 ) return 0;
  if( pSetting->width==0 ){
    return is_false(pSetting->def)==is_false(zVal);
  }
  if( fossil_strcmp(pSetting->def, zVal)==0 ) return 1;
  if( is_false(zVal) && is_false(pSetting->def) ) return 1;
  if( is_truth(zVal) && is_truth(pSetting->def) ) return 1;
  return 0;
}

/*
** Print the current value of a setting identified by the pSetting
** pointer.
**
** Only show the value, not the setting name, if valueOnly is true.
**
** Show nothing if bIfChng is true and the setting is not currently set
** or is set to its default value.
*/
void print_setting(const Setting *pSetting, int valueOnly){
void print_setting(const Setting *pSetting, int valueOnly, int bIfChng){
  Stmt q;
  int versioned = 0;
  if( pSetting->versionable && g.localOpen ){
    /* Check to see if this is overridden by a versionable settings file */
    Blob versionedPathname;
    blob_zero(&versionedPathname);
    blob_appendf(&versionedPathname, "%s.fossil-settings/%s",
                 g.zLocalRoot, pSetting->name);
    if( file_size(blob_str(&versionedPathname), ExtFILE)>=0 ){
      versioned = 1;
    }
    blob_reset(&versionedPathname);
  }
  if( valueOnly && versioned ){
    const char *zVal = db_get_versioned(pSetting->name, NULL, NULL);
    if( !bIfChng || (zVal!=0 && fossil_strcmp(zVal, pSetting->def)!=0) ){
    fossil_print("%s\n", db_get_versioned(pSetting->name, NULL));
      fossil_print("%s\n", db_get_versioned(pSetting->name, NULL, NULL));
    }else{
      versioned = 0;
    }
    return;
  }
  if( g.repositoryOpen ){
    db_prepare(&q,
       "SELECT '(local)', value FROM config WHERE name=%Q"
       " UNION ALL "
       "SELECT '(global)', value FROM global_config WHERE name=%Q",
       pSetting->name, pSetting->name
    );
  }else{
    db_prepare(&q,
      "SELECT '(global)', value FROM global_config WHERE name=%Q",
      pSetting->name
    );
  }
  if( db_step(&q)==SQLITE_ROW ){
    const char *zVal = db_column_text(&q,1);
    if( bIfChng && setting_has_default_value(pSetting,zVal) ){
      if( versioned ){
        fossil_print("%-24s (versioned)\n", pSetting->name);
        versioned = 0;
      }
    if( valueOnly ){
    }else if( valueOnly ){
      fossil_print("%s\n", db_column_text(&q, 1));
    }else{
      fossil_print("%-20s %-8s %s\n", pSetting->name, db_column_text(&q, 0),
          db_column_text(&q, 1));
    }
      const char *zVal = (const char*)db_column_text(&q,1);
      const char *zName = (const char*)db_column_text(&q,0);
      if( zVal==0 ) zVal = "NULL";
      if( strchr(zVal,'\n')==0 ){
        fossil_print("%-24s %-11s %s\n", pSetting->name, zName, zVal);
      }else{
        fossil_print("%-24s %-11s\n", pSetting->name, zName);
        while( zVal[0] ){
          char *zNL = strchr(zVal, '\n');
          if( zNL==0 ){
            fossil_print("    %s\n", zVal);
            break;
          }else{
            int n = (int)(zNL - zVal);
            while( n>0 && fossil_isspace(zVal[n-1]) ){ n--; }
            fossil_print("    %.*s\n", n, zVal);
            zVal = zNL+1;
          }
        }
      }
    }
  }else if( bIfChng ){
    /* Display nothing */
    versioned = 0;
  }else if( valueOnly ){
    fossil_print("\n");
  }else{
    fossil_print("%-20s\n", pSetting->name);
    fossil_print("%-24s\n", pSetting->name);
  }
  if( versioned ){
    fossil_print("  (overridden by contents of file .fossil-settings/%s)\n",
                 pSetting->name);
  }
  db_finalize(&q);
}

#if INTERFACE
/*
** Define all settings, which can be controlled via the set/unset
** command.
**
** var is the name of the internal configuration name for db_(un)set.
** If var is 0, the settings name is used.
**
** width is the length for the edit field on the behavior page, 0 is
** used for on/off checkboxes. A negative value indicates that that
** used for on/off checkboxes. A negative value indicates that the
** page should not render this setting. Such values may be rendered
** separately/manually on another page, e.g., /setup_access, and are
** exposed via the CLI settings command.
**
** The behaviour page doesn't use a special layout. It lists all
** set-commands and displays the 'set'-help as info.
*/
struct Setting {
  const char *name;     /* Name of the setting */
  const char *var;      /* Internal variable name used by db_set() */
  int width;            /* Width of display.  0 for boolean values and
                        ** negative for values which should not appear
                        ** on the /setup_settings page. */
  char versionable;     /* Is this setting versionable? */
  char forceTextArea;   /* Force using a text area for display? */
  char sensitive;       /* True if this a security-sensitive setting */
  const char *def;      /* Default value */
};

#endif /* INTERFACE */

/*
** SETTING: access-log      boolean default=off
** SETTING: access-log      boolean default=on
**
** When the access-log setting is enabled, all login attempts (successful
** and unsuccessful) on the web interface are recorded in the "access" table
** of the repository.
*/
/*
** SETTING: admin-log       boolean default=off
** SETTING: admin-log       boolean default=on
**
** When the admin-log setting is enabled, configuration changes are recorded
** in the "admin_log" table of the repository.
*/
/*
** SETTING: allow-symlinks  boolean default=off sensitive
**
4557
4558
4559
4560
4561
4562
4563
4564

4565
4566
4567
4568

4569
4570
4571
4572
4573
4574





4575
4576
4577
4578
4579
4580
4581
4582
4583
4584


4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4770
4771
4772
4773
4774
4775
4776

4777
4778
4779
4780

4781
4782



4783

4784
4785
4786
4787
4788
4789
4790

4791
4792
4793
4794
4795


4796
4797



4798
4799
4800
4801
4802
4803
4804







-
+



-
+

-
-
-

-
+
+
+
+
+


-





-
-
+
+
-
-
-







** that the "clean" command will delete without prompting or allowing
** undo.  Example: *.a,*.o,*.so  The parsing rules are complex;
** see https://fossil-scm.org/home/doc/trunk/www/globs.md#syntax
*/
/*
** SETTING: clearsign       boolean default=off
** When enabled, fossil will attempt to sign all commits
** with gpg.  When disabled, commits will be unsigned.
** with gpg or ssh.  When disabled, commits will be unsigned.
*/
/*
** SETTING: comment-format  width=16 default=1
** Set the default options for printing timeline comments to the console.
** Set the algorithm for printing timeline comments to the console.
**
** The global --comfmtflags command-line option (or alias --comment-format)
** overrides this setting.
**
** Possible values are:
**    1     Activate the legacy comment printing format (default).
**    1     Use the original comment printing algorithm:
**             *   Leading and trailing whitespace is removed
**             *   Internal whitespace is converted into a single space (0x20)
**             *   Line breaks occurs at whitespace or hyphens if possible
**          This is the recommended value and the default.
**
** Or a bitwise combination of the following flags:
**    0     Activate the newer (non-legacy) comment printing format.
**    2     Trim leading and trailing CR and LF characters.
**    4     Trim leading and trailing white space characters.
**    8     Attempt to break lines on word boundaries.
**   16     Break lines before the original comment embedded in other text.
**
** Note: To preserve line breaks, activate the newer (non-legacy) comment
** printing format (i.e. set to "0", or a combination not including "1").
** Note: To preserve line breaks and/or other whitespace within comment text,
** make this setting some integer value that omits the "1" bit.
**
** Note: The options for timeline comments displayed on the web UI can be
** configured through the /setup_timeline web page.
*/
/*
** SETTING: crlf-glob       width=40 versionable block-text
** The VALUE of this setting is a list of GLOB patterns matching files
** in which it is allowed to have CR, CR+LF or mixed line endings,
** suppressing Fossil's normal warning about this. Set it to "*" to
** disable CR+LF checking entirely.  Example: *.md,*.txt
4630
4631
4632
4633
4634
4635
4636








4637
4638
4639
4640
4641
4642

4643
4644
4645
4646
4647
4648
4649
4840
4841
4842
4843
4844
4845
4846
4847
4848
4849
4850
4851
4852
4853
4854
4855
4856
4857
4858
4859

4860
4861
4862
4863
4864
4865
4866
4867







+
+
+
+
+
+
+
+





-
+







** SETTING: dotfiles        boolean versionable default=off
** If enabled, include --dotfiles option for all compatible commands.
*/
/*
** SETTING: editor          width=32 sensitive
** The value is an external command that will launch the
** text editor command used for check-in comments.
**
** If this value is not set, then environment variables VISUAL and
** EDITOR are consulted, in that order.  If neither of those are set,
** then a search is made for common text editors, including
** "notepad", "nano", "pico", "jove", "edit", "vi", "vim", and "ed".
**
** If this setting is false ("off", "no", "false", or "0") then no
** text editor is used.
*/
/*
** SETTING: empty-dirs      width=40 versionable block-text
** The value is a list of pathnames parsed according to the same rules as
** the *-glob settings.  On update and checkout commands, if no directory
** exists with that name, an empty directory will be be created, even if
** exists with that name, an empty directory will be created, even if
** it must create one or more parent directories.
*/
/*
** SETTING: encoding-glob   width=40 versionable block-text
** The VALUE of this setting is a list of GLOB patterns matching files that
** the "commit" command will ignore when issuing warnings about text files
** that may use another encoding than ASCII or UTF-8. Set to "*" to disable
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
4692
4693
4694

4695
4696


4697
4698
4699
4700
4701
4702
4703
4898
4899
4900
4901
4902
4903
4904








4905
4906

4907
4908
4909
4910
4911
4912
4913
4914
4915







-
-
-
-
-
-
-
-
+

-
+
+







** commits.  If enabled on a server, whenever a client attempts
** to obtain a check-in lock during auto-sync, the server will
** send the "pragma avoid-delta-manifests" statement in its reply,
** which will cause the client to avoid generating a delta
** manifest.
*/
/*
** SETTING: forum-close-policy    boolean default=off
** If true, forum moderators may close/re-open forum posts, and reply
** to closed posts. If false, only administrators may do so. Note that
** this only affects the forum web UI, not post-closing tags which
** arrive via the command-line or from synchronization with a remote.
*/
/*
** SETTING: gdiff-command    width=40 default=gdiff sensitive
** SETTING: gdiff-command    width=40 sensitive
** The value is an external command to run when performing a graphical
** diff. If undefined, text diff will be used.
** diff. If undefined, a --tk diff is done if commands "tclsh" and "wish"
** are on PATH, or a --by diff is done if "tclsh" or "wish" are unavailable.
*/
/*
** SETTING: gmerge-command   width=40 sensitive
** The value is a graphical merge conflict resolver command operating
** on four files.  Examples:
**
**     kdiff3 "%baseline" "%original" "%merge" -o "%output"
4830
4831
4832
4833
4834
4835
4836
4837


4838
4839
4840
4841
4842
4843
4844
5042
5043
5044
5045
5046
5047
5048

5049
5050
5051
5052
5053
5054
5055
5056
5057







-
+
+







** the associated files within the check-out -AND- the "rm"
** and "delete" commands will also remove the associated
** files from within the check-out.
*/
/*
** SETTING: pgp-command      width=40 sensitive
** Command used to clear-sign manifests at check-in.
** Default value is "gpg --clearsign -o"
** Default value is "gpg --clearsign -o".
** For SSH, use e.g. "ssh-keygen -q -Y sign -n fossilscm -f ~/.ssh/id_ed25519"
*/
/*
** SETTING: proxy            width=32 default=system
** URL of the HTTP proxy. If undefined or "system", the "http_proxy"
** environment variable is consulted. If "off", a direct HTTP connection is
** used.
*/
4876
4877
4878
4879
4880
4881
4882
4883


4884
4885
4886
4887
4888
4889
4890
5089
5090
5091
5092
5093
5094
5095

5096
5097
5098
5099
5100
5101
5102
5103
5104







-
+
+







**    5)  fossil all ui
**    6)  fossil all server
**
** All repositories are searched (in lexicographical order) and the first
** repository with a non-zero "repolist-skin" value is used as the skin
** for the repository list page.  If none of the repositories on the list
** have a non-zero "repolist-skin" setting then the repository list is
** displayed using unadorned HTML ("skinless").
** displayed using unadorned HTML ("skinless"), with the page title taken
** from the FOSSIL_REPOLIST_TITLE environment variable.
**
** If repolist-skin has a value of 2, then the repository is omitted from
** the list in use cases 1 through 4, but not for 5 and 6.
*/
/*
** SETTING: self-pw-reset    boolean default=off sensitive
** Allow users to request that an email containing a hyperlink
5081
5082
5083
5084
5085
5086
5087


5088
5089
5090
5091
5092
5093
5094
5095
5096
5097

5098
5099
5100
5101
5102
5103
5104
5295
5296
5297
5298
5299
5300
5301
5302
5303
5304
5305

5306
5307
5308
5309
5310
5311
5312
5313
5314
5315
5316
5317
5318
5319
5320







+
+


-







+







** that applies to all repositories.  The local values are stored in the
** "config" table of the repository and the global values are stored in the
** configuration database.  If both a local and a global value exists for a
** setting, the local value takes precedence.  This command normally operates
** on the local settings.  Use the --global option to change global settings.
**
** Options:
**   --changed  Only show settings if the value differs from the default
**   --exact    Only consider exact name matches
**   --global   Set or unset the given property globally instead of
**              setting or unsetting it for the open repository only
**   --exact    Only consider exact name matches
**   --value    Only show the value of a given property (implies --exact)
**
** See also: [[configuration]]
*/
void setting_cmd(void){
  int i;
  int globalFlag = find_option("global","g",0)!=0;
  int bIfChng = find_option("changed",0,0)!=0;
  int exactFlag = find_option("exact",0,0)!=0;
  int valueFlag = find_option("value",0,0)!=0;
  /* Undocumented "--test-for-subsystem SUBSYS" option used to test
  ** the db_get_for_subsystem() interface: */
  const char *zSubsys = find_option("test-for-subsystem",0,1);
  int unsetFlag = g.argv[1][0]=='u';
  int nSetting;
5115
5116
5117
5118
5119
5120
5121
5122
5123
5124
5125
5126
5127

5128
5129
5130
5131
5132
5133
5134
5331
5332
5333
5334
5335
5336
5337

5338
5339
5340
5341

5342
5343
5344
5345
5346
5347
5348
5349







-




-
+







  if( unsetFlag && g.argc!=3 ){
    usage("PROPERTY ?-global?");
  }
  if( valueFlag ){
    if( g.argc!=3 ){
      fossil_fatal("--value is only supported when qurying a given property");
    }
    exactFlag = 1;
  }

  if( g.argc==2 ){
    for(i=0; i<nSetting; i++){
      print_setting(&aSetting[i], 0);
      print_setting(&aSetting[i], 0, bIfChng);
    }
  }else if( g.argc==3 || g.argc==4 ){
    const char *zName = g.argv[2];
    int n = (int)strlen(zName);
    const Setting *pSetting = db_find_setting(zName, !exactFlag);
    if( pSetting==0 ){
      fossil_fatal("no such setting: %s", zName);
5175
5176
5177
5178
5179
5180
5181
5182

5183
5184
5185
5186
5187
5188
5189
5390
5391
5392
5393
5394
5395
5396

5397
5398
5399
5400
5401
5402
5403
5404







-
+







          fossil_print("%s (subsystem %s) ->",  pSetting->name, zSubsys);
          if( zValue ){
            fossil_print(" [%s]", zValue);
            fossil_free(zValue);
          }
          fossil_print("\n");
        }else{
          print_setting(pSetting, valueFlag);
          print_setting(pSetting, valueFlag, bIfChng);
        }
        pSetting++;
      }
    }
  }else{
    usage("?PROPERTY? ?VALUE? ?-global?");
  }
5359
5360
5361
5362
5363
5364
5365
5366
5367


5368
5369
5370
5371
5372

5373
5374
5375
5376
5377
5378
5379
5574
5575
5576
5577
5578
5579
5580


5581
5582
5583
5584
5585
5586

5587
5588
5589
5590
5591
5592
5593
5594







-
-
+
+




-
+







  fossil_print("Repository database: %s\n", g.zRepositoryName);
  fossil_print("Local database:      %s\n", g.zLocalDbName);
  fossil_print("Config database:     %s\n", g.zConfigDbName);
}

/*
** Compute a "fingerprint" on the repository.  A fingerprint is used
** to verify that that the repository has not been replaced by a clone
** of the same repository.  More precisely, a fingerprint are used to
** to verify that the repository has not been replaced by a clone
** of the same repository.  More precisely, a fingerprint is used to
** verify that the mapping between SHA3 hashes and RID values is unchanged.
**
** The check-out database ("localdb") stores RID values.  When associating
** a check-out database against a repository database, it is useful to verify
** the fingerprint so that we know tha the RID values in the check-out
** the fingerprint so that we know that the RID values in the check-out
** database still correspond to the correct entries in the BLOB table of
** the repository.
**
** The fingerprint is based on the RCVFROM table.  When constructing a
** new fingerprint, use the most recent RCVFROM entry.  (Set rcvid==0 to
** accomplish this.)  When verifying an old fingerprint, use the same
** RCVFROM entry that generated the fingerprint in the first place.
5425
5426
5427
5428
5429
5430
5431
5432

5433
5434
5435
5436
5437
5438
5439
5640
5641
5642
5643
5644
5645
5646

5647
5648
5649
5650
5651
5652
5653
5654







-
+








/*
** COMMAND: test-fingerprint
**
** Usage: %fossil test-fingerprint ?RCVID?
**
** Display the repository fingerprint using the supplied RCVID or
** using the latest RCVID if not is given on the command line.
** using the latest RCVID if none is given on the command line.
** Show both the legacy and the newer version of the fingerprint,
** and the currently stored fingerprint if there is one.
*/
void test_fingerprint(void){
  int rcvid = 0;
  db_find_and_open_repository(OPEN_ANY_SCHEMA,0);
  if( g.argc==3 ){
Changes to src/default.css.
1
2
3
4
5



6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15





+
+
+







/* This CSS file holds the default implementations for all of fossil's
   CSS classes. When /style.css is requested, the rules in this file
   are emitted first, followed by (1) page-specific CSS (if any) and
   (2) skin-specific CSS.
*/
body {
  z-index: 0 /* Used by robot.c:robot_proofofwork() and href.js */;
}
div.sidebox {
  float: right;
  background-color: white;
  border-width: medium;
  border-style: double;
  margin: 10px;
}
52
53
54
55
56
57
58



59
60
61
62
63
64
65
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71







+
+
+







tr.timelineCurrent td {
  border-radius: 0;
  border-width: 0;
}
span.timelineLeaf {
  font-weight: bold;
}
span.timelineHash {
  font-weight: bold;
}
span.timelineHistDsp {
  font-weight: bold;
}
td.timelineTime {
  vertical-align: top;
  text-align: right;
  white-space: nowrap;
555
556
557
558
559
560
561

562
563
564
565
566
567
568
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575







+








/* Rules governing diff layout and colors */
table.diff {
  width: 100%;
  border-spacing: 0;
  border-radius: 5px;
  border: 1px solid black;
  overflow: hidden; /* Prevent background from overlapping rounded borders. */
  font-size: 80%;
}
table.diff td.diffln{
  padding: 0;
}
table.diff td.diffln > pre{
  padding: 0 0.25em 0 0.5em;
746
747
748
749
750
751
752
























753
754
755
756
757
758
759
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







}
body.tkt div.content ol.tkt-changes > li:target > p > span {
  border-bottom: 3px solid gold;
}
body.tkt div.content ol.tkt-changes > li:target > ol {
  border-left: 1px solid gold;
}
body.tkt .tktCommentArea {
  display: flex;
  flex-direction: column;
}
body.tkt .newest-first-controls {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
}
body.tkt .tktCommentArea.reverse {
  flex-direction: column-reverse;
}
body.cpage-ckout .file-change-line,
body.cpage-info .file-change-line,
body.cpage-vinfo .file-change-line,
body.cpage-ci .file-change-line,
body.cpage-vdiff .file-change-line {
  margin-top: 16px;
  margin-bottom: 16px;
  margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
}

span.modpending {
  color: #b03800;
  font-style: italic;
}
pre.th1result {
  white-space: pre-wrap;
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
1148
1149
1150
1151



1152

1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
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
1182
1183
1184
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
1210





1211
1212
1213
1214
1215
1216
1217







-
-
+
+
+
+






+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+














+
+
+
-
+



-
-
-
-
-







}
label {
  white-space: nowrap;
}
label[for] {
  cursor: pointer;
}
.copy-button {
  display: inline-block;
button.copy-button,
button.copy-button:hover,
button.copy-button:focus,
button.copy-button:active {
  width: 14px;
  height: 14px;
/*Note: .24em is slightly smaller than the average width of a normal space.*/
  margin: -2px .24em 0 0;
  padding: 0;
  border: 0;
  outline: 0;
  background: none;
  font-size: inherit; /* Required for horizontal spacing. */
  vertical-align: middle;
  user-select: none;
  cursor: pointer;
}
button.copy-button-flipped,
button.copy-button-flipped:hover,
button.copy-button-flipped:focus,
button.copy-button-flipped:active {
  margin: -2px 0 0 .24em;
}
button.copy-button span {
  display: block;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' \
viewBox='0,0,14,14'%3E%3Cpath style='fill:black;opacity:0' \
d='M14,14H0V0h14v14z'/%3E%3Cpath style='fill:rgb(240,240,240)' \
d='M1,0h6.6l2,2h1l3.4,3.4v8.6h-10v-2h-3z'/%3E%3Cpath style='fill:rgb(64,64,64)' \
d='M2,1h5l3,3v7h-8z'/%3E%3Cpath style='fill:rgb(248,248,248)' \
d='M3,2h3.6l2.4,2.4v5.6h-6z'/%3E%3Cpath style='fill:rgb(80,128,208)' \
d='M4,5h4v1h-4zm0,2h4v1h-4z'/%3E%3Cpath style='fill:rgb(64,64,64)' \
d='M5,3h5l3,3v7h-8z'/%3E%3Cpath style='fill:rgb(248,248,248)' \
d='M10,4.4v1.6h1.6zm-4,-0.6h3v3h-3zm0,3h6v5.4h-6z'/%3E%3Cpath style='fill:rgb(80,128,208)' \
d='M7,8h4v1h-4zm0,2h4v1h-4z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  cursor: pointer;
}
button.copy-button:enabled:active span {
  background-size: 90%;
}
.copy-button.disabled {
button.copy-button:disabled span {
  filter: grayscale(1);
  opacity: 0.4;
}
.copy-button-flipped {
/*Note: .16em is suitable for element grouping.*/
  margin-left: .16em;
  margin-right: 0;
}
.nobr {
  white-space: nowrap;
}
.accordion {
  cursor: pointer;
}
.accordion_btn {
1179
1180
1181
1182
1183
1184
1185
1186

1187
1188
1189
1190
1191
1192
1193
1229
1230
1231
1232
1233
1234
1235

1236
1237
1238
1239
1240
1241
1242
1243







-
+







.accordion_closed > .accordion_btn_minus {
  display: none;
}
.accordion_closed > .accordion_btn_plus {
  display: inline-block;
}
.accordion_panel {
  overflow: hidden;
  overflow-y: clip;
  transition: max-height 0.25s ease-out;
}
.error {
  color: darkred;
  background: yellow;
}
.warning {
1754
1755
1756
1757
1758
1759
1760
1761

1762
1763
1764
1765
1766

1767
1768
1769
1770
1771
1772
1773
1804
1805
1806
1807
1808
1809
1810

1811
1812
1813
1814
1815

1816
1817
1818
1819
1820
1821
1822
1823







-
+




-
+







div.pikchr-wrapper.indent:not(.source),
div.pikchr-wrapper.indent.source.source-inline{
  margin-left: 4em;
}
div.pikchr-wrapper.float-left:not(.source),
div.pikchr-wrapper.float-left.source.source-inline {
  float: left;
  padding: 4em;
  padding: 1em 2em;
}
div.pikchr-wrapper.float-right:not(.source),
div.pikchr-wrapper.float-right.source.source-inline{
  float: right;
  padding: 4em;
  padding: 1em 2em;
}

/* For pikchr-wrapper.source mode, toggle pre.pikchr-src and
   svg.pikchr visibility... */
div.pikchr-wrapper.source > div.pikchr-src {
  /* Source code  ^^^^^^^ is visible, else it is hidden */
}
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1866
1867
1868
1869
1870
1871
1872




1873
1874
1875
1876
1877
1878
1879







-
-
-
-







.settings-icon:hover {
  border: 1px outset rgba(127,127,127,1);
}
body.fossil-dark-style .settings-icon {
  filter: invert(100%);
}

input[type="checkbox"].diff-toggle {
  float: right;
}

body.branch .brlist > table > tbody > tr:hover:not(.selected),
body.branch .brlist > table > tbody > tr.selected {
  background-color: #ffc;
}
body.branch .brlist > table > tbody td:first-child > input {
  cursor: pointer;
}
1847
1848
1849
1850
1851
1852
1853

1854

1855
1856
1857
1858


1859
1860
1861
1862
1863
1864
1865
1893
1894
1895
1896
1897
1898
1899
1900

1901




1902
1903
1904
1905
1906
1907
1908
1909
1910







+
-
+
-
-
-
-
+
+







 * to avoid repeating this long list of fonts. */
code, kbd, pre, samp, tt, var,
    div.markdown ol.footnotes > li.fn-joined > sup.fn-joined,
    table.numbered-lines > tbody > tr,
    tr.diffskip > td.chunkctrl,
    #fossil-status-bar,
    .monospace {
  font-family: "MesloLGSDZ Nerd Font Mono",     /* 2025 hotness */
  font-family: "Source Code Pro", "Menlo", "Monaco", "Consolas",
               "Source Code Pro",               /* 2012 hotness */
               "Andale Mono", "Ubuntu Mono", "Deja Vu Sans Mono",
               "Letter Gothic", "Letter Gothic Std", "Prestige Elite Std",
               "Courier", "Courier New",
               monospace;
               "Menlo", "Monaco", "SF Mono",    /* Safari reverted to Courier in 2022 */
               monospace;                       /* let OS/browser default take over */
}

div.markdown > ol.footnotes {
  font-size: 90%;
}
div.markdown > ol.footnotes > li {
  margin-bottom: 0.5em;
1928
1929
1930
1931
1932
1933
1934


















1935
1936
1937
1938
1939
1940
1941
1973
1974
1975
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







div.markdown > ol.footnotes > li > .fn-backrefs > a:target {
  background: gold;
}
div.markdown span.notescope:hover,
div.markdown span.notescope:target {
  border-bottom: 2px solid gold;
}

/* Cause <dd> elements to be aligned complete to the
** right of their <dt> on help pages. */
dl.helpOptions {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: 1rem;
}
dl.helpOptions > dt {
  grid-column: 1;
}
dl.helpOptions > dd {
  grid-column: 2;
  margin: 0;
}
div.helpPage blockquote {
  margin-left: 0.2em;
}

/* Objects in the "desktoponly" class are invisible on mobile */
@media screen and (max-width: 600px) {
  .desktoponly {
    display: none;
  }
}
Changes to src/delta.c.
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
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







-

+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







** buffer is not a multiple of 4 bytes length, compute the sum that would
** have occurred if the buffer was padded with zeros to the next multiple
** of four bytes.
*/
static unsigned int checksum(const char *zIn, size_t N){
  static const int byteOrderTest = 1;
  const unsigned char *z = (const unsigned char *)zIn;
  const unsigned char *zEnd = (const unsigned char*)&zIn[N&~3];
  unsigned sum = 0;
  if( N>0 ){
    const unsigned char *zEnd = (const unsigned char*)&zIn[N&~3];
  assert( (z - (const unsigned char*)0)%4==0 );  /* Four-byte alignment */
  if( 0==*(char*)&byteOrderTest ){
    /* This is a big-endian machine */
    while( z<zEnd ){
      sum += *(unsigned*)z;
      z += 4;
    }
  }else{
    /* A little-endian machine */
#if GCC_VERSION>=4003000
    while( z<zEnd ){
      sum += __builtin_bswap32(*(unsigned*)z);
      z += 4;
    }
#elif defined(_MSC_VER) && _MSC_VER>=1300
    while( z<zEnd ){
      sum += _byteswap_ulong(*(unsigned*)z);
      z += 4;
    }
#else
    unsigned sum0 = 0;
    unsigned sum1 = 0;
    unsigned sum2 = 0;
    while(N >= 16){
      sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]);
      sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]);
      sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]);
      sum  += ((unsigned)z[3] + z[7] + z[11]+ z[15]);
      z += 16;
      N -= 16;
    }
    while(N >= 4){
      sum0 += z[0];
      sum1 += z[1];
      sum2 += z[2];
      sum  += z[3];
      z += 4;
      N -= 4;
    }
    sum += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
#endif
  }
  switch(N&3){
    case 3:   sum += (z[2] << 8);
    case 2:   sum += (z[1] << 16);
    case 1:   sum += (z[0] << 24);
    default:  ;
    assert( (z - (const unsigned char*)0)%4==0 );  /* Four-byte alignment */
    if( 0==*(char*)&byteOrderTest ){
      /* This is a big-endian machine */
      while( z<zEnd ){
        sum += *(unsigned*)z;
        z += 4;
      }
    }else{
      /* A little-endian machine */
  #if GCC_VERSION>=4003000
      while( z<zEnd ){
        sum += __builtin_bswap32(*(unsigned*)z);
        z += 4;
      }
  #elif defined(_MSC_VER) && _MSC_VER>=1300
      while( z<zEnd ){
        sum += _byteswap_ulong(*(unsigned*)z);
        z += 4;
      }
  #else
      unsigned sum0 = 0;
      unsigned sum1 = 0;
      unsigned sum2 = 0;
      while(N >= 16){
        sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]);
        sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]);
        sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]);
        sum  += ((unsigned)z[3] + z[7] + z[11]+ z[15]);
        z += 16;
        N -= 16;
      }
      while(N >= 4){
        sum0 += z[0];
        sum1 += z[1];
        sum2 += z[2];
        sum  += z[3];
        z += 4;
        N -= 4;
      }
      sum += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
  #endif
    }
    switch(N&3){
      case 3:   sum += (z[2] << 8);
      case 2:   sum += (z[1] << 16);
      case 1:   sum += (z[0] << 24);
      default:  ;
    }
  }
  return sum;
}

/*
** Create a new delta.
**
312
313
314
315
316
317
318
319

320
321
322
323
324
325
326
314
315
316
317
318
319
320

321
322
323
324
325
326
327
328







-
+







**
** where NNN is the number of bytes of text (base-64) and TTTTT is the text.
**
** The last term is of the form
**
**     NNN;
**
** In this case, NNN is a 32-bit bigendian checksum of the output file
** In this case, NNN is a 32-bit big endian checksum of the output file
** that can be used to verify that the delta applied correctly.  All
** numbers are in base-64.
**
** Pure text files generate a pure text delta.  Binary files generate a
** delta that may contain some binary data.
**
** Algorithm:
568
569
570
571
572
573
574
575
576


577
578
579
580
581
582
583
570
571
572
573
574
575
576


577
578
579
580
581
582
583
584
585







-
-
+
+







int delta_apply(
  const char *zSrc,      /* The source or pattern file */
  int lenSrc,            /* Length of the source file */
  const char *zDelta,    /* Delta to apply to the pattern */
  int lenDelta,          /* Length of the delta */
  char *zOut             /* Write the output into this preallocated buffer */
){
  unsigned int limit;
  unsigned int total = 0;
  sqlite3_uint64 limit;
  sqlite3_uint64 total = 0;
#ifdef FOSSIL_ENABLE_DELTA_CKSUM_TEST
  char *zOrigOut = zOut;
#endif

  limit = getInt(&zDelta, &lenDelta);
  if( *zDelta!='\n' ){
    /* ERROR: size integer not terminated by "\n" */
598
599
600
601
602
603
604
605

606
607
608
609
610
611
612
600
601
602
603
604
605
606

607
608
609
610
611
612
613
614







-
+







        zDelta++; lenDelta--;
        DEBUG1( printf("COPY %d from %d\n", cnt, ofst); )
        total += cnt;
        if( total>limit ){
          /* ERROR: copy exceeds output file size */
          return -1;
        }
        if( (int)(ofst+cnt) > lenSrc ){
        if( (u64)ofst+(u64)cnt > (u64)lenSrc ){
          /* ERROR: copy extends past end of input */
          return -1;
        }
        memcpy(zOut, &zSrc[ofst], cnt);
        zOut += cnt;
        break;
      }
Changes to src/deltafunc.c.
249
250
251
252
253
254
255

256
257
258
259
260
261
262
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263







+







#define DELTAPARSEVTAB_A2     2
#define DELTAPARSEVTAB_DELTA  3
  if( rc==SQLITE_OK ){
    pNew = sqlite3_malloc64( sizeof(*pNew) );
    *ppVtab = (sqlite3_vtab*)pNew;
    if( pNew==0 ) return SQLITE_NOMEM;
    memset(pNew, 0, sizeof(*pNew));
    sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS);
  }
  return rc;
}

/*
** This method is the destructor for deltaparsevtab_vtab objects.
*/
294
295
296
297
298
299
300





301
302
303
304
305





306
307
308
309
310
311
312
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







+
+
+
+
+





+
+
+
+
+







*/
static int deltaparsevtabNext(sqlite3_vtab_cursor *cur){
  deltaparsevtab_cursor *pCur = (deltaparsevtab_cursor*)cur;
  const char *z;
  int i = 0;

  pCur->iCursor = pCur->iNext;
  if( pCur->iCursor >= pCur->nDelta ){
    pCur->eOp = DELTAPARSE_OP_ERROR;
    pCur->iNext = pCur->nDelta;
    return SQLITE_OK;
  }
  z = pCur->aDelta + pCur->iCursor;
  pCur->a1 = deltaGetInt(&z, &i);
  switch( z[0] ){
    case '@': {
      z++;
      if( pCur->iNext>=pCur->nDelta ){
        pCur->eOp = DELTAPARSE_OP_ERROR;
        pCur->iNext = pCur->nDelta;
        break;
      }
      pCur->a2 = deltaGetInt(&z, &i);
      pCur->eOp = DELTAPARSE_OP_COPY;
      pCur->iNext = (int)(&z[1] - pCur->aDelta);
      break;
    }
    case ':': {
      z++;
352
353
354
355
356
357
358



359
360



361
362
363
364
365
366
367
363
364
365
366
367
368
369
370
371
372


373
374
375
376
377
378
379
380
381
382







+
+
+
-
-
+
+
+







      sqlite3_result_int(ctx, pCur->a1);
      break;
    }
    case DELTAPARSEVTAB_A2: {
      if( pCur->eOp==DELTAPARSE_OP_COPY ){
        sqlite3_result_int(ctx, pCur->a2);
      }else if( pCur->eOp==DELTAPARSE_OP_INSERT ){
        if( pCur->a2 + pCur->a1 > pCur->nDelta ){
          sqlite3_result_zeroblob(ctx, pCur->a1);
        }else{
        sqlite3_result_blob(ctx, pCur->aDelta+pCur->a2, pCur->a1,
                            SQLITE_TRANSIENT);
          sqlite3_result_blob(ctx, pCur->aDelta+pCur->a2, pCur->a1,
                              SQLITE_TRANSIENT);
        }
      }
      break;
    }
    case DELTAPARSEVTAB_DELTA: {
      sqlite3_result_blob(ctx, pCur->aDelta, pCur->nDelta, SQLITE_TRANSIENT);
      break;
    }
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







-
+








/*
** Return TRUE if the cursor has been moved off of the last
** row of output.
*/
static int deltaparsevtabEof(sqlite3_vtab_cursor *cur){
  deltaparsevtab_cursor *pCur = (deltaparsevtab_cursor*)cur;
  return pCur->eOp==DELTAPARSE_OP_EOF;
  return pCur->eOp==DELTAPARSE_OP_EOF || pCur->iCursor>=pCur->nDelta;
}

/*
** This method is called to "rewind" the deltaparsevtab_cursor object back
** to the first row of output.  This method is always called at least
** once prior to any call to deltaparsevtabColumn() or deltaparsevtabRowid() or
** deltaparsevtabEof().
Changes to src/descendants.c.
154
155
156
157
158
159
160

















161
162
163
164
165
166
167
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







      "    WHERE tagxref.rid=leaves.rid "
      "      AND tagxref.tagid=%d"
      "      AND tagxref.tagtype>0)",
      TAG_CLOSED
    );
  }
}

/*
** If RID refers to a check-in, return the mtime of that check-in - the
** Julian day number of when the check-in occurred.
*/
double mtime_of_rid(int rid, double mtime){
  static Stmt q;
  db_static_prepare(&q,"SELECT mtime FROM event WHERE objid=:rid");
  db_bind_int(&q, ":rid", rid);
  if( db_step(&q)==SQLITE_ROW ){
    mtime = db_column_double(&q,0);
  }
  db_reset(&q);
  return mtime;
}



/*
** Load the record ID rid and up to |N|-1 closest ancestors into
** the "ok" table.  If N is zero, no limit.  If ridBackTo is not zero
** then stop the search upon reaching the ancestor with rid==ridBackTo.
*/
void compute_ancestors(int rid, int N, int directOnly, int ridBackTo){
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
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







-
+
-
-


-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-







    **    (1)  Primary parents
    **    (2)  Merge parents
    **    (3)  Cherrypick merge parents.
    **    (4)  All ancestores of 1 and 2 but not of 3.
    */
    double rLimitMtime = 0.0;
    if( ridBackTo ){
      rLimitMtime = db_double(0.0,
      rLimitMtime = mtime_of_rid(ridBackTo, 0.0);
         "SELECT mtime FROM event WHERE objid=%d",
         ridBackTo);
    }
    db_multi_exec(
      "WITH RECURSIVE "
      "  parent(pid,cid,isCP) AS ("
      "    SELECT plink.pid, plink.cid, 0 AS xisCP FROM plink"
      "    UNION ALL"
      "    SELECT parentid, childid, 1 FROM cherrypick WHERE NOT isExclude"
      "  ),"
      "  ancestor(rid, mtime, isCP) AS ("
      "    SELECT %d, mtime, 0 FROM event WHERE objid=%d "
      "    UNION "
      "    SELECT parent.pid, event.mtime, parent.isCP"
      "      FROM ancestor, parent, event"
      "     WHERE parent.cid=ancestor.rid"
      "       AND event.objid=parent.pid"
      "       AND NOT ancestor.isCP"
      "       AND (event.mtime>=%.17g OR parent.pid=%d)"
      "     ORDER BY mtime DESC LIMIT %d"
      "  )"
      "INSERT OR IGNORE INTO ok"
      "WITH RECURSIVE\n"
      "  parent(pid,cid,isCP) AS (\n"
      "    SELECT plink.pid, plink.cid, 0 AS xisCP FROM plink\n"
      "    UNION ALL\n"
      "    SELECT parentid, childid, 1 FROM cherrypick WHERE NOT isExclude\n"
      "  ),\n"
      "  ancestor(rid, mtime, isCP) AS (\n"
      "    SELECT %d, mtime, 0 FROM event WHERE objid=%d\n"
      "    UNION\n"
      "    SELECT parent.pid, event.mtime, parent.isCP\n"
      "      FROM ancestor, parent, event\n"
      "     WHERE parent.cid=ancestor.rid\n"
      "       AND event.objid=parent.pid\n"
      "       AND NOT ancestor.isCP\n"
      "       AND (event.mtime>=%.17g OR parent.pid=%d)\n"
      "     ORDER BY mtime DESC LIMIT %d\n"
      "  )\n"
      "INSERT OR IGNORE INTO ok SELECT rid FROM ancestor;",
      "  SELECT rid FROM ancestor;",
      rid, rid, rLimitMtime, ridBackTo, N
    );
    if( ridBackTo && db_changes()>1 ){
      db_multi_exec("INSERT OR IGNORE INTO ok VALUES(%d)", ridBackTo);
    }
  }
}
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334








335
336
337
338
339
340
341
334
335
336
337
338
339
340








341
342
343
344
345
346
347
348
349
350
351
352
353
354
355







-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+







void compute_descendants(int rid, int N){
  if( !N ){
     N = -1;
  }else if( N<0 ){
     N = -N;
  }
  db_multi_exec(
    "WITH RECURSIVE"
    "  dx(rid,mtime) AS ("
    "     SELECT %d, 0"
    "     UNION"
    "     SELECT plink.cid, plink.mtime FROM dx, plink"
    "      WHERE plink.pid=dx.rid"
    "      ORDER BY 2"
    "  )"
    "WITH RECURSIVE\n"
    "  dx(rid,mtime) AS (\n"
    "     SELECT %d, 0\n"
    "     UNION\n"
    "     SELECT plink.cid, plink.mtime FROM dx, plink\n"
    "      WHERE plink.pid=dx.rid\n"
    "      ORDER BY 2\n"
    "  )\n"
    "INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d",
    rid, N
  );
}

/*
** COMMAND: descendants*
421
422
423
424
425
426
427
428

429
430
431
432
433
434
435
435
436
437
438
439
440
441

442
443
444
445
446
447
448
449







-
+







  int recomputeFlag = find_option("recompute",0,0)!=0;
  int byBranch = find_option("bybranch",0,0)!=0;
  int multipleFlag = find_option("multiple","m",0)!=0;
  const char *zWidth = find_option("width","W",1);
  char *zLastBr = 0;
  int n, width;
  char zLineNo[10];
  char * const zMainBranch = db_get("main-branch","trunk");
  const char *zMainBranch = db_main_branch();

  if( multipleFlag ) byBranch = 1;
  if( zWidth ){
    width = atoi(zWidth);
    if( (width!=0) && (width<=39) ){
      fossil_fatal("-W|--width value must be >39 or 0");
    }
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
533
534
535
536
537
538
539

540
541
542
543
544
545
546







-







    }
    z = mprintf("%s [%S] %s%s", zDate, zId, zCom,
                zBranchPoint ? zBranchPoint : "");
    comment_print(z, zCom, 7, width, get_comment_format());
    fossil_free(z);
    fossil_free(zBranchPoint);
  }
  fossil_free(zMainBranch);
  fossil_free(zLastBr);
  db_finalize(&q);
}

/*
** WEBPAGE: leaves
**
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








-
+
+





-
+





-
+
-
-








-
+
-
-













-
+
-
-




+
-
+




#if INTERFACE
/* Flag parameters to compute_uses_file() */
#define USESFILE_DELETE   0x01  /* Include the check-ins where file deleted */

#endif

/*
** Append a new VALUES term.
*/
static void uses_file_append_term(Blob *pSql, int *pnCnt, int rid){
  if( *pnCnt==0 ){
    blob_append_sql(pSql, "(%d)", rid);
    *pnCnt = 4;
  }else if( (*pnCnt)%10==9 ){
    blob_append_sql(pSql, ",\n  (%d)", rid);
  }else{
    blob_append_sql(pSql, ",(%d)", rid);
  }
  ++*pnCnt;
}


/*
** Add to table zTab the record ID (rid) of every check-in that contains
** the file fid.
*/
void compute_uses_file(const char *zTab, int fid, int usesFlags){
  Bag seen;
  Bag pending;
  Stmt ins;
  Blob ins = BLOB_INITIALIZER;
  int nIns = 0;
  Stmt q;
  int rid;

  bag_init(&seen);
  bag_init(&pending);
  db_prepare(&ins, "INSERT OR IGNORE INTO \"%w\" VALUES(:rid)", zTab);
  blob_append_sql(&ins, "INSERT OR IGNORE INTO \"%w\" VALUES", zTab);
  db_prepare(&q, "SELECT mid FROM mlink WHERE fid=%d", fid);
  while( db_step(&q)==SQLITE_ROW ){
    int mid = db_column_int(&q, 0);
    bag_insert(&pending, mid);
    bag_insert(&seen, mid);
    db_bind_int(&ins, ":rid", mid);
    uses_file_append_term(&ins, &nIns, mid);
    db_step(&ins);
    db_reset(&ins);
  }
  db_finalize(&q);

  db_prepare(&q, "SELECT mid FROM mlink WHERE pid=%d", fid);
  while( db_step(&q)==SQLITE_ROW ){
    int mid = db_column_int(&q, 0);
    bag_insert(&seen, mid);
    if( usesFlags & USESFILE_DELETE ){
      db_bind_int(&ins, ":rid", mid);
      uses_file_append_term(&ins, &nIns, mid);
      db_step(&ins);
      db_reset(&ins);
    }
  }
  db_finalize(&q);
  db_prepare(&q, "SELECT cid FROM plink WHERE pid=:rid AND isprim");

  while( (rid = bag_first(&pending))!=0 ){
    bag_remove(&pending, rid);
    db_bind_int(&q, ":rid", rid);
    while( db_step(&q)==SQLITE_ROW ){
      int mid = db_column_int(&q, 0);
      if( bag_find(&seen, mid) ) continue;
      bag_insert(&seen, mid);
      bag_insert(&pending, mid);
      db_bind_int(&ins, ":rid", mid);
      uses_file_append_term(&ins, &nIns, mid);
      db_step(&ins);
      db_reset(&ins);
    }
    db_reset(&q);
  }
  db_finalize(&q);
  db_exec_sql(blob_str(&ins));
  db_finalize(&ins);
  blob_reset(&ins);
  bag_clear(&seen);
  bag_clear(&pending);
}
Changes to src/diff.c.
48
49
50
51
52
53
54

55
56
57
58
59
60
61
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62







+







#define DIFF_JSON              0x00010000 /* JSON output */
#define DIFF_DEBUG             0x00020000 /* Debugging diff output */
#define DIFF_RAW               0x00040000 /* Raw triples - for debugging */
#define DIFF_TCL               0x00080000 /* For the --tk option */
#define DIFF_INCBINARY         0x00100000 /* The --diff-binary option */
#define DIFF_SHOW_VERS         0x00200000 /* Show compared versions */
#define DIFF_DARKMODE          0x00400000 /* Use dark mode for HTML */
#define DIFF_BY_TOKEN          0x01000000 /* Split on tokens, not lines */

/*
** Per file information that may influence output.
*/
#define DIFF_FILE_ADDED        0x40000000 /* Added or rename destination */
#define DIFF_FILE_DELETED      0x80000000 /* Deleted or rename source */
#define DIFF_FILE_MASK         0xc0000000 /* Used for clearing file flags */
105
106
107
108
109
110
111

112
113
114
115
116
117
118
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120







+







  int nContext;            /* Number of lines of context */
  int wColumn;             /* Column width in -y mode */
  u32 nFile;               /* Number of files diffed so far */
  const char *zDiffCmd;    /* External diff command to use instead of builtin */
  const char *zBinGlob;    /* GLOB pattern for binary files */
  ReCompiled *pRe;         /* Show only changes matching this pattern */
  const char *zLeftHash;   /* HASH-id of the left file */
  const char *azLabel[2];  /* Optional labels for left and right files */
};

#endif /* INTERFACE */

/*
** Initialize memory for a DiffConfig based on just a diffFlags integer.
*/
317
318
319
320
321
322
323







































































































324
325
326
327
328
329
330
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  }while( zNL[0]!='\0' && zNL[1]!='\0' );
  assert( i==nLine );

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

/*
** Character classes for the purpose of tokenization.
**
**    1 - alphanumeric
**    2 - whitespace
**    3 - punctuation
*/
static char aTCharClass[256] = {
  2, 2, 2, 2,  2, 2, 2, 2,   2, 2, 2, 2,  2, 2, 2, 2, 
  2, 2, 2, 2,  2, 2, 2, 2,   2, 2, 2, 2,  2, 2, 2, 2, 
  2, 3, 3, 3,  3, 3, 3, 3,   3, 3, 3, 3,  3, 3, 3, 3,
  1, 1, 1, 1,  1, 1, 1, 1,   1, 3, 3, 3,  3, 3, 3, 3,

  3, 1, 1, 1,  1, 1, 1, 1,   1, 1, 1, 1,  1, 1, 1, 1,
  1, 1, 1, 1,  1, 1, 1, 1,   1, 1, 1, 3,  3, 3, 3, 3,
  3, 1, 1, 1,  1, 1, 1, 1,   1, 1, 1, 1,  1, 1, 1, 1,
  1, 1, 1, 1,  1, 1, 1, 1,   1, 1, 1, 3,  3, 3, 3, 3,

  1, 1, 1, 1,  1, 1, 1, 1,   1, 1, 1, 1,  1, 1, 1, 1,
  1, 1, 1, 1,  1, 1, 1, 1,   1, 1, 1, 1,  1, 1, 1, 1,
  1, 1, 1, 1,  1, 1, 1, 1,   1, 1, 1, 1,  1, 1, 1, 1,
  1, 1, 1, 1,  1, 1, 1, 1,   1, 1, 1, 1,  1, 1, 1, 1,

  1, 1, 1, 1,  1, 1, 1, 1,   1, 1, 1, 1,  1, 1, 1, 1,
  1, 1, 1, 1,  1, 1, 1, 1,   1, 1, 1, 1,  1, 1, 1, 1,
  1, 1, 1, 1,  1, 1, 1, 1,   1, 1, 1, 1,  1, 1, 1, 1,
  1, 1, 1, 1,  1, 1, 1, 1,   1, 1, 1, 1,  1, 1, 1, 1
};

/*
** Count the number of tokens in the given string.
*/
static int count_tokens(const unsigned char *p, int n){
  int nToken = 0;
  int iPrev = 0;
  int i;
  for(i=0; i<n; i++){
    char x = aTCharClass[p[i]];
    if( x!=iPrev ){
      iPrev = x;
      nToken++;
    }
  }
  return nToken;
}

/*
** Return an array of DLine objects containing a pointer to the
** start of each token and a hash of that token.  The lower
** bits of the hash store the length of each token.
**
** This is like break_into_lines() except that it works with tokens
** instead of lines.  A token is:
**
**     *  A contiguous sequence of alphanumeric characters.
**     *  A contiguous sequence of whitespace
**     *  A contiguous sequence of punctuation characters.
**
** Return 0 if the file is binary or contains a line that is
** too long.
*/
static DLine *break_into_tokens(
  const char *z,
  int n,
  int *pnToken,
  u64 diffFlags
){
  int nToken, i, k;
  u64 h, h2;
  DLine *a;
  unsigned char *p = (unsigned char*)z;

  nToken = count_tokens(p, n);
  a = fossil_malloc( sizeof(a[0])*(nToken+1) );
  memset(a, 0, sizeof(a[0])*(nToken+1));
  if( n==0 ){
    *pnToken = 0;
    return a;
  }
  i = 0;
  while( n>0 ){
    char x = aTCharClass[*p];
    h = 0xcbf29ce484222325LL;
    for(k=1; k<n && aTCharClass[p[k]]==x; k++){
      h ^= p[k];
      h *= 0x100000001b3LL;
    }
    a[i].z = (char*)p;
    a[i].n = k;
    a[i].h = h = ((h%281474976710597LL)<<LENGTH_MASK_SZ) | k;
    h2 = h % nToken;
    a[i].iNext = a[h2].iHash;
    a[h2].iHash = i+1;
    p += k; n -= k;
    i++;
  };
  assert( i==nToken );

  /* Return results */
  *pnToken = nToken;
  return a;
}

/*
** Return zero if two DLine elements are identical.
*/
static int compare_dline(const DLine *pA, const DLine *pB){
  if( pA->h!=pB->h ) return 1;
  return memcmp(pA->z,pB->z, pA->h&LENGTH_MASK);
938
939
940
941
942
943
944
945

946
947
948
949
950
951
952
1043
1044
1045
1046
1047
1048
1049

1050
1051
1052
1053
1054
1055
1056
1057







-
+







  DiffConfig *pCfg;                 /* Configuration information */
};

/************************* DiffBuilderDebug ********************************/
/* This version of DiffBuilder is used for debugging the diff and diff
** diff formatter logic.  It is accessed using the (undocumented) --debug
** option to the diff command.  The output is human-readable text that
** describes the various method calls that are invoked agains the DiffBuilder
** describes the various method calls that are invoked against the DiffBuilder
** object.
*/
static void dfdebugSkip(DiffBuilder *p, unsigned int n, int isFinal){
  blob_appendf(p->pOut, "SKIP %d (%d..%d left and %d..%d right)%s\n",
                n, p->lnLeft+1, p->lnLeft+n, p->lnRight+1, p->lnRight+n,
                isFinal ? " FINAL" : "");
  p->lnLeft += n;
2978
2979
2980
2981
2982
2983
2984

2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001






3002
3003
3004
3005





3006
3007
3008
3009
3010
3011
3012
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113




3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125







+

















+
+
+
+
+
+
-
-
-
-
+
+
+
+
+







  Blob *pA_Blob,   /* FROM file */
  Blob *pB_Blob,   /* TO file */
  Blob *pOut,      /* Write diff here if not NULL */
  DiffConfig *pCfg /* Configuration options */
){
  int ignoreWs; /* Ignore whitespace */
  DContext c;
  int nDel = 0, nIns = 0;

  if( pCfg->diffFlags & DIFF_INVERT ){
    Blob *pTemp = pA_Blob;
    pA_Blob = pB_Blob;
    pB_Blob = pTemp;
  }
  ignoreWs = (pCfg->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( (pCfg->diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
    c.xDiffer = compare_dline_ignore_allws;
  }else{
    c.xDiffer = compare_dline;
  }
  if( pCfg->diffFlags & DIFF_BY_TOKEN ){
    c.aFrom = break_into_tokens(blob_str(pA_Blob), blob_size(pA_Blob),
                               &c.nFrom, pCfg->diffFlags);
    c.aTo = break_into_tokens(blob_str(pB_Blob), blob_size(pB_Blob),
                             &c.nTo, pCfg->diffFlags);
  }else{
  c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
                             &c.nFrom, pCfg->diffFlags);
  c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
                           &c.nTo, pCfg->diffFlags);
    c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
                               &c.nFrom, pCfg->diffFlags);
    c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
                             &c.nTo, pCfg->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, pCfg->diffFlags);
    }
    return 0;
3033
3034
3035
3036
3037
3038
3039














3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
























3055
3056
3057
3058
3059
3060
3061
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166















3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197







+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







      if( pOut ) diff_errmsg(pOut, DIFF_TOO_MANY_CHANGES, pCfg->diffFlags);
      return 0;
    }
  }
  if( (pCfg->diffFlags & DIFF_NOOPT)==0 ){
    diff_optimize(&c);
  }
  if( (pCfg->diffFlags & DIFF_BY_TOKEN)!=0 ){
    /* Convert token counts into byte counts. */
    int i;
    int iA = 0;
    int iB = 0;
    for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){
      int k, sum;
      for(k=0, sum=0; k<c.aEdit[i]; k++) sum += c.aFrom[iA++].n;
      iB += c.aEdit[i];
      c.aEdit[i] = sum;
      for(k=0, sum=0; k<c.aEdit[i+1]; k++) sum += c.aFrom[iA++].n;
      c.aEdit[i+1] = sum;
      for(k=0, sum=0; k<c.aEdit[i+2]; k++) sum += c.aTo[iB++].n;
      c.aEdit[i+2] = sum;

  if( pOut ){
    if( pCfg->diffFlags & DIFF_NUMSTAT ){
      int nDel = 0, nIns = 0, i;
      for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){
        nDel += c.aEdit[i+1];
        nIns += c.aEdit[i+2];
      }
      g.diffCnt[1] += nIns;
      g.diffCnt[2] += nDel;
      if( nIns+nDel ){
        g.diffCnt[0]++;
        blob_appendf(pOut, "%10d %10d", nIns, nDel);
      }
    }else if( pCfg->diffFlags & DIFF_RAW ){
    }
  }

  if( pCfg->diffFlags & DIFF_NUMSTAT ){
    int i;
    for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){
      nDel += c.aEdit[i+1];
      nIns += c.aEdit[i+2];
    }
    g.diffCnt[1] += nIns;
    g.diffCnt[2] += nDel;
    if( nIns+nDel ){
      g.diffCnt[0]++;
    }
  }

  if( pOut ){
    if( pCfg->diffFlags & DIFF_NUMSTAT && !(pCfg->diffFlags & DIFF_HTML)){
      if( nIns+nDel ){
        if( !(pCfg->diffFlags & DIFF_BRIEF) ){
          blob_appendf(pOut, "%10d %10d", nIns, nDel);
        }
      }
    }else if( pCfg->diffFlags & (DIFF_RAW|DIFF_BY_TOKEN) ){
      const int *R = c.aEdit;
      unsigned int r;
      for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
        blob_appendf(pOut, " copy %6d  delete %6d  insert %6d\n",
                     R[r], R[r+1], R[r+2]);
      }
    }else if( pCfg->diffFlags & DIFF_JSON ){
3098
3099
3100
3101
3102
3103
3104

3105

3106

3107

3108


3109

3110
3111

3112

3113

3114

3115
3116
3117
3118
3119
3120
3121
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253

3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267







+

+

+

+

+
+

+

-
+

+

+

+








/*
** Initialize the DiffConfig object using command-line options.
**
** Process diff-related command-line options and return an appropriate
** "diffFlags" integer.
**
**   -b|--browser                 Show the diff output in a web-browser
**   --brief                      Show filenames only        DIFF_BRIEF
**   --by                         Shorthand for "--browser -y"
**   -c|--context N               N lines of context.        nContext
**   --dark                       Use dark mode for Tcl/Tk and HTML output
**   --html                       Format for HTML            DIFF_HTML
**   -i|--internal                Use built-in diff, not an external tool
**   --invert                     Invert the diff            DIFF_INVERT
**   --json                       Output formatted as JSON
**   --label NAME                 Column label.  Can be repeated once.
**   -n|--linenum                 Show line numbers          DIFF_LINENO
**   -N|--new-file                Alias for --verbose
**   --noopt                      Disable optimization       DIFF_NOOPT
**   --numstat                    Show change counts         DIFF_NUMSTAT
**   -s|--numstat                 Show change counts         DIFF_NUMSTAT
**   --strip-trailing-cr          Strip trailing CR          DIFF_STRIP_EOLCR
**   --tcl                        Tcl-formatted output used internally by --tk
**   --unified                    Unified diff.              ~DIFF_SIDEBYSIDE
**   -v|--verbose                 Show complete text of added or deleted files
**   -w|--ignore-all-space        Ignore all whitespaces     DIFF_IGNORE_ALLWS
**   --webpage                    Format output as a stand-alone HTML webpage
**   -W|--width N                 N character lines.         wColumn
**   -y|--side-by-side            Side-by-side diff.         DIFF_SIDEBYSIDE
**   -Z|--ignore-trailing-space   Ignore eol-whitespaces     DIFF_IGNORE_EOLWS
*/
void diff_options(DiffConfig *pCfg, int isGDiff, int bUnifiedTextOnly){
  u64 diffFlags = 0;
  const char *z;
3155
3156
3157
3158
3159
3160
3161



3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173




3174
3175
3176

3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194




3195
3196
3197
3198
3199
3200
3201
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328

3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358







+
+
+












+
+
+
+


-
+


















+
+
+
+







      diffFlags |= DIFF_TCL;
    }

    /* Undocumented and unsupported flags used for development
    ** debugging and analysis: */
    if( find_option("debug",0,0)!=0 ) diffFlags |= DIFF_DEBUG;
    if( find_option("raw",0,0)!=0 )   diffFlags |= DIFF_RAW;
    if( find_option("bytoken",0,0)!=0 ){
      diffFlags = DIFF_RAW|DIFF_BY_TOKEN;
    }
  }
  if( (z = find_option("context","c",1))!=0 ){
    char *zEnd;
    f = (int)strtol(z, &zEnd, 10);
    if( zEnd[0]==0 && errno!=ERANGE ){
      pCfg->nContext = f;
      diffFlags |= DIFF_CONTEXT_EX;
    }
  }
  if( (z = find_option("width","W",1))!=0 && (f = atoi(z))>0 ){
    pCfg->wColumn = f;
  }
  pCfg->azLabel[0] = find_option("label",0,1);
  if( pCfg->azLabel[0] ){
    pCfg->azLabel[1] = find_option("label",0,1);
  }
  if( find_option("linenum","n",0)!=0 ) diffFlags |= DIFF_LINENO;
  if( find_option("noopt",0,0)!=0 ) diffFlags |= DIFF_NOOPT;
  if( find_option("numstat",0,0)!=0 ) diffFlags |= DIFF_NUMSTAT;
  if( find_option("numstat","s",0)!=0 ) diffFlags |= DIFF_NUMSTAT;
  if( find_option("versions","h",0)!=0 ) diffFlags |= DIFF_SHOW_VERS;
  if( find_option("dark",0,0)!=0 ) diffFlags |= DIFF_DARKMODE;
  if( find_option("invert",0,0)!=0 ) diffFlags |= DIFF_INVERT;
  if( find_option("brief",0,0)!=0 ) diffFlags |= DIFF_BRIEF;
  if( find_option("internal","i",0)==0
   && (diffFlags & (DIFF_HTML|DIFF_TCL|DIFF_DEBUG|DIFF_JSON))==0
  ){
    pCfg->zDiffCmd = find_option("command", 0, 1);
    if( pCfg->zDiffCmd==0 ) pCfg->zDiffCmd = diff_command_external(isGDiff);
    if( pCfg->zDiffCmd ){
      const char *zDiffBinary;
      pCfg->zBinGlob = diff_get_binary_glob();
      zDiffBinary = find_option("diff-binary", 0, 1);
      if( zDiffBinary ){
        if( is_truth(zDiffBinary) ) diffFlags |= DIFF_INCBINARY;
      }else if( db_get_boolean("diff-binary", 1) ){
        diffFlags |= DIFF_INCBINARY;
      }
    }else if( isGDiff) {
      /* No external gdiff command found, using --by */
      diffFlags |= DIFF_HTML|DIFF_WEBPAGE|DIFF_LINENO|DIFF_BROWSER
                     |DIFF_SIDEBYSIDE;
    }
  }
  if( find_option("verbose","v",0)!=0 ) diffFlags |= DIFF_VERBOSE;
  /* Deprecated, but retained for script compatibility. */
  else if( find_option("new-file","N",0)!=0 ) diffFlags |= DIFF_VERBOSE;

  pCfg->diffFlags = diffFlags;
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237

3238
3239
3240
3241
3242
3243
3244
3245
3246














































3247
3248
3249
3250
3251
3252
3253
3369
3370
3371
3372
3373
3374
3375



3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390

3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453







-
-
-















-
+









+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







** In other words, this command provides a mechanism to use Fossil's file
** difference engine on arbitrary disk files.  See the "diff" command for
** computing differences between files that are under management.
**
** This command prints the differences between the two files FILE1 and FILE2.
** all of the usual diff formatting options (--tk, --by, -c N, etc.) apply.
** See the "diff" command for a full list of command-line options.
**
** This command used to be called "test-diff".  The older "test-diff" spelling
** still works, for compatibility.
*/
void xdiff_cmd(void){
  Blob a, b, out;
  const char *zRe;           /* Regex filter for diff output */
  DiffConfig DCfg;

  if( find_option("tk",0,0)!=0 ){
    diff_tk("xdiff", 2);
    return;
  }
  find_option("i",0,0);
  find_option("v",0,0);
  diff_options(&DCfg, 0, 0);
  zRe = find_option("regexp","e",1);
  if( zRe ){
    const char *zErr = re_compile(&DCfg.pRe, zRe, 0);
    const char *zErr = fossil_re_compile(&DCfg.pRe, zRe, 0);
    if( zErr ) fossil_fatal("regex error: %s", zErr);
  }
  verify_all_options();
  if( g.argc!=4 ) usage("FILE1 FILE2");
  blob_zero(&out);
  diff_begin(&DCfg);
  diff_print_filenames(g.argv[2], g.argv[3], &DCfg, &out);
  blob_read_from_file(&a, g.argv[2], ExtFILE);
  blob_read_from_file(&b, g.argv[3], ExtFILE);
  text_diff(&a, &b, &out, &DCfg);
  blob_write_to_file(&out, "-");
  diff_end(&DCfg, 0);
  re_free(DCfg.pRe);
}

/*
** COMMAND: fdiff
**
** Usage: %fossil fdiff [options] HASH1 HASH2
**
** Compute a diff between two artifacts in a repository (either a repository
** identified by the "-R FILENAME" option, or the repository that contains
** the working directory).
**
** All of the usual diff formatting options (--tk, --by, -c N, etc.) apply.
** See the "diff" command for a full list of command-line options.
*/
void fdiff_cmd(void){
  Blob a, b, out;
  const char *zRe;           /* Regex filter for diff output */
  int rid;
  DiffConfig DCfg;

  if( find_option("tk",0,0)!=0 ){
    diff_tk("fdiff", 2);
    return;
  }
  find_option("i",0,0);
  find_option("v",0,0);
  diff_options(&DCfg, 0, 0);
  zRe = find_option("regexp","e",1);
  if( zRe ){
    const char *zErr = fossil_re_compile(&DCfg.pRe, zRe, 0);
    if( zErr ) fossil_fatal("regex error: %s", zErr);
  }
  db_find_and_open_repository(0, 0);
  verify_all_options();
  if( g.argc!=4 ) usage("HASH1 HASH2");
  blob_zero(&out);
  diff_begin(&DCfg);
  diff_print_filenames(g.argv[2], g.argv[3], &DCfg, &out);
  rid = name_to_typed_rid(g.argv[2], 0);
  content_get(rid, &a);
  rid = name_to_typed_rid(g.argv[3], 0);
  content_get(rid, &b);
  text_diff(&a, &b, &out, &DCfg);
  blob_write_to_file(&out, "-");
  diff_end(&DCfg, 0);
  re_free(DCfg.pRe);
}

/**************************************************************************
3420
3421
3422
3423
3424
3425
3426
3427

3428
3429
3430
3431
3432
3433
3434
3620
3621
3622
3623
3624
3625
3626

3627
3628
3629
3630
3631
3632
3633
3634







-
+







  }else{
    /* Default limit is as much as we can do in 1.000 seconds */
    iLimit = 0;
    mxTime = current_time_in_milliseconds()+1000;
  }
  db_begin_transaction();

  /* Get the artifact ID for the check-in begin analyzed */
  /* Get the artifact ID for the check-in being analyzed */
  if( zRevision ){
    cid = name_to_typed_rid(zRevision, "ci");
  }else{
    db_must_be_within_tree();
    cid = db_lget_int("checkout", 0);
  }
  origid = zOrigin ? name_to_typed_rid(zOrigin, "ci") : 0;
3586
3587
3588
3589
3590
3591
3592
3593

3594
3595
3596
3597
3598
3599
3600
3786
3787
3788
3789
3790
3791
3792

3793
3794
3795
3796
3797
3798
3799
3800







-
+







  struct AnnVers *p;
  unsigned clr1, clr2, clr;
  int bBlame = g.zPath[0]!='a';/* True for BLAME output.  False for ANNOTATE. */

  /* Gather query parameters */
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( exclude_spiders(0) ) return;
  if( robot_restrict("annotate") ) return;
  fossil_nice_default();
  zFilename = P("filename");
  zRevision = PD("checkin",0);
  zOrigin = P("origin");
  zLimit = P("limit");
  showLog = PB("log");
  fileVers = PB("filevers");
3737
3738
3739
3740
3741
3742
3743





3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754


3755
3756
3757



3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770

3771
3772
3773
3774
3775


3776









3777
3778
3779
3780
3781
3782
3783
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958

3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004







+
+
+
+
+










-
+
+



+
+
+













+





+
+

+
+
+
+
+
+
+
+
+







** thus the output shows the most recent change to each line.  However,
** if the -o|--origin option is used to specify some future check-in
** (example: "-o trunk") then these commands show changes moving towards
** that alternative origin.  Thus using "-o trunk" on an historical version
** of the file shows the first time each line in the file was changed or
** removed by any subsequent check-in.
**
** With -t or -T, the "blame" and "praise" commands show for each file the
** latest (relative to the revision given by -r) check-in that modified it and
** the check-in's author. If not given, the revision defaults to "current" for
** a check-out. Option -T additionally shows a comment snippet for the check-in.
**
** Options:
**   --filevers                  Show file version numbers rather than
**                               check-in versions
**   -r|--revision VERSION       The specific check-in containing the file
**   -l|--log                    List all versions analyzed
**   -n|--limit LIMIT            LIMIT can be one of:
**                                 N      Up to N versions
**                                 Xs     As much as possible in X seconds
**                                 none   No limit
**   -o|--origin VERSION         The origin check-in. By default this is the
**                               root of the repository. Set to "trunk" or
**                               root of the repository. Set to the name of
**                               the main branch (usually "trunk") or
**                               similar for a reverse annotation.
**   -w|--ignore-all-space       Ignore white space when comparing lines
**   -Z|--ignore-trailing-space  Ignore whitespace at line end
**   -t                          Show latest check-in and its author for each
**                               tracked file in the tree as of VERSION
**   -T                          Like -t, plus comment snippet
**
** See also: [[info]], [[finfo]], [[timeline]]
*/
void annotate_cmd(void){
  const char *zRevision; /* Revision name, or NULL for current check-in */
  Annotator ann;         /* The annotation of the file */
  int i;                 /* Loop counter */
  const char *zLimit;    /* The value to the -n|--limit option */
  const char *zOrig;     /* The value for -o|--origin */
  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. */
  int bTreeInfo = 0;     /* Show for the entire tree: 1=checkin, 2=with comment */
  int szHash;            /* Display size of a version hash */
  Blob treename;         /* Name of file to be annotated */
  char *zFilename;       /* Name of file to be annotated */

  bBlame = g.argv[1][0]!='a';
  if( find_option("t","t",0)!=0 ) bTreeInfo = 1;
  if( find_option("T","T",0)!=0 ) bTreeInfo = 2;
  zRevision = find_option("revision","r",1);
  if( bBlame && bTreeInfo ){
    if( find_repository_option()!=0 && zRevision==0 ){
       fossil_fatal("the -r is required in addition to -R");
    }
    db_find_and_open_repository(0, 0);
    if( zRevision==0 ) zRevision = "current";
    ls_cmd_rev(zRevision,1,1,0,1,bTreeInfo,0,0);
    return;
  }
  zLimit = find_option("limit","n",1);
  zOrig = find_option("origin","o",1);
  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 ){
Changes to src/diff.tcl.
1
2
3
4
5
6

7
8
9
10
11
12
13
1
2
3
4
5

6
7
8
9
10
11
12
13





-
+







# The "diff --tk" command outputs prepends a "set fossilcmd {...}" line
# to this file, then runs this file using "tclsh" in order to display the
# graphical diff in a separate window.  A typical "set fossilcmd" line
# looks like this:
#
#     set fossilcmd {| "./fossil" diff --html -y -i -v}
#     set fossilcmd {| "./fossil" diff --tcl -i -v}
#
# This header comment is stripped off by the "mkbuiltin.c" program.
#
set prog {
package require Tk

array set CFG_light {
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
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
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







+
+
+
+
+
+
+
+
+
-
-
+
+

+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+







+
+
+
+
+
+
+
+
+






+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+









+
+
+
-
+
+


+
+
+
-
+
+







  upvar $iivar ii
  if {$ii>=$N} {return -1}
  set x [lindex $difftxt $ii]
  incr ii
  return $x
}

proc reloadDiff {} {
  global fossilcmd difftxt
  unset -nocomplain difftxt
  set idx [.txtA index @0,0]
  readDiffs $fossilcmd 1
  update
  viewDiff $idx
}

proc readDiffs {fossilcmd} {
  global difftxt
proc readDiffs {fossilcmd redo} {
  global difftxt debug
  if {![info exists difftxt]} {
    if {$debug} {
      puts "# [list open $fossilcmd r]"
      flush stdout
    }
    if {[catch {
    set in [open $fossilcmd r]
    fconfigure $in -encoding utf-8
    set difftxt [split [read $in] \n]
    close $in
      set in [open $fossilcmd r]
      fconfigure $in -encoding utf-8
      set difftxt [split [read $in] \n]
      close $in
    } msg]} {
      if {$redo} {
        tk_messageBox -type ok -title Error -message "Unable to refresh:\n$msg"
        return 0
      } else {
        puts $msg
        exit 1
      }
    }
  }
  set N [llength $difftxt]
  set ii 0
  set nDiffs 0
  set n1 0
  set n2 0  
  array set widths {txt 3 ln 3 mkr 1}
  if {$redo} {
    foreach c [cols] {$c config -state normal}
    .lnA delete 1.0 end
    .txtA delete 1.0 end
    .lnB delete 1.0 end
    .txtB delete 1.0 end
    .mkr delete 1.0 end
    .wfiles.lb delete 0 end
  }
  
  
  set fromIndex [lsearch -glob $fossilcmd *-from]
  set toIndex [lsearch -glob $fossilcmd *-to]
  set branchIndex [lsearch -glob $fossilcmd *-branch]
  set checkinIndex [lsearch -glob $fossilcmd *-checkin]
  if {[lsearch -glob $fossilcmd *-label]>=0
    || [lsearch {xdiff fdiff merge-info} [lindex $fossilcmd 2]]>=0
  } {
    set fA {}
    set fB {}
  } else {
    if {[string match *?--external-baseline* $fossilcmd]} {
      set fA {external baseline}
    } else {
  set fA {base check-in}
  set fB {current check-out}
  if {$fromIndex > -1} {set fA [lindex $fossilcmd $fromIndex+1]}
  if {$toIndex > -1} {set fB [lindex $fossilcmd $toIndex+1]}
  if {$branchIndex > -1} {set fA "branch point"; set fB "leaf of branch '[lindex $fossilcmd $branchIndex+1]'"}
  if {$checkinIndex > -1} {set fA "primary parent"; set fB [lindex $fossilcmd $checkinIndex+1]}
  
      set fA {base check-in}
    }
    set fB {current check-out}
    if {$fromIndex > -1} {
      set fA [lindex $fossilcmd $fromIndex+1]
    }
    if {$toIndex > -1} {
      set fB [lindex $fossilcmd $toIndex+1]
    }
    if {$branchIndex > -1} {
      set fA "branch point"; set fB "leaf of branch '[lindex $fossilcmd $branchIndex+1]'"
    }
    if {$checkinIndex > -1} {
      set fA "primary parent"; set fB [lindex $fossilcmd $checkinIndex+1]
    }
  }
  
  while {[set line [getLine $difftxt $N ii]] != -1} {
    switch -- [lindex $line 0] {
      FILE {
        incr nDiffs
        foreach wx [list [string length $n1] [string length $n2]] {
          if {$wx>$widths(ln)} {set widths(ln) $wx}
        }
        .lnA insert end \n fn \n -
        if {$fA==""} {
          .txtA insert end "[lindex $line 1]\n" fn \n -
        } else {
        .txtA insert end "[lindex $line 1] ($fA)\n" fn \n -
          .txtA insert end "[lindex $line 1] ($fA)\n" fn \n -
        }
        .mkr insert end \n fn \n -
        .lnB insert end \n fn \n -
        if {$fB==""} {
          .txtB insert end "[lindex $line 2]\n" fn \n -
        } else {
        .txtB insert end "[lindex $line 2] ($fB)\n" fn \n -
          .txtB insert end "[lindex $line 2] ($fB)\n" fn \n -
        }
        .wfiles.lb insert end [lindex $line 2]
        set n1 0
        set n2 0
      }
      SKIP {
        set n [lindex $line 1]
        incr n1 $n
221
222
223
224
225
226
227
228
229


230
231
232
233
234
235
236
279
280
281
282
283
284
285


286
287
288
289
290
291
292
293
294







-
-
+
+







  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
  .wfiles.lb config -height $nDiffs
  if {$nDiffs <= [.wfiles.lb cget -height]} {
    grid remove .wfiles.sb
  }

  return $nDiffs
}

proc viewDiff {idx} {
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
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







-
+












+
+







}

::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} {
if {[readDiffs $fossilcmd 0] == 0} {
  tk_messageBox -type ok -title $CFG(TITLE) -message "No changes"
  exit
}
update idletasks

proc saveDiff {} {
  set fn [tk_getSaveFile]
  if {$fn==""} return
  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 darkmode $::darkmode"
  puts $out "set debug $::debug"
  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
}
542
543
544
545
546
547
548

549
550
551




552

553
554
555
556
557
558
559
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616

617
618
619
620
621
622
623
624







+



+
+
+
+
-
+







    $w yview -pickplace $idx
    $w tag add search search "$idx +$count chars"
    $w tag config search -background {#fcc000}
  }
  set ::search $w
}
::ttk::button .bb.quit -text {Quit} -command exit
::ttk::button .bb.reload -text {Reload} -command reloadDiff
::ttk::button .bb.invert -text {Invert} -command invertDiff
::ttk::button .bb.save -text {Save As...} -command saveDiff
::ttk::button .bb.search -text {Search} -command searchOnOff
pack .bb.quit -side left
if {$fossilcmd ne ""} {
  pack .bb.reload -side left
}
pack .bb.quit .bb.invert -side left
pack .bb.invert -side left
if {$fossilcmd!=""} {pack .bb.save -side left}
pack .bb.files .bb.search -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
Changes to src/diffcmd.c.
125
126
127
128
129
130
131
132

133
134
135
136
137

138
139
140
141
142
143
144
125
126
127
128
129
130
131

132
133
134
135
136

137
138
139
140
141
142
143
144







-
+




-
+







void diff_print_versions(const char *zFrom, const char *zTo, DiffConfig *pCfg){
  if( (pCfg->diffFlags & (DIFF_SIDEBYSIDE|DIFF_BRIEF|DIFF_NUMSTAT|
        DIFF_HTML|DIFF_WEBPAGE|DIFF_BROWSER|DIFF_JSON|DIFF_TCL))==0 ){
    fossil_print("Fossil-Diff-From:  %s\n",
      zFrom[0]=='(' ? zFrom : mprintf("%S %s",
        rid_to_uuid(symbolic_name_to_rid(zFrom, "ci")),
        db_text("","SELECT datetime(%f)||' UTC'",
          symbolic_name_to_mtime(zFrom, 0))));
          symbolic_name_to_mtime(zFrom, 0, 0))));
    fossil_print("Fossil-Diff-To:    %s\n",
      zTo[0]=='(' ? zTo : mprintf("%S %s",
        rid_to_uuid(symbolic_name_to_rid(zTo, "ci")),
        db_text("","SELECT datetime(%f)||' UTC'",
          symbolic_name_to_mtime(zTo, 0))));
          symbolic_name_to_mtime(zTo, 0, 1))));
    fossil_print("%.66c\n", '-');
  }
}

/*
** Print the "Index:" message that patches wants to see at the top of a diff.
*/
162
163
164
165
166
167
168


169
170
171
172
173
174
175
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177







+
+







  DiffConfig *pCfg,       /* Diff configuration */
  Blob *pOut              /* Write to this blob, or stdout of this is NULL */
){
  u64 diffFlags = pCfg->diffFlags;
  /* Standardize on /dev/null, regardless of platform. */
  if( pCfg->diffFlags & DIFF_FILE_ADDED ) zLeft = "/dev/null";
  if( pCfg->diffFlags & DIFF_FILE_DELETED ) zRight = "/dev/null";
  if( pCfg->azLabel[0] ) zLeft = pCfg->azLabel[0];
  if( pCfg->azLabel[1] ) zRight = pCfg->azLabel[1];
  if( diffFlags & (DIFF_BRIEF|DIFF_RAW) ){
    /* no-op */
  }else if( diffFlags & DIFF_DEBUG ){
    blob_appendf(pOut, "FILE-LEFT   %s\nFILE-RIGHT  %s\n", zLeft, zRight);
  }else if( diffFlags & DIFF_WEBPAGE ){
    if( fossil_strcmp(zLeft,zRight)==0 ){
      blob_appendf(pOut,"<h1>%h</h1>\n", zLeft);
208
209
210
211
212
213
214
215

216
217
218
219
220
221
222
210
211
212
213
214
215
216

217
218
219
220
221
222
223
224







-
+







      if( n1>w-10 ) n1 = w - 10;
      if( n2>w-10 ) n2 = w - 10;
      blob_appendf(pOut, "%.*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{
    blob_appendf(pOut, "--- %s\n+++ %s\n", zLeft, zRight);
    blob_appendf(pOut, "--- %s\t\n+++ %s\t\n", zLeft, zRight);
  }
}


/*
** Default header texts for diff with --webpage
*/
247
248
249
250
251
252
253
254

255
256
257
258
259
260
261
262

263
264
265
266
267
268
269
249
250
251
252
253
254
255

256
257
258
259
260
261
262
263

264
265
266
267
268
269
270
271







-
+







-
+







@ }
@ table.diff pre {
@   margin: 0 0 0 0;
@   line-height: inherit;
@   font-size: inherit;
@ }
@ td.diffln {
@   width: 1px;
@   width: fit-content;
@   text-align: right;
@   padding: 0 1em 0 0;
@ }
@ td.difflne {
@   padding-bottom: 0.4em;
@ }
@ td.diffsep {
@   width: 1px;
@   width: fit-content;
@   padding: 0 0.3em 0 1em;
@   line-height: inherit;
@   font-size: inherit;
@ }
@ td.diffsep pre {
@   line-height: inherit;
@   font-size: inherit;
377
378
379
380
381
382
383
384

385
386
387
388
389
390
391
392

393
394
395
396
397
398
399
379
380
381
382
383
384
385

386
387
388
389
390
391
392
393

394
395
396
397
398
399
400
401







-
+







-
+







@ }
@ table.diff pre {
@   margin: 0 0 0 0;
@   line-height: inherit;
@   font-size: inherit;
@ }
@ td.diffln {
@   width: 1px;
@   width: fit-content;
@   text-align: right;
@   padding: 0 1em 0 0;
@ }
@ td.difflne {
@   padding-bottom: 0.4em;
@ }
@ td.diffsep {
@   width: 1px;
@   width: fit-content;
@   padding: 0 0.3em 0 1em;
@   line-height: inherit;
@   font-size: inherit;
@ }
@ td.diffsep pre {
@   line-height: inherit;
@   font-size: inherit;
583
584
585
586
587
588
589
590

591
592
593
594
595
596
597
598

599


600
601
602
603
604
605
606
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







-
+








+
-
+
+







      zName2 = NULL_DEVICE;
    }else{
      blob_read_from_file(&file2, zFile2, ExtFILE);
      zName2 = zName;
    }

    /* Compute and output the differences */
    if( pCfg->diffFlags & DIFF_BRIEF ){
    if( (pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){
      if( blob_compare(pFile1, &file2) ){
        fossil_print("CHANGED  %s\n", zName);
      }
    }else{
      blob_zero(&out);
      text_diff(pFile1, &file2, &out, pCfg);
      if( blob_size(&out) ){
        if( pCfg->diffFlags & DIFF_NUMSTAT ){
          if( !(pCfg->diffFlags & DIFF_BRIEF) ){
          blob_appendf(pOut, "%s %s\n", blob_str(&out), zName);
            blob_appendf(pOut, "%s %s\n", blob_str(&out), zName);
          }
        }else{
          diff_print_filenames(zName, zName2, pCfg, pOut);
          blob_appendf(pOut, "%s\n", blob_str(&out));
        }
      }
      blob_reset(&out);
    }
689
690
691
692
693
694
695
696



697
698
699
700
701
702

703


704
705
706
707
708
709
710
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







-
+
+
+






+
-
+
+







*/
void diff_file_mem(
  Blob *pFile1,             /* In memory content to compare from */
  Blob *pFile2,             /* In memory content to compare to */
  const char *zName,        /* Display name of the file */
  DiffConfig *pCfg          /* Diff flags */
){
  if( pCfg->diffFlags & DIFF_BRIEF ) return;
  if( (pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){
    return;
  }
  if( pCfg->zDiffCmd==0 ){
    Blob out;      /* Diff output text */

    blob_zero(&out);
    text_diff(pFile1, pFile2, &out, pCfg);
    if( pCfg->diffFlags & DIFF_NUMSTAT ){
      if( !(pCfg->diffFlags & DIFF_BRIEF) ){
      fossil_print("%s %s\n", blob_str(&out), zName);
        fossil_print("%s %s\n", blob_str(&out), zName);
      }
    }else{
      diff_print_filenames(zName, zName, pCfg, 0);
      fossil_print("%s\n", blob_str(&out));
    }

    /* Release memory resources */
    blob_reset(&out);
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
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







-
-
-
+
+
+
+








-
+



-
+




-
+







    rc = memcmp(blob_buffer(&file), blob_buffer(blob), blob_size(&file))==0;
  }
  blob_reset(&file);
  return rc;
}

/*
** Run a diff between the version zFrom and files on disk.  zFrom might
** be NULL which means to simply show the difference between the edited
** files on disk and the check-out on which they are based.
** Run a diff between the version zFrom and files on disk in the current
** working checkout.  zFrom might be NULL which means to simply show the
** difference between the edited files on disk and the check-out on which
** they are based.
**
** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
** command zDiffCmd to do the diffing.
**
** When using an external diff program, zBinGlob contains the GLOB patterns
** for file names to treat as binary.  If fIncludeBinary is zero, these files
** will be skipped in addition to files that may contain binary content.
*/
void diff_against_disk(
void diff_version_to_checkout(
  const char *zFrom,        /* Version to difference from */
  DiffConfig *pCfg,         /* Flags controlling diff output */
  FileDirList *pFileDir,    /* Which files to diff */
  Blob *pOut            /* Blob to output diff instead of stdout */
  Blob *pOut                /* Blob to output diff instead of stdout */
){
  int vid;
  Blob sql;
  Stmt q;
  int asNewFile;            /* Treat non-existant files as empty files */
  int asNewFile;            /* Treat non-existent files as empty files */
  int isNumStat;            /* True for --numstat */

  asNewFile = (pCfg->diffFlags & (DIFF_VERBOSE|DIFF_NUMSTAT|DIFF_HTML))!=0;
  isNumStat = (pCfg->diffFlags & (DIFF_NUMSTAT|DIFF_TCL|DIFF_HTML))!=0;
  vid = db_lget_int("checkout", 0);
  vfile_check_signature(vid, CKSIG_ENOTFILE);
  blob_zero(&sql);
926
927
928
929
930
931
932
933

934
935
936
937
938
939
940
941
942

943
944
945
946
947
948
949
935
936
937
938
939
940
941

942
943
944
945
946
947
948
949
950

951
952
953
954
955
956
957
958







-
+








-
+







    blob_reset(&fname);
  }
  db_finalize(&q);
  db_end_transaction(1);  /* ROLLBACK */
}

/*
** Run a diff between the undo buffer and files on disk.
** Run a diff from the undo buffer to files on disk.
**
** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
** command zDiffCmd to do the diffing.
**
** When using an external diff program, zBinGlob contains the GLOB patterns
** for file names to treat as binary.  If fIncludeBinary is zero, these files
** will be skipped in addition to files that may contain binary content.
*/
static void diff_against_undo(
static void diff_undo_to_checkout(
  DiffConfig *pCfg,         /* Flags controlling diff output */
  FileDirList *pFileDir     /* List of files and directories to diff */
){
  Stmt q;
  Blob content;
  db_prepare(&q, "SELECT pathname, content FROM undo");
  blob_init(&content, 0, 0);
985
986
987
988
989
990
991
992



993
994
995
996
997
998
999
994
995
996
997
998
999
1000

1001
1002
1003
1004
1005
1006
1007
1008
1009
1010







-
+
+
+







  if( pFrom ){
    zName = pFrom->zName;
  }else if( pTo ){
    zName = pTo->zName;
  }else{
    zName = DIFF_NO_NAME;
  }
  if( pCfg->diffFlags & DIFF_BRIEF ) return;
  if( (pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT) ){
    return;
  }
  diff_print_index(zName, pCfg, 0);
  if( pFrom ){
    rid = uuid_to_rid(pFrom->zUuid, 0);
    content_get(rid, &f1);
  }else{
    blob_zero(&f1);
  }
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
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
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
1169


1170
1171


1172
1173
1174
1175
1176
1177





1178
1179
1180
1181
1182
1183
1184
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
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226







-
+














+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




-

+
+
-
-
+
+
-
-
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-

-
+



















+

+

+
+
+
+
+
+







    }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){
      /* No changes */
      (void)file_dir_match(pFileDir, pFromFile->zName); /* Record name usage */
      pFromFile = manifest_file_next(pFrom,0);
      pToFile = manifest_file_next(pTo,0);
    }else{
      if( file_dir_match(pFileDir, pToFile->zName) ){
        if( pCfg->diffFlags & DIFF_BRIEF ){
        if((pCfg->diffFlags & DIFF_BRIEF) && !(pCfg->diffFlags & DIFF_NUMSTAT)){
          fossil_print("CHANGED %s\n", pFromFile->zName);
        }else{
          diff_manifest_entry(pFromFile, pToFile, pCfg);
        }
      }
      pFromFile = manifest_file_next(pFrom,0);
      pToFile = manifest_file_next(pTo,0);
    }
  }
  manifest_destroy(pFrom);
  manifest_destroy(pTo);
}

/*
** Compute the difference from an external tree of files to the current
** working checkout with its edits.
**
** To put it another way:  Every managed file in the current working
** checkout is compared to the file with same name under zExternBase.  The
** zExternBase files are on the left and the files in the current working
** directory are on the right.
*/
void diff_externbase_to_checkout(
  const char *zExternBase,   /* Remote tree to use as the baseline */
  DiffConfig *pCfg,          /* Diff settings */
  FileDirList *pFileDir      /* Only look at these files */
){
  int vid;
  Stmt q;

  vid = db_lget_int("checkout",0);
  if( file_isdir(zExternBase, ExtFILE)!=1 ){
    fossil_fatal("\"%s\" is not a directory", zExternBase);
  }
  db_prepare(&q,
    "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname",
    vid
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zFile;  /* Name of file in the repository */
    char *zLhs;         /* Full name of left-hand side file */
    char *zRhs;         /* Full name of right-hand side file */
    Blob rhs;           /* Full text of RHS */
    Blob lhs;           /* Full text of LHS */

    zFile = db_column_text(&q,0);
    if( !file_dir_match(pFileDir, zFile) ) continue;
    zLhs = mprintf("%s/%s", zExternBase, zFile);
    zRhs = mprintf("%s%s", g.zLocalRoot, zFile);
    if( file_size(zLhs, ExtFILE)<0 ){
      blob_zero(&lhs);
    }else{
      blob_read_from_file(&lhs, zLhs, ExtFILE);
    }
    blob_read_from_file(&rhs, zRhs, ExtFILE);
    if( blob_size(&lhs)!=blob_size(&rhs)
     || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0
    ){
      diff_print_index(zFile, pCfg, 0);
      diff_file_mem(&lhs, &rhs, zFile, pCfg);
    }
    blob_reset(&lhs);
    blob_reset(&rhs);
    fossil_free(zLhs);
    fossil_free(zRhs);
  }
  db_finalize(&q);
}


/*
** Return the name of the external diff command, or return NULL if
** no external diff command is defined.
*/
const char *diff_command_external(int guiDiff){
  const char *zDefault;
  const char *zName;
  zName = guiDiff ? "gdiff-command" : "diff-command";
  return db_get(zName, 0);

  if( guiDiff ){
}

#if defined(_WIN32)
    zDefault = "WinDiff.exe";
/*
** Return true if it reasonable to run "diff -tk" for "gdiff".
**
** Details: Return true if all of the following are true:
**
**     (1)   The isGDiff flags is true
#else
    zDefault = 0;
#endif
    zName = "gdiff-command";
  }else{
**     (2)   The "gdiff-command" setting is undefined
**     (3)   There is a "tclsh" on PATH
**     (4)   There is a "wish" on PATH
*/
int gdiff_using_tk(int isGdiff){
  if( isGdiff
   && db_get("gdiff-command","")[0]==0
   && fossil_app_on_path("tclsh",0)
   && fossil_app_on_path("wish",0)
  ){
    return 1;
    zDefault = 0;
    zName = "diff-command";
  }
  return db_get(zName, zDefault);
  return 0;
}

/*
** 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.
*/
void diff_tk(const char *zSubCmd, int firstArg){
  int i;
  Blob script;
  const char *zTempFile = 0;
  char *zCmd;
  const char *zTclsh;
  int bDebug = find_option("tkdebug",0,0)!=0;
  int bDarkMode = find_option("dark",0,0)!=0;
  (void)find_option("debug",0,0);
  blob_zero(&script);
  /* Caution:  When this routine is called from the merge-info command,
  ** the --tcl argument requires an argument.  But merge-info does not
  ** use -i, so we can take -i as that argument.  This routine needs to
  ** always have -i after --tcl.
  **                                                CAUTION!
  **                                                vvvvvvv            */
  blob_appendf(&script, "set fossilcmd {| \"%/\" %s -tcl -i -v",
               g.nameOfExe, zSubCmd);
  find_option("tcl",0,0);
  find_option("html",0,0);
  find_option("side-by-side","y",0);
  find_option("internal","i",0);
  find_option("verbose","v",0);
1154
1155
1156
1157
1158
1159
1160

1161
1162
1163
1164
1165
1166
1167
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253







+







    }else{
      int j;
      blob_append(&script, " ", 1);
      for(j=0; z[j]; j++) blob_appendf(&script, "\\%03o", (unsigned char)z[j]);
    }
  }
  blob_appendf(&script, "}\nset darkmode %d\n", bDarkMode);
  blob_appendf(&script, "set debug %d\n", bDebug);
  blob_appendf(&script, "%s", builtin_file("diff.tcl", 0));
  if( zTempFile ){
    blob_write_to_file(&script, zTempFile);
    fossil_print("To see diff, run: %s \"%s\"\n", zTclsh, zTempFile);
  }else{
#if defined(FOSSIL_ENABLE_TCL)
    Th_FossilInit(TH_INIT_DEFAULT);
1201
1202
1203
1204
1205
1206
1207
1208


1209
1210
1211
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
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261



1262
1263
1264
1265
1266
1267
1268



1269
1270
1271

1272
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
1320
1321
1322
1323
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
1359
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
1398
1399
1400
1401
1402
1403
1404
1405
1406

1407
1408
1409
1410
1411
1412
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
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
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
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
1423
1424
1425
1426

1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
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
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
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533







-
+
+




















+
+
+
+














-
+

















-
+
+
+






-
+
+
+


-
+



-
+
















+

+
-
+



-





-
+









+

-
+


-











+
+
-
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+

+


















+
+
+
-
+











+
+
-
+




-
+

-
+


















-
-
+
+

















+






** COMMAND: gdiff
**
** Usage: %fossil diff|gdiff ?OPTIONS? ?FILE1? ?FILE2 ...?
**
** 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 all unsaved changes
** currently in the working check-out.
** currently in the working check-out.  The "gdiff" variant means to
** use a GUI diff.
**
** The default output format is a "unified patch" (the same as the
** output of "diff -u" on most unix systems).  Many alternative formats
** are available.  A few of the more useful alternatives:
**
**    --tk              Pop up a Tcl/Tk-based GUI to show the diff
**    --by              Show a side-by-side diff in the default web browser
**    -b                Show a linear diff in the default web browser
**    -y                Show a text side-by-side diff
**    --webpage         Format output as HTML
**    --webpage -y      HTML output in the side-by-side format
**
** The "--from VERSION" option is used to specify 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. Similarly, the "--to VERSION"
** option 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.  The "--checkin VERSION" option
** shows the changes made by check-in VERSION relative to its primary parent.
** The "--branch BRANCHNAME" shows all the changes on the branch BRANCHNAME.
**
** With the "--from VERSION" option, if VERSION is actually a directory name
** (not a tag or check-in hash) then the files under that directory are used
** as the baseline for the diff.
**
** The "-i" command-line option forces the use of Fossil's own internal
** diff logic rather than any external diff program that might be configured
** using the "setting" command.  If no external diff program is configured,
** then the "-i" option is a no-op.  The "-i" option converts "gdiff" into
** "diff".
**
** The "--diff-binary" option enables or disables the inclusion of binary files
** 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 the external diff
** program.  This option overrides the "binary-glob" setting.
**
** These command show differences between managed files. Use the "fossil xdiff"
** These commands show differences between managed files. Use the "fossil xdiff"
** command to see differences in unmanaged files.
**
** 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
**   -b|--browser                Show the diff output in a web-browser
**   --by                        Shorthand for "--browser -y"
**   -ci|--checkin VERSION       Show diff of all changes in VERSION
**   --command PROG              External diff program. Overrides "diff-command"
**   -c|--context N              Show N lines of context around each change,
**                               with negative N meaning show all content
**   --dark                      Use dark mode for the Tcl/Tk-based GUI and HTML
**   --diff-binary BOOL          Include binary files with external commands
**   --exec-abs-paths            Force absolute path names on external commands
**   --exec-rel-paths            Force relative path names on external commands
**   -r|--from VERSION           Select VERSION as source for the diff
**   -r|--from VERSION           Use VERSION as the baseline for the diff, or
**                               if VERSION is a directory name, use files in
**                               that directory as the baseline.
**   -w|--ignore-all-space       Ignore white space when comparing lines
**   -i|--internal               Use internal diff logic
**   --invert                    Invert the diff
**   --json                      Output formatted as JSON
**   -n|--linenum                Show line numbers
**   -N|--new-file               Alias for --verbose
**   --numstat                   Show only the number of added and deleted lines
**   -s|--numstat                Show the number of added and deleted lines per
**                               file, omitting the diff. When combined
**                               with --brief, show only the total row.
**   -y|--side-by-side           Side-by-side diff
**   --strip-trailing-cr         Strip trailing CR
**   --tcl                       Tcl-formated output used internally by --tk
**   --tcl                       Tcl-formatted output used internally by --tk
**   --tclsh PATH                Tcl/Tk shell used for --tk (default: "tclsh")
**   --tk                        Launch a Tcl/Tk GUI for display
**   --to VERSION                Select VERSION as target for the diff
**   --undo                      Diff against the "undo" buffer
**   --undo                      Use the undo buffer as the baseline
**   --unified                   Unified diff
**   -v|--verbose                Output complete text of added or deleted files
**   -h|--versions               Show compared versions in the diff header
**   --webpage                   Format output as a stand-alone HTML webpage
**   -W|--width N                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 */
  const char *zFrom;         /* Source version number */
  const char *zTo;           /* Target version number */
  const char *zCheckin;      /* Check-in version number */
  const char *zBranch;       /* Branch to diff */
  int againstUndo = 0;       /* Diff against files in the undo buffer */
  FileDirList *pFileDir = 0; /* Restrict the diff to these files */
  DiffConfig DCfg;           /* Diff configuration object */
  int bFromIsDir = 0;        /* True if zFrom is a directory name */

  isGDiff = g.argv[1][0]=='g';
  if( find_option("tk",0,0)!=0 || has_option("tclsh") ){
  if( find_option("tk",0,0)!=0|| has_option("tclsh") ){
    diff_tk("diff", 2);
    return;
  }
  isGDiff = g.argv[1][0]=='g';
  zFrom = find_option("from", "r", 1);
  zTo = find_option("to", 0, 1);
  zCheckin = find_option("checkin", "ci", 1);
  zBranch = find_option("branch", 0, 1);
  againstUndo = find_option("undo",0,0)!=0;
  if( againstUndo && ( zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){
  if( againstUndo && (zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){
    fossil_fatal("cannot use --undo together with --from, --to, --checkin,"
                 " or --branch");
  }
  if( zBranch ){
    if( zTo || zFrom || zCheckin ){
      fossil_fatal("cannot use --from, --to, or --checkin with --branch");
    }
    zTo = zBranch;
    zFrom = mprintf("root:%s", zBranch);
    zBranch = 0;
  }
  if( zCheckin!=0 && ( zFrom!=0 || zTo!=0 ) ){
  if( zCheckin!=0 && (zFrom!=0 || zTo!=0) ){
    fossil_fatal("cannot use --checkin together with --from or --to");
  }
  g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0;
  if( 0==zCheckin ){
    if( zTo==0 || againstUndo ){
      db_must_be_within_tree();
    }else if( zFrom==0 ){
      fossil_fatal("must use --from if --to is present");
    }else{
      db_find_and_open_repository(0, 0);
    }
  }else{
    db_find_and_open_repository(0, 0);
  }
  if( gdiff_using_tk(isGDiff) ){
    restore_option("--from", zFrom, 1);
  diff_options(&DCfg, isGDiff, 0);
    restore_option("--to", zTo, 1);
    restore_option("--checkin", zCheckin, 1);
    restore_option("--branch", zBranch, 1);
    if( againstUndo ) restore_option("--undo", 0, 0);
    diff_tk("diff", 2);
    return;
  }
  determine_exec_relative_option(1);
  if( zFrom!=file_tail(zFrom)
   && file_isdir(zFrom, ExtFILE)==1
   && !db_exists("SELECT 1 FROM tag WHERE tagname='sym-%q'", zFrom)
  ){
    bFromIsDir = 1;
    if( zTo ){
      fossil_fatal("cannot use --to together with \"--from PATH\"");
    }
  }
  diff_options(&DCfg, isGDiff, 0);
  verify_all_options();
  g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0;
  if( g.argc>=3 ){
    int i;
    Blob fname;
    pFileDir = fossil_malloc( sizeof(*pFileDir) * (g.argc-1) );
    memset(pFileDir, 0, sizeof(*pFileDir) * (g.argc-1));
    for(i=2; i<g.argc; i++){
      file_tree_name(g.argv[i], &fname, 0, 1);
      pFileDir[i-2].zName = fossil_strdup(blob_str(&fname));
      if( strcmp(pFileDir[i-2].zName,".")==0 ){
        pFileDir[0].zName[0] = '.';
        pFileDir[0].zName[1] = 0;
        break;
      }
      pFileDir[i-2].nName = blob_size(&fname);
      pFileDir[i-2].nUsed = 0;
      blob_reset(&fname);
    }
  }
  if( (DCfg.diffFlags & DIFF_NUMSTAT) && !(DCfg.diffFlags & DIFF_BRIEF) ){
    fossil_print("%10s %10s\n", "INSERTED", "DELETED");
  }
  if ( zCheckin!=0 ){
  if( zCheckin!=0 ){
    int ridTo = name_to_typed_rid(zCheckin, "ci");
    zTo = zCheckin;
    zFrom = db_text(0,
      "SELECT uuid FROM blob, plink"
      " WHERE plink.cid=%d AND plink.isprim AND plink.pid=blob.rid",
      ridTo);
    if( zFrom==0 ){
      fossil_fatal("check-in %s has no parent", zTo);
    }
  }
  diff_begin(&DCfg);
  if( bFromIsDir ){
    diff_externbase_to_checkout(zFrom, &DCfg, pFileDir);
  if( againstUndo ){
  }else if( againstUndo ){
    if( db_lget_int("undo_available",0)==0 ){
      fossil_print("No undo or redo is available\n");
      return;
    }
    diff_against_undo(&DCfg, pFileDir);
    diff_undo_to_checkout(&DCfg, pFileDir);
  }else if( zTo==0 ){
    diff_against_disk(zFrom, &DCfg, pFileDir, 0);
    diff_version_to_checkout(zFrom, &DCfg, pFileDir, 0);
  }else{
    diff_two_versions(zFrom, zTo, &DCfg, pFileDir);
  }
  if( pFileDir ){
    int i;
    for(i=0; pFileDir[i].zName; i++){
      if( pFileDir[i].nUsed==0
       && strcmp(pFileDir[0].zName,".")!=0
       && !file_isdir(g.argv[i+2], ExtFILE)
      ){
        fossil_fatal("not found: '%s'", g.argv[i+2]);
      }
      fossil_free(pFileDir[i].zName);
    }
    fossil_free(pFileDir);
  }
  diff_end(&DCfg, 0);
  if ( DCfg.diffFlags & DIFF_NUMSTAT ){
    fossil_print("%10d %10d TOTAL over %d changed files\n",
                 g.diffCnt[1], g.diffCnt[2], g.diffCnt[0]);
    fossil_print("%10d %10d TOTAL over %d changed file%s\n",
           g.diffCnt[1], g.diffCnt[2], g.diffCnt[0], g.diffCnt[0]!=1 ? "s": "");
  }
}

/*
** WEBPAGE: vpatch
** URL: /vpatch?from=FROM&to=TO
**
** Show a patch that goes from check-in FROM to check-in TO.
*/
void vpatch_page(void){
  const char *zFrom = P("from");
  const char *zTo = P("to");
  DiffConfig DCfg;
  cgi_check_for_malice();
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( zFrom==0 || zTo==0 ) fossil_redirect_home();
  if( robot_restrict("diff") ) return;

  fossil_nice_default();
  cgi_set_content_type("text/plain");
  diff_config_init(&DCfg, DIFF_VERBOSE);
  diff_two_versions(zFrom, zTo, &DCfg, 0);
}
Changes to src/dispatch.c.
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
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







-
+













-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



-
-
+
+







#include "config.h"
#include <assert.h>
#include "dispatch.h"

#if INTERFACE
/*
** An instance of this object defines everything we need to know about an
** individual command, webpage, or setting.
** individual command, webpage, setting, or help topic.
*/
struct CmdOrPage {
  const char *zName;       /* Name.  Webpages start with "/". Commands do not */
  void (*xFunc)(void);     /* Implementation function, or NULL for settings */
  const char *zHelp;       /* Raw help text */
  int iHelp;               /* Index of help variable */
  unsigned int eCmdFlags;  /* Flags */
};

/***************************************************************************
** These macros must match similar macros in mkindex.c
** Allowed values for CmdOrPage.eCmdFlags.
*/
#define CMDFLAG_1ST_TIER     0x0001     /* Most important commands */
#define CMDFLAG_2ND_TIER     0x0002     /* Obscure and seldom used commands */
#define CMDFLAG_TEST         0x0004     /* Commands for testing only */
#define CMDFLAG_WEBPAGE      0x0008     /* Web pages */
#define CMDFLAG_COMMAND      0x0010     /* A command */
#define CMDFLAG_SETTING      0x0020     /* A setting */
#define CMDFLAG_VERSIONABLE  0x0040     /* A versionable setting */
#define CMDFLAG_BLOCKTEXT    0x0080     /* Multi-line text setting */
#define CMDFLAG_BOOLEAN      0x0100     /* A boolean setting */
#define CMDFLAG_RAWCONTENT   0x0200     /* Do not interpret POST content */
/* NOTE:                     0x0400 = CMDFLAG_SENSITIVE in mkindex.c! */
#define CMDFLAG_HIDDEN       0x0800     /* Elide from most listings */
#define CMDFLAG_LDAVG_EXEMPT 0x1000     /* Exempt from load_control() */
#define CMDFLAG_ALIAS        0x2000     /* Command aliases */
#define CMDFLAG_KEEPEMPTY    0x4000     /* Do not unset empty settings */
#define CMDFLAG_1ST_TIER     0x000001   /* Most important commands */
#define CMDFLAG_2ND_TIER     0x000002   /* Obscure and seldom used commands */
#define CMDFLAG_TEST         0x000004   /* Commands for testing only */
#define CMDFLAG_WEBPAGE      0x000008   /* Web pages */
#define CMDFLAG_COMMAND      0x000010   /* A command */
#define CMDFLAG_SETTING      0x000020   /* A setting */
#define CMDFLAG_VERSIONABLE  0x000040   /* A versionable setting */
#define CMDFLAG_BLOCKTEXT    0x000080   /* Multi-line text setting */
#define CMDFLAG_BOOLEAN      0x000100   /* A boolean setting */
#define CMDFLAG_RAWCONTENT   0x000200   /* Do not interpret POST content */
/* NOTE:                     0x000400 = CMDFLAG_SENSITIVE in mkindex.c! */
#define CMDFLAG_HIDDEN       0x000800   /* Elide from most listings */
#define CMDFLAG_LDAVG_EXEMPT 0x001000   /* Exempt from load_control() */
#define CMDFLAG_ALIAS        0x002000   /* Command aliases */
#define CMDFLAG_KEEPEMPTY    0x004000   /* Do not unset empty settings */
#define CMDFLAG_ABBREVSUBCMD 0x008000   /* Help text abbreviates subcommands */
#define CMDFLAG_TOPIC        0x010000   /* A help topic */
/**************************************************************************/

/* Values for the 2nd parameter to dispatch_name_search() */
#define CMDFLAG_ANY         0x0038      /* Match anything */
#define CMDFLAG_PREFIX      0x0200      /* Prefix match is ok */
#define CMDFLAG_ANY          0x010038   /* Match anything */
#define CMDFLAG_PREFIX       0x000200   /* Prefix match is OK */

#endif /* INTERFACE */

/*
** The page_index.h file contains the definition for aCommand[] - an array
** of CmdOrPage objects that defines all available commands and webpages
** known to Fossil.
272
273
274
275
276
277
278
279

280
281
282
283
284
285
286
274
275
276
277
278
279
280

281
282
283
284
285
286
287
288







-
+







  int j;
  while( i<n ){
    char c = z[i];
    if( c=='[' && (j = help_is_link(z+i, n-i))>0 ){
      if( i ) blob_append(pOut, z, i);
      z += i+2;
      n -= i+2;
      blob_appendf(pOut, "<a href='%R/help?cmd=%.*s'>%.*s</a>",
      blob_appendf(pOut, "<a href='%R/help/%.*s'>%.*s</a>",
         j-3, z, j-3, z);
      z += j-1;
      n -= j-1;
      i = 0;
    }else if( c=='%' && n-i>=7 && strncmp(z+i,"%fossil",7)==0 ){
      if( i ) blob_append(pOut, z, i);
      z += i+7;
458
459
460
461
462
463
464

465




466
467
468
469
470
471
472
460
461
462
463
464
465
466
467

468
469
470
471
472
473
474
475
476
477
478







+
-
+
+
+
+







      }else if( isDT
             || zHelp[nIndent]=='-'
             || hasGap(zHelp+nIndent,i-nIndent) ){
        iLevel++;
        aIndent[iLevel] = nIndent;
        azEnd[iLevel] = zEndDL;
        wantP = 0;
        if( isDT ){
        blob_append(pHtml, "<blockquote><dl>\n", -1);
          blob_append(pHtml, "<blockquote><dl>\n", -1);
        }else{
          blob_append(pHtml, "<blockquote><dl class=\"helpOptions\">\n", -1);
        }
      }else if( azEnd[iLevel]==zEndDL ){
        iLevel++;
        aIndent[iLevel] = nIndent;
        azEnd[iLevel] = zEndDD;
        if( wantP ){
          blob_append(pHtml,"<p>", 3);
          wantP = 0;
520
521
522
523
524
525
526
527

528
529








530
531
532
533
534
535
536
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







-
+


+
+
+
+
+
+
+
+







    blob_appendf(pHtml, "%s\n", azEnd[iLevel--]);
  }
}

/*
** Format help text for TTY display.
*/
static void help_to_text(const char *zHelp, Blob *pText){
static void help_to_text(const char *zHelp, Blob *pText, int bUsage){
  int i, x;
  char c;
  if( zHelp[0]=='>' ){
    if( !bUsage ){
      blob_appendf(pText, "Usage:");
    }else{
      blob_append_char(pText, ' ');
    }
    zHelp++;
  }
  for(i=0; (c = zHelp[i])!=0; i++){
    if( c=='%' && strncmp(zHelp+i,"%fossil",7)==0 ){
      if( i>0 ) blob_append(pText, zHelp, i);
      blob_append(pText, "fossil", 6);
      zHelp += i+7;
      i = -1;
      continue;
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







+







  fossil_print("Help text for:\n");
  if( mask & CMDFLAG_1ST_TIER ) fossil_print(" * Commands\n");
  if( mask & CMDFLAG_2ND_TIER ) fossil_print(" * Auxiliary commands\n");
  if( mask & CMDFLAG_ALIAS )    fossil_print(" * Aliases\n");
  if( mask & CMDFLAG_TEST )     fossil_print(" * Test commands\n");
  if( mask & CMDFLAG_WEBPAGE )  fossil_print(" * Web pages\n");
  if( mask & CMDFLAG_SETTING )  fossil_print(" * Settings\n");
  if( mask & CMDFLAG_TOPIC )    fossil_print(" * Help Topic\n");
  if( useHtml ){
    fossil_print("-->\n");
    fossil_print("<!-- start_all_help -->\n");
  }else{
    fossil_print("---\n");
  }
  /* Fill in help string buckets */
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







-
+







      }else if( rawOut ){
        for(j=0; j<occHelp[aCommand[i].iHelp]; j++)
          fossil_print("# %s\n", aCommand[bktHelp[aCommand[i].iHelp][j]].zName);
        fossil_print("%s\n\n", aCommand[i].zHelp);
      }else{
          Blob txt;
          blob_init(&txt, 0, 0);
          help_to_text(aCommand[i].zHelp, &txt);
          help_to_text(aCommand[i].zHelp, &txt, 0);
          for(j=0; j<occHelp[aCommand[i].iHelp]; j++){
            fossil_print("# %s%s\n",
              aCommand[bktHelp[aCommand[i].iHelp][j]].zName,
              (aCommand[i].eCmdFlags & CMDFLAG_VERSIONABLE)!=0 ?
              " (versionable)" : "");
          }
          fossil_print("%s\n\n", blob_str(&txt));
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
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







+



















-
+









+
+
+







**
** Show help text for commands and pages.  Useful for proof-reading.
** Defaults to just the CLI commands.  Specify --www to see only the
** web pages, or --everything to see both commands and pages.
**
** Options:
**    -a|--aliases      Show aliases
**    -c|--topics       Show help topics
**    -e|--everything   Show all commands and pages.  Omit aliases to
**                      avoid duplicates.
**    -h|--html         Transform output to HTML
**    -o|--options      Show global options
**    -r|--raw          No output formatting
**    -s|--settings     Show settings
**    -t|--test         Include test- commands
**    -w|--www          Show WWW pages
*/
void test_all_help_cmd(void){
  int mask = CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER;
  int useHtml = find_option("html","h",0)!=0;
  int rawOut = find_option("raw","r",0)!=0;

  if( find_option("www","w",0) ){
    mask = CMDFLAG_WEBPAGE;
  }
  if( find_option("everything","e",0) ){
    mask = CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER | CMDFLAG_WEBPAGE |
              CMDFLAG_ALIAS | CMDFLAG_SETTING | CMDFLAG_TEST;
              CMDFLAG_ALIAS | CMDFLAG_SETTING | CMDFLAG_TEST | CMDFLAG_TOPIC;
  }
  if( find_option("settings","s",0) ){
    mask = CMDFLAG_SETTING;
  }
  if( find_option("aliases","a",0) ){
    mask = CMDFLAG_ALIAS;
  }
  if( find_option("test","t",0) ){
    mask |= CMDFLAG_TEST;
  }
  if( find_option("topics","c",0) ){
    mask |= CMDFLAG_TOPIC;
  }
  display_all_help(mask, useHtml, rawOut);
}

/*
** Count the number of entries in the aCommand[] table that match
** the given flag.
699
700
701
702
703
704
705


706
707
708
709
710
711
712
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733







+
+







     countCmds( CMDFLAG_ALIAS ));
  fossil_print("  test             %4d\n",
     countCmds( CMDFLAG_TEST ));
  fossil_print("web-pages:      %4d\n",
     countCmds( CMDFLAG_WEBPAGE ));
  fossil_print("settings:       %4d\n",
     countCmds( CMDFLAG_SETTING ));
  fossil_print("help-topics:    %4d\n",
     countCmds( CMDFLAG_TOPIC ));
  fossil_print("total entries:  %4d\n", MX_COMMAND);
}

/*
** Compute an estimate of the edit-distance between to input strings.
**
** The first string is the input.  The second is the pattern.  Only the
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







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
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
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
821
822
823
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
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
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
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







+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
+

-
-
+
+
+
+
+



+
+
+
+
+
+
-
+

















+
+
+
+
+
+
+
-
-
+
+
-

+
+
+
+
+
+
+
-
+


-
+


+
+

-
+

-
-
+
+
+
+
+
+

-
+






-
+




















+


-
+












-
+
+
+


-
+
















-
+











-
+







-
+




-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-











-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    n = dispatch_approx_match(g.argv[i], 20, az);
    for(j=0; j<n; j++){
      fossil_print("   %s\n", az[j]);
    }
  }
}


/*
** Returns 1 if the command or page name zName is known to be a
** command/page which is only available in certain builds/platforms,
** else returns 0.
*/
static int help_is_platform_command(const char *zName){
  const char *aList[] = {
    /* List of commands/pages which are known to only be available in
    ** certain builds/platforms. */
    "winsrv",
    "json", "/json",
    NULL /* end-of-list sentinel */
  };
  int i = 0;
  const char *z;
  for( z = aList[0]; z ; z = aList[++i] ){
    if( 0==fossil_strcmp(zName, z) ) return 1;
  }
  return 0;
}

/*
** WEBPAGE: help
** URL: /help?name=CMD
** URL: /help/CMD or  /help/www/PAGE
**
** Show the built-in help text for CMD.  CMD can be a command-line interface
** command or a page name from the web interface or a setting.
** Show the built-in help text for CMD or PAGE.  CMD can be a command-line
** interface command or a setting name.  PAGE is the name of a
** web interface.  /help//PAGE also works if the double-/ makes it through
** the main web server.
**
** Query parameters:
**
**    name=CMD        Show help for CMD where CMD is a command name or
**                    or setting name.  If CMD beings with "/" it is
**                    interpreted as a PAGE name.
**
**    name=www/PAGE   Show help for web page PAGE.
**
**    name=/PAGE      The initial "www/" on web-page help can be abbreviated as
**                    webpage name or setting name.
**                    just "/"
**
**    plaintext       Show the help within <pre>...</pre>, as if it were
**                    displayed using the "fossil help" command.
**
**    raw             Show the raw help text without any formatting.
**                    (Used for debugging.)
*/
void help_page(void){
  const char *zCmd = P("cmd");

  if( zCmd==0 ) zCmd = P("name");
  cgi_check_for_malice();
  if( zCmd && *zCmd ){
    int rc;
    const CmdOrPage *pCmd = 0;

    style_set_current_feature("tkt");
    style_submenu_element("Topic-List", "%R/help");
    if( search_restrict(SRCH_HELP)!=0 ){
      style_submenu_element("Search","%R/search?y=h");
    }
    if( strncmp(zCmd,"www/",4)==0 && zCmd[4]!=0 ){
      /* Use https://domain/fossil/help/www/timeline or similar with the "www"
      ** intermediate tag to view web-page documentation. */
    style_header("Help: %s", zCmd);

      zCmd += 3;
    }
    style_submenu_element("Command-List", "%R/help");
    rc = dispatch_name_search(zCmd, CMDFLAG_ANY|CMDFLAG_PREFIX, &pCmd);
    if( pCmd ){
      style_header("Help: %s", pCmd->zName);
    }else{
      style_header("Help");
    }
    if( pCmd==0 ){
      /* No <h1> line in this case */
    if( *zCmd=='/' ){
    }else if( *zCmd=='/' ){
      /* Some of the webpages require query parameters in order to work.
      ** @ <h1>The "<a href='%R%s(zCmd)'>%s(zCmd)</a>" page:</h1> */
      @ <h1>The "%h(zCmd)" page:</h1>
      @ <h1>The "%h(pCmd->zName)" page:</h1>
    }else if( rc==0 && (pCmd->eCmdFlags & CMDFLAG_SETTING)!=0 ){
      @ <h1>The "%h(pCmd->zName)" setting:</h1>
    }else if( rc==0 && (pCmd->eCmdFlags & CMDFLAG_TOPIC)!=0 ){
      @ <h1>The "%h(pCmd->zName)" help topic:</h1>
    }else{
      @ <h1>The "%h(zCmd)" command:</h1>
      @ <h1>The "%h(pCmd->zName)" command:</h1>
    }
    if( rc==1 ){
      @ unknown command: %h(zCmd)
    if( rc==1 || (rc==2 && zCmd[0]=='/') ){
      if( zCmd && help_is_platform_command(zCmd) ){
        @ Not available in this build: "%h(zCmd)"
      }else{
        @ Unknown topic: "%h(zCmd)"
      }
    }else if( rc==2 ){
      @ ambiguous command prefix: %h(zCmd)
      @ Ambiguous prefix: "%h(zCmd)"
    }else{
      if( pCmd->zHelp[0]==0 ){
        @ No help available for "%h(pCmd->zName)"
      }else if( P("plaintext") ){
        Blob txt;
        blob_init(&txt, 0, 0);
        help_to_text(pCmd->zHelp, &txt);
        help_to_text(pCmd->zHelp, &txt, 0);
        @ <pre class="helpPage">
        @ %h(blob_str(&txt))
        @ </pre>
        blob_reset(&txt);
      }else if( P("raw") ){
        @ <pre class="helpPage">
        @ %h(pCmd->zHelp)
        @ </pre>
      }else{
        @ <div class="helpPage">
        help_to_html(pCmd->zHelp, cgi_output_blob());
        @ </div>
      }
    }
  }else{
    int i;
    const char *zWidth = "28ex";
    unsigned char occHelp[FOSSIL_MX_CMDIDX] = {0};   /* Help str occurrences */
    int bktHelp[FOSSIL_MX_CMDIDX][MX_HELP_DUP] = {{0}};/* Help str->commands */
    style_header("Help");
    search_screen(SRCH_HELP, 0x02);

    @ <a name='commands'></a>
    @ <h1>Available commands:</h1>
    @ <h1>Commands:</h1>
    @ <div class="columns" style="column-width: %s(zWidth);">
    @ <ul>
    /* Fill in help string buckets */
    for(i=0; i<MX_COMMAND; i++){
      if(aCommand[i].eCmdFlags & CMDFLAG_HIDDEN) continue;
      bktHelp[aCommand[i].iHelp][occHelp[aCommand[i].iHelp]++] = i;
    }
    for(i=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      const char *zBoldOn  = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"<b>" :"";
      const char *zBoldOff = aCommand[i].eCmdFlags&CMDFLAG_1ST_TIER?"</b>":"";
      if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
      if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)!=0 ) continue;
      if( (aCommand[i].eCmdFlags & (CMDFLAG_SETTING|CMDFLAG_TOPIC))!=0 ){
        continue;
      }
      else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue;
      else if( (aCommand[i].eCmdFlags & CMDFLAG_ALIAS)!=0 ) continue;
      @ <li><a href="%R/help?cmd=%s(z)">%s(zBoldOn)%s(z)%s(zBoldOff)</a>
      @ <li><a href="%R/help/%s(z)">%s(zBoldOn)%s(z)%s(zBoldOff)</a>
      /* Output aliases */
      if( occHelp[aCommand[i].iHelp] > 1 ){
        int j;
        int aliases[MX_HELP_DUP], nAliases=0;
        for(j=0; j<occHelp[aCommand[i].iHelp]; j++){
          if( bktHelp[aCommand[i].iHelp][j] != i ){
            if( aCommand[bktHelp[aCommand[i].iHelp][j]].eCmdFlags
                & CMDFLAG_ALIAS ){
              aliases[nAliases++] = bktHelp[aCommand[i].iHelp][j];
            }
          }
        }
        if( nAliases>0 ){
          int k;
          @(\
          for(k=0; k<nAliases; k++){
            @<a href="%R/help?cmd=%s(aCommand[aliases[k]].zName)">\
            @<a href="%R/help/%s(aCommand[aliases[k]].zName)">\
            @%s(aCommand[aliases[k]].zName)</a>%s((k<nAliases-1)?", ":"")\
          }
          @)\
        }
      }
      @ </li>
    }

    @ </ul></div>

    @ <a name='webpages'></a>
    @ <h1>Available web UI pages:</h1>
    @ <h1>Web pages:</h1>
    @ <div class="columns" style="column-width: %s(zWidth);">
    @ <ul>
    for(i=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      if( '/'!=*z ) continue;
      else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue;
      if( aCommand[i].zHelp[0] ){
        @ <li><a href="%R/help?cmd=%s(z)">%s(z+1)</a></li>
        @ <li><a href="%R/help/www%s(z)">%s(z+1)</a></li>
      }else{
        @ <li>%s(z+1)</li>
      }
    }
    @ </ul></div>

    @ <a name='unsupported'></a>
    @ <h1>Unsupported commands:</h1>
    @ <div class="columns" style="column-width: %s(zWidth);">
    @ <ul>
    for(i=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      if( strncmp(z,"test",4)!=0 ) continue;
      else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue;
      if( aCommand[i].zHelp[0] ){
        @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
      }else{
        @ <li>%s(z)</li>
      }
    }
    @ </ul></div>

    @ <a name='settings'></a>
    @ <h1>Settings:</h1>
    @ <div class="columns" style="column-width: %s(zWidth);">
    @ <ul>
    for(i=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      if( (aCommand[i].eCmdFlags & CMDFLAG_SETTING)==0 ) continue;
      else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue;
      if( aCommand[i].zHelp[0] ){
        @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
        @ <li><a href="%R/help/%s(z)">%s(z)</a></li>
      }else{
        @ <li>%s(z)</li>
      }
    }
    @ </ul></div>

    @ <a name='topics'></a>
    @ <h1>Other Miscellaneous Help Topics:</h1>
    @ <div class="columns" style="column-width: %s(zWidth);">
    @ <ul>
    for(i=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      if( (aCommand[i].eCmdFlags & CMDFLAG_TOPIC)==0 ) continue;
      if( aCommand[i].zHelp[0] ){
        @ <li><a href="%R/help/%s(z)">%s(z)</a></li>
      }else{
        @ <li>%s(z)</li>
      }
    }
    @ </ul></div>

    @ <a name='unsupported'></a>
    @ <h1>Unsupported and Testing Commands:</h1>
    @ <div class="columns" style="column-width: %s(zWidth);">
    @ <ul>
    for(i=0; i<MX_COMMAND; i++){
      const char *z = aCommand[i].zName;
      if( strncmp(z,"test",4)!=0 ) continue;
      else if( (aCommand[i].eCmdFlags & CMDFLAG_HIDDEN)!=0 ) continue;
      if( aCommand[i].zHelp[0] ){
        @ <li><a href="%R/help/%s(z)">%s(z)</a></li>
      }else{
        @ <li>%s(z)</li>
      }
    }
    @ </ul></div>

  }
999
1000
1001
1002
1003
1004
1005


1006
1007
1008
1009
1010
1011
1012
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103







+
+







      zDesc = "1st tier command";
    }else if( e & CMDFLAG_2ND_TIER ){
      zDesc = "2nd tier command";
    }else if( e & CMDFLAG_ALIAS ){
      zDesc = "alias";
    }else if( e & CMDFLAG_TEST ){
      zDesc = "test command";
    }else if( e & CMDFLAG_TOPIC ){
      zDesc = "help-topic";
    }else if( e & CMDFLAG_WEBPAGE ){
      if( e & CMDFLAG_RAWCONTENT ){
        zDesc = "raw-content web page";
      }else{
        zDesc = "web page";
      }
    }else{
1053
1054
1055
1056
1057
1058
1059



























































































































































































































1060
1061
1062
1063
1064
1065
1066
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
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
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
1210
1211
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
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
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
1320
1321
1322
1323
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
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







      occHelp[aCommand[i].iHelp] = 0;
    }
  }
  @ </dl>
  blob_reset(&buf);
  style_finish_page();
}

/*
** Analyze p and return one of three values:
**
**     0     p is the continuation of a prior subcommand.
**
**     1     p is text past the end of a prior subcommand.
**
**     2     p is the start of a new subcommand.
*/
static int is_subcommand(Blob *p, int bAbbrevSubcmd){
  int i, sz;
  const unsigned char *z = (const unsigned char*)blob_buffer(p);
  sz = blob_size(p);
  if( sz>6 ) sz = 6;
  for(i=0; i<sz && fossil_isspace(z[i]); i++){}
  if( i>=sz ) return 0;

  if( bAbbrevSubcmd==0 ){
    if( i>1 ) return 0;
    return z[0]=='>' ? 2 : 1;
  }else{
    return (i==3 && fossil_isalpha(z[3])) ? 2 : 1;
  }
}

/*
** Input z[] is help text for zTopic.  If zTopic has sub-command zSub,
** then cut out all portions of the original help text that do not
** directly pertain to zSub and write the zSub-relevant parts into
** pOut.
**
** Return the number of lines of z[] written into pOut.  A return of
** zero means no simplification occurred.
*/
static int simplify_to_subtopic(
  const char *z,            /* Full original help text */
  Blob *pOut,               /* Write simplified help text here */
  const char *zTopic,       /* TOPIC */
  const char *zSubtopic,    /* SUBTOPIC */
  int bAbbrevSubcmd         /* True if z[] contains abbreviated subcommands */
){
  Blob in, line;/*, subsection;*/
  int n = 0;
  char *zQTop = re_quote(zTopic);
  char *zQSub = re_quote(zSubtopic);
  char *zPattern;
  ReCompiled *pRe = 0;

  if( bAbbrevSubcmd ){
    zPattern = mprintf("   ([a-z]+ ?\\| ?)*%s\\b", zQSub);
  }else{
    zPattern = mprintf(">  ?fossil [-a-z]+ .*\\b%s\\b", zQSub);
  }
  fossil_free(zQTop);
  fossil_free(zQSub);
  fossil_re_compile(&pRe, zPattern, 0);
  fossil_free(zPattern);
  blob_init(&in, z, -1);
  while( blob_line(&in, &line) ){
    if( re_match(pRe, (unsigned char*)blob_buffer(&line), blob_size(&line)) ){
      int atStart = 1;
      blob_appendb(pOut, &line);
      n++;
      while( blob_line(&in, &line) ){
        if( re_match(pRe,(unsigned char*)blob_buffer(&line),blob_size(&line)) ){
          blob_appendb(pOut, &line);
          n++;
          atStart = 1;
        }else{
          int x = is_subcommand(&line,bAbbrevSubcmd);
          if( x==2 ){
            if( atStart ){
              blob_appendb(pOut, &line);
              n++;
            }else{
              break;
            }
          }else if( x==1 ){
            break;
          }else{
            blob_appendb(pOut, &line);
            n++;
            atStart = 0;
          }
        }
      }
    }
  }
  blob_reset(&line);
  re_free(pRe);
  if( n ){
    blob_trim(pOut);
    blob_reset(&in);
  }
  return n;
}

/*
** Input p is a "Usage:" line or a subcommand line.  Simplify this line
** for the --usage option and write it into pOut.
*/
static void simplify_usage_line(
  Blob *p,
  Blob *pOut,
  int bAbbrevSubcmd,
  const char *zCmd
){
  const char *z = blob_buffer(p);
  int sz = blob_size(p);
  int i = 0;
  if( sz>6 && z[0]=='U' ){
    for(i=1; i<sz && !fossil_isspace(z[i]); i++){}
  }else if( sz>0 && z[0]=='>' ){
    i = 1;
  }else if( sz>4 && bAbbrevSubcmd
         && memcmp(z,"   ",3)==0 && !fossil_isspace(z[3]) ){
    int j;
    for(j=3; j<sz-1 && (z[j]!=' ' || z[j+1]!=' '); j++){}
    blob_appendf(pOut, "fossil %s %.*s\n", zCmd, j-3, &z[3]);
    return;
  }else{
    while( i<sz && fossil_isspace(z[i]) ) i++;
    if( i+2<sz && (z[i]=='o' || z[i]=='O') && z[i+1]=='r' ){
      while( i<sz && !fossil_isspace(z[i]) ) i++;
    }
  }
  while( i<sz && fossil_isspace(z[i]) ) i++;
  blob_append(pOut, &z[i], sz-i);
}

/*
** Input z[] is help text for a command zTopic.  Write into pOut all lines of
** z[] that show the command-line syntax for that command.  Lines written
** to pOut are lines that begin with out of:
**
**     Usage:
**     or:
**     > fossil TOPIC
**
** Return the number of lines written into pOut.
*/
static int simplify_to_usage(
  const char *z,            /* Full original help text */
  Blob *pOut,               /* Write simplified help text here */
  const char *zTopic,       /* The command for which z[] is full help text */
  int bAbbrevSubcmd         /* z[] uses abbreviated subcommands */
){
  ReCompiled *pRe = 0;
  Blob in, line;
  int n = 0;

  if( bAbbrevSubcmd ){
    fossil_re_compile(&pRe, "^(Usage: |   [a-z][-a-z|]+ .*)", 0);
  }else{
    fossil_re_compile(&pRe, "^(Usage: | *[Oo]r: +%fossi |>  ?fossil )", 0);
  }
  blob_init(&in, z, -1);
  while( blob_line(&in, &line) ){
    if( re_match(pRe, (unsigned char*)blob_buffer(&line), blob_strlen(&line)) ){
      simplify_usage_line(&line, pOut, bAbbrevSubcmd, zTopic);
      n++;
    }
  }
  re_free(pRe);
  if( n ) blob_trim(pOut);
  return n;
}

/*
** Input z[] is help text.  Write into pOut all lines of z[] that show
** command-line options.  Return the number of lines written.
*/
static int simplify_to_options(
  const char *z,            /* Full original help text */
  Blob *pOut,               /* Write simplified help text here */
  int bAbbrevSubcmd,        /* z[] uses abbreviated subcommands */
  const char *zCmd          /* Name of the command that z[] describes */
){
  ReCompiled *pRe = 0;
  Blob txt, line, subsection;
  int n = 0;
  int bSubsectionSeen = 0;

  blob_init(&txt, z, -1);
  blob_init(&subsection, 0, 0);
  fossil_re_compile(&pRe, "^ +-.*  ", 0);
  while( blob_line(&txt, &line) ){
    int len = blob_size(&line);
    unsigned char *zLine = (unsigned char *)blob_buffer(&line);
    if( re_match(pRe, zLine, len) ){
      if( blob_size(&subsection) ){
        simplify_usage_line(&subsection, pOut, bAbbrevSubcmd, zCmd);
        blob_reset(&subsection);
      }
      blob_appendb(pOut, &line);
    }else if( len>7 && !fossil_isspace(zLine[0]) && bSubsectionSeen 
           && sqlite3_strlike("%options:%",blob_str(&line),0)==0 ){
      subsection = line;
    }else if( !bAbbrevSubcmd && len>9
           && (memcmp(zLine,"> fossil ",9)==0 
               || memcmp(zLine,">  fossil",9)==0) ){
      subsection = line;
      bSubsectionSeen = 1;
    }else if( bAbbrevSubcmd && len>5 && memcmp(zLine,"   ",3)==0
           && fossil_isalpha(zLine[3]) ){
      subsection = line;
      bSubsectionSeen = 1;
    }else if( len>1 && !fossil_isspace(zLine[0]) && bSubsectionSeen ){
      blob_reset(&subsection);
    }
  }
  re_free(pRe);
  blob_trim(pOut);
  blob_reset(&subsection);
  return n;
}



static void multi_column_list(const char **azWord, int nWord){
  int i, j, len;
  int mxLen = 0;
  int nCol;
  int nRow;
  for(i=0; i<nWord; i++){
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
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
1169
1170
1171
1172
1173

1174
1175





1176
1177
1178
1179
1180
1181
1182
1183
1184
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
1210
1211
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
1249

1250
1251

1252
1253
1254
1255
1256
1257
1258

1259

1260
1261
1262
1263




1264
1265
1266
1267
1268
1269
1270
1271
1272
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
1320
1321
1322
1323
1324
1325
1423
1424
1425
1426
1427
1428
1429


1430
1431



1432





1433
1434
1435
1436
1437

















1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
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
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
1517
1518
1519
1520
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
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
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
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
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







-
-
+
+
-
-
-
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+




-
+



-
+
+
+




+
+
+
+






-
-
-
-



+

-
+
+
+
+
+



-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



+
-
-
+
+
-
-
-
+


















+
+
+
+






-




+
+


+
-
-
+
+
+




-
+
+



+

+
+
+
+
+
+
+



+
-
-
-
+
+
+
+
+
+





+

-
+




-
-

+
-
+




+
+
+
+










-
-
-
-
-
-
-
+
+
+
+
+
+
+

+





+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+












+
+
-
+


-
+

-
+

+
+





-
+






+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







      aCmd[nCmd++] = aCommand[i].zName;
    }
    multi_column_list(aCmd, nCmd);
  }
}

/*
** Documentation on universal command-line options.
*/
** TOPIC: options
**
/* @-comment: # */
static const char zOptions[] =
@ Command-line options common to all commands:
** Command-line options common to all commands:
@
@   --args FILENAME         Read additional arguments and options from FILENAME
@   --case-sensitive BOOL   Set case sensitivity for file names
@   --cgitrace              Active CGI tracing
@   --chdir PATH            Change to PATH before performing any operations
**
**   --args FILENAME         Read additional arguments and options from FILENAME
**   --case-sensitive BOOL   Set case sensitivity for file names
**   --cgitrace              Active CGI tracing
**   --chdir PATH            Change to PATH before performing any operations
@   --comfmtflags VALUE     Set comment formatting flags to VALUE
@   --comment-format VALUE  Alias for --comfmtflags
@   --errorlog FILENAME     Log errors to FILENAME
@   --help                  Show help on the command rather than running it
@   --httptrace             Trace outbound HTTP requests
@   --localtime             Display times using the local timezone
@   --nocgi                 Do not act as CGI
@   --no-th-hook            Do not run TH1 hooks
@   --quiet                 Reduce the amount of output
@   --sqlstats              Show SQL usage statistics when done
@   --sqltrace              Trace all SQL commands
@   --sshtrace              Trace SSH activity
@   --ssl-identity NAME     Set the SSL identity to NAME
@   --systemtrace           Trace calls to system()
@   -U|--user USER          Make the default user be USER
@   --utc                   Display times using UTC
@   --vfs NAME              Cause SQLite to use the NAME VFS
**   --errorlog FILENAME     Log errors to FILENAME
**   -?|--help               Show help on the command rather than running it
**   --httptrace             Trace outbound HTTP requests
**   --localtime             Display times using the local timezone
**   --nocgi                 Do not act as CGI
**   --no-th-hook            Do not run TH1 hooks
**   -q|--quiet              Reduce the amount of output
**   --sqlstats              Show SQL usage statistics when done
**   --sqltrace              Trace all SQL commands
**   --sshtrace              Trace SSH activity
**   --ssl-identity NAME     Set the SSL identity to NAME
**   --systemtrace           Trace calls to system()
**   -U|--user USER          Make the default user be USER
**   --utc                   Display times using UTC
**   --vfs NAME              Cause SQLite to use the NAME VFS
;
**
** Additional options available on most commands that use network I/O:
**
**   --accept-any-cert       Disable server SSL cdert validation. Accept any SSL
**                           cert that the server provides.  WARNING: Unsafe!
**                           Testing and debugging use only!
**   --ipv4                  Use only IPv4.  Disable IPv6 support.
**   --ipv6                  Use only IPv6.  Disable IPv4 support.
**   --nosync                Disable autosync for the current command.
**   --proxy URL             Specify the HTTP proxy to use.  URL can be "off".
*/

/*
** COMMAND: help
**
** Usage: %fossil help [OPTIONS] [TOPIC]
** Usage: %fossil help [OPTIONS] [TOPIC] [SUBCOMMAND]
**
** Display information on how to use TOPIC, which may be a command, webpage, or
** setting.  Webpage names begin with "/".  If TOPIC is omitted, a list of
** topics is returned.
** topics is returned.  If there is an extra argument after TOPIC, it is
** the name of a subcommand, in which case only the help text for that one
** subcommand is shown.
**
** The following options can be used when TOPIC is omitted:
**
**    -a|--all          List both common and auxiliary commands
**    -e|--everything   List all help on all topics
**    -f|--full         List full set of commands (including auxiliary
**                      and unsupported "test" commands), options,
**                      settings, and web pages
**    -o|--options      List command-line options common to all commands
**    -s|--setting      List setting names
**    -t|--test         List unsupported "test" commands
**    -v|--verbose      List both names and help text
**    -x|--aux          List only auxiliary commands
**    -w|--www          List all web pages
**    -f|--full         List full set of commands (including auxiliary
**                      and unsupported "test" commands), options,
**                      settings, and web pages
**    -e|--everything   List all help on all topics
**
** These options can be used when TOPIC is present:
**
**    -c|--commands     Restrict TOPIC search to commands
**    -h|--html         Format output as HTML rather than plain text
**    -c|--commands     Restrict TOPIC search to commands
**    -o|--options      Show command-line options for TOPIC
**    --raw             Output raw, unformatted help text
**    -u|--usage        Show a succinct usage summary, not full help text
**
** See also:  [[usage]], [[options]], [[search]] with the -h option
*/
void help_cmd(void){
  int rc;
  int mask = CMDFLAG_ANY;
  int isPage = 0;
  int verboseFlag = 0;
  int commandsFlag = 0;
  const char *z;
  const char *zCmdOrPage;
  const CmdOrPage *pCmd = 0;
  int useHtml = 0;
  const char *zTopic;
  Blob txt;
  int mask = CMDFLAG_ANY;        /* Mask of help topic types */
  int isPage = 0;                /* True if TOPIC is a page */
  int verboseFlag = 0;           /* -v option */
  int commandsFlag = 0;          /* -c option */
  const char *z;                 /* Original, untranslated help text */
  const char *zCmdOrPage;        /* "command" or "page" or "setting" */
  const CmdOrPage *pCmd = 0;     /* ptr to aCommand[] entry for TOPIC */
  int useHtml = 0;               /* -h option */
  int bUsage;                    /* --usage */
  int bRaw;                      /* --raw option */
  int bOptions;                  /* --options */
  const char *zTopic;            /* TOPIC argument */
  const char *zSubtopic = 0;     /* SUBTOPIC argument */
  Blob subtext1, subtext2, s3;   /* Subsets of z[] containing subtopic/usage */
  Blob txt;                      /* Text after rendering */
  int bAbbrevSubcmd = 0;         /* Help text uses abbreviated subcommands */

  verboseFlag = find_option("verbose","v",0)!=0;
  commandsFlag = find_option("commands","c",0)!=0;
  useHtml = find_option("html","h",0)!=0;
  bRaw = find_option("raw",0,0)!=0;
  if( find_option("options","o",0) ){
    fossil_print("%s", zOptions);
  bOptions = find_option("options","o",0)!=0;
  bUsage = find_option("usage","u",0)!=0;
    return;
  }
  else if( find_option("all","a",0) ){
  if( find_option("all","a",0) ){
    command_list(CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER, verboseFlag, useHtml);
    return;
  }
  else if( find_option("www","w",0) ){
    command_list(CMDFLAG_WEBPAGE, verboseFlag, useHtml);
    return;
  }
  else if( find_option("aux","x",0) ){
    command_list(CMDFLAG_2ND_TIER, verboseFlag, useHtml);
    return;
  }
  else if( find_option("test","t",0) ){
    command_list(CMDFLAG_TEST, verboseFlag, useHtml);
    return;
  }
  else if( find_option("setting","s",0) ){
    command_list(CMDFLAG_SETTING, verboseFlag, useHtml);
    return;
  }
  else if( find_option("topic","c",0) ){
    command_list(CMDFLAG_TOPIC, verboseFlag, useHtml);
    return;
  }
  else if( find_option("full","f",0) ){
    fossil_print("fossil commands:\n\n");
    command_list(CMDFLAG_1ST_TIER, verboseFlag, useHtml);
    fossil_print("\nfossil auxiliary commands:\n\n");
    command_list(CMDFLAG_2ND_TIER, verboseFlag, useHtml);
    fossil_print("\n%s", zOptions);
    fossil_print("\nfossil settings:\n\n");
    command_list(CMDFLAG_SETTING, verboseFlag, useHtml);
    fossil_print("\nfossil web pages:\n\n");
    command_list(CMDFLAG_WEBPAGE, verboseFlag, useHtml);
    fossil_print("\nfossil miscellaneous help topics:\n\n");
    command_list(CMDFLAG_TOPIC, verboseFlag, useHtml);
    fossil_print("\nfossil test commands (unsupported):\n\n");
    command_list(CMDFLAG_TEST, verboseFlag, useHtml);
    if ( !verboseFlag ) {
    fossil_print("\n");
    version_cmd();
      fossil_print("\n");
      version_cmd();
    }
    return;
  }
  else if( find_option("everything","e",0) ){
    display_all_help(CMDFLAG_1ST_TIER | CMDFLAG_2ND_TIER | CMDFLAG_WEBPAGE |
                     CMDFLAG_SETTING | CMDFLAG_TEST, useHtml, 0);
                     CMDFLAG_SETTING | CMDFLAG_TEST | CMDFLAG_TOPIC,
                     useHtml, 0);
    return;
  }
  verify_all_options();
  zCmdOrPage = "help topic";
  if( g.argc<3 ){
    if( bOptions ){
      zTopic = "options";
      zSubtopic = 0;
      mask = CMDFLAG_TOPIC;
      bOptions = 0;
      goto find_and_show_help;
    }
    z = g.argv[0];
    fossil_print(
      "Usage: %s help TOPIC\n"
      "Things to try:\n\n"
      "Try \"%s help help\" or \"%s help -a\" for more options\n"
      "Frequently used commands:\n",
      z, z, z);
      "   %s help help\n"
      "   %s help -o\n"
      "   %s help -a\n"
      "   %s search -h TOPIC\n\n"
      "Other common values for TOPIC:\n\n",
      z, z, z, z, z);
    command_list(CMDFLAG_1ST_TIER,verboseFlag,useHtml);
    if( !verboseFlag ) version_cmd();
    return;
  }
  zTopic = g.argv[2];
  zSubtopic = g.argc>=4 ? g.argv[3] : 0;
  isPage = ('/' == zTopic[0]) ? 1 : 0;
  if(isPage){
  if( isPage ){
    zCmdOrPage = "page";
  }else if( commandsFlag ){
    mask = CMDFLAG_COMMAND;
    zCmdOrPage = "command";
  }else{
    zCmdOrPage = "command or setting";
  }
find_and_show_help:
  rc = dispatch_name_search(g.argv[2], mask|CMDFLAG_PREFIX, &pCmd);
  rc = dispatch_name_search(zTopic, mask|CMDFLAG_PREFIX, &pCmd);
  if( rc ){
    int i, n;
    const char *az[5];
    if( rc==1 ){
      if( help_is_platform_command(g.argv[2]) ){
        fossil_print("Not available in this build: %s\n", g.argv[2]);
        return;
      }
      fossil_print("unknown %s: %s\n", zCmdOrPage, g.argv[2]);
    }else{
      fossil_print("ambiguous %s prefix: %s\n",
                 zCmdOrPage, g.argv[2]);
    }
    fossil_print("Did you mean one of these TOPICs:\n");
    n = dispatch_approx_match(g.argv[2], 5, az);
    for(i=0; i<n; i++){
      fossil_print("  *  %s\n", az[i]);
    }
    fossil_print("Also consider using:\n");
    fossil_print("   fossil help TOPIC     ;# show help on TOPIC\n");
    fossil_print("   fossil help -a        ;# show all commands\n");
    fossil_print("   fossil help -w        ;# show all web-pages\n");
    fossil_print("   fossil help -s        ;# show all settings\n");
    fossil_print("   fossil help -o        ;# show global options\n");
    fossil_exit(1);
    fossil_print("Other commands to try:\n");
    fossil_print("   fossil search -h PATTERN  ;# search all help text\n");
    fossil_print("   fossil help -a            ;# show all commands\n");
    fossil_print("   fossil help -w            ;# show all web-pages\n");
    fossil_print("   fossil help -s            ;# show all settings\n");
    fossil_print("   fossil help -o            ;# show global options\n");
    return;
  }
  bAbbrevSubcmd = (pCmd->eCmdFlags & CMDFLAG_ABBREVSUBCMD)!=0;
  z = pCmd->zHelp;
  if( z==0 ){
    fossil_fatal("no help available for the %s %s",
                 pCmd->zName, zCmdOrPage);
  }
  blob_init(&subtext1, 0, 0);
  blob_init(&subtext2, 0, 0);
  blob_init(&s3, 0, 0);
  if( zSubtopic!=0 ){
    if( simplify_to_subtopic(z, &subtext1, zTopic, zSubtopic, bAbbrevSubcmd) ){
      z = blob_str(&subtext1);
    }else{
      fossil_print("No subtopic \"%s\" for \"%s\".\n", zSubtopic, zTopic);
      bUsage = 1;
      zSubtopic = 0;
    }
  }
  if( bUsage ){
    if( simplify_to_usage(z, &subtext2, zTopic, bAbbrevSubcmd) ){
      z = blob_str(&subtext2);
    }else{
      bUsage = 0;
    }
  }
  if( bOptions ){
    simplify_to_options(z, &s3, bAbbrevSubcmd, zTopic);
    z = blob_str(&s3);
  }
  if( pCmd->eCmdFlags & CMDFLAG_SETTING ){
  if( pCmd && pCmd->eCmdFlags & CMDFLAG_SETTING ){
    const Setting *pSetting = db_find_setting(pCmd->zName, 0);
    char *zDflt = 0;
    if( pSetting!=0 && pSetting->def!=0 && *pSetting->def!=0 ){
      zDflt = mprintf(" (default: %s)", pSetting->def);
    }
    fossil_print("Setting: \"%s\"%s%s\n\n",
         pCmd->zName, zDflt!=0 ? zDflt : "",
         (pCmd->eCmdFlags & CMDFLAG_VERSIONABLE)!=0 ? " (versionable)" : ""
    );
    fossil_free(zDflt);
  }
  blob_init(&txt, 0, 0);
  if( bRaw ){
    blob_append(&txt, z, -1);
  if( useHtml ){
  }else if( useHtml ){
    help_to_html(z, &txt);
  }else{
    help_to_text(z, &txt);
    help_to_text(z, &txt, bUsage || zSubtopic!=0);
  }
  fossil_print("%s\n", blob_str(&txt));
  if( blob_strlen(&txt)>0 ) fossil_print("%s\n", blob_str(&txt));
  blob_reset(&txt);
  blob_reset(&subtext1);
  blob_reset(&subtext2);
}

/*
** Return a pointer to the setting information array.
**
** This routine provides access to the aSetting2[] array which is created
** This routine provides access to the aSetting[] array which is created
** by the mkindex utility program and included with <page_index.h>.
*/
const Setting *setting_info(int *pnCount){
  if( pnCount ) *pnCount = (int)(sizeof(aSetting)/sizeof(aSetting[0])) - 1;
  return aSetting;
}

/*
** Return a pointer to a specific Setting entry for the setting named
** in the argument.  Or return NULL if no such setting exists.
**
** The pointer returned points into the middle of the global aSetting[]
** array that is generated by mkindex.  Use setting_info() to fetch the
** whole array.  Use this routine to fetch a specific entry.
*/
const Setting *setting_find(const char *zName){
  int iFirst = 0;
  int iLast = ArraySize(aSetting)-1;
  while( iFirst<=iLast ){
    int iCur = (iFirst+iLast)/2;
    int c = strcmp(aSetting[iCur].name, zName);
    if( c<0 ){
      iFirst = iCur+1;
    }else if( c>0 ){
      iLast = iCur-1;
    }else{
      return &aSetting[iCur];
    }
  }
  return 0;
}

/*****************************************************************************
** A virtual table for accessing the information in aCommand[], and
** especially the help-text
*/

/* helptextVtab_vtab is a subclass of sqlite3_vtab which is
1437
1438
1439
1440
1441
1442
1443


1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457

1458
1459
1460
1461
1462
1463
1464
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864

1865
1866
1867
1868
1869
1870
1871
1872







+
+













-
+







      const char *zType = 0;
      if( pPage->eCmdFlags & CMDFLAG_COMMAND ){
        zType = "command";
      }else if( pPage->eCmdFlags & CMDFLAG_WEBPAGE ){
        zType = "webpage";
      }else if( pPage->eCmdFlags & CMDFLAG_SETTING ){
        zType = "setting";
      }else if( pPage->eCmdFlags & CMDFLAG_TOPIC ){
        zType = "help-topic";
      }
      sqlite3_result_text(ctx, zType, -1, SQLITE_STATIC);
      break;
    }
    case 2:  /* flags */
      sqlite3_result_int(ctx, pPage->eCmdFlags);
      break;
    case 3:  /* helptext */
      sqlite3_result_text(ctx, pPage->zHelp, -1, SQLITE_STATIC);
      break;
    case 4: { /* formatted */
      Blob txt;
      blob_init(&txt, 0, 0);
      help_to_text(pPage->zHelp, &txt);
      help_to_text(pPage->zHelp, &txt, 0);
      sqlite3_result_text(ctx, blob_str(&txt), -1, fossil_free);
      break;
    }
    case 5: { /* formatted */
      Blob txt;
      blob_init(&txt, 0, 0);
      help_to_html(pPage->zHelp, &txt);
Changes to src/doc.c.
518
519
520
521
522
523
524
525

526
527
528

529
530
531
532
533
534

535
536
537
538
539
540
541
518
519
520
521
522
523
524

525
526
527

528
529
530
531
532
533

534
535
536
537
538
539
540
541







-
+


-
+





-
+







void mimetype_list_page(void){
  int i;
  char *zCustomList = 0;    /* value of the mimetypes setting */
  int nCustomEntries = 0;   /* number of entries in the mimetypes
                            ** setting */
  mimetype_verify();
  style_header("Mimetype List");
  @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename
  @ <p>The Fossil <a href="%R/help/www/doc">/doc</a> page uses filename
  @ suffixes and the following tables to guess at the appropriate mimetype
  @ for each document. Mimetypes may be customized and overridden using
  @ <a href="%R/help?cmd=mimetypes">the mimetypes config setting</a>.</p>
  @ <a href="%R/help/mimetypes">the mimetypes config setting</a>.</p>
  zCustomList = db_get("mimetypes",0);
  if( zCustomList!=0 ){
    Blob list, entry, key, val;
    @ <h1>Repository-specific mimetypes</h1>
    @ <p>The following extension-to-mimetype mappings are defined via
    @ the <a href="%R/help?cmd=mimetypes">mimetypes setting</a>.</p>
    @ the <a href="%R/help/mimetypes">mimetypes setting</a>.</p>
    @ <table class='sortable mimetypetable' border=1 cellpadding=0 \
    @ data-column-types='tt' data-init-sort='0'>
    @ <thead>
    @ <tr><th>Suffix<th>Mimetype
    @ </thead>
    @ <tbody>
    blob_set(&list, zCustomList);
637
638
639
640
641
642
643
644

645
646
647
648
649
650
651
637
638
639
640
641
642
643

644
645
646
647
648
649
650
651







-
+







    if( nAttr==10 && fossil_strnicmp(zAttr,"data-title",10)==0 ){
      /* The text argument to data-title="" will have had any characters that
      ** are special to HTML encoded.  We need to decode these before turning
      ** the text into a title, as the title text will be reencoded later */
      char *zTitle = mprintf("%.*s", nValue, zValue);
      int i;
      for(i=0; fossil_isspace(zTitle[i]); i++){}
      html_to_plaintext(zTitle+i, pTitle);
      html_to_plaintext(zTitle+i, pTitle, 0);
      fossil_free(zTitle);
      seenTitle = 1;
      if( seenClass ) return 1;
    }
  }
  return seenClass;
}
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
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







-
+












+





+










+







  const char *zDefaultTitle,    /* Default title */
  const char *zFilename         /* Name of the file being rendered */
){
  Blob title;
  int isPopup = P("popup")!=0;
  blob_init(&title,0,0);
  if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){
    Blob tail;
    Blob tail = BLOB_INITIALIZER;
    style_adunit_config(ADUNIT_RIGHT_OK);
    if( wiki_find_title(pBody, &title, &tail) ){
      if( !isPopup ) style_header("%s", blob_str(&title));
      wiki_convert(&tail, 0, WIKI_BUTTONS);
    }else{
      if( !isPopup ) style_header("%s", zDefaultTitle);
      wiki_convert(pBody, 0, WIKI_BUTTONS);
    }
    if( !isPopup ){
      document_emit_js();
      style_finish_page();
    }
    blob_reset(&tail);
  }else if( fossil_strcmp(zMime, "text/x-markdown")==0 ){
    Blob tail = BLOB_INITIALIZER;
    markdown_to_html(pBody, &title, &tail);
    if( !isPopup ){
      if( blob_size(&title)>0 ){
        markdown_dehtmlize_blob(&title);
        style_header("%s", blob_str(&title));
      }else{
        style_header("%s", zDefaultTitle);
      }
    }
    convert_href_and_output(&tail);
    if( !isPopup ){
      document_emit_js();
      style_finish_page();
    }
    blob_reset(&tail);
  }else if( fossil_strcmp(zMime, "text/plain")==0 ){
    style_header("%s", zDefaultTitle);
    @ <blockquote><pre>
    @ %h(blob_str(pBody))
    @ </pre></blockquote>
    document_emit_js();
    style_finish_page();
947
948
949
950
951
952
953

954
955
956
957
958
959
960
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964







+







#endif
  };

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  style_set_current_feature("doc");
  blob_init(&title, 0, 0);
  blob_init(&filebody, 0, 0);
  zDfltTitle = isUV ? "" : "Documentation";
  db_begin_transaction();
  while( rid==0 && (++nMiss)<=count(azSuffix) ){
    zName = P("name");
    if( isUV ){
      if( zName==0 ) zName = "index.wiki";
      i = 0;
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
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







-
+










+
+
















+
+







  /* The file is now contained in the filebody blob.  Deliver the
  ** file to the user
  */
  zMime = nMiss==0 ? P("mimetype") : 0;
  if( zMime==0 ){
    zMime = mimetype_from_name(zName);
  }
  Th_Store("doc_name", zName);
  Th_StoreUnsafe("doc_name", zName);
  if( vid ){
    Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
                                       "  FROM blob WHERE rid=%d", vid));
    Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event"
                                    " WHERE objid=%d AND type='ci'", vid));
  }
  cgi_check_for_malice();
  document_render(&filebody, zMime, zDfltTitle, zName);
  if( nMiss>=count(azSuffix) ) cgi_set_status(404, "Not Found");
  db_end_transaction(0);
  blob_reset(&title);
  blob_reset(&filebody);
  return;

  /* Jump here when unable to locate the document */
doc_not_found:
  db_end_transaction(0);
  if( isUV && P("name")==0 ){
    uvlist_page();
    return;
  }
  cgi_set_status(404, "Not Found");
  style_header("Not Found");
  @ <p>Document %h(zOrigName) not found
  if( fossil_strcmp(zCheckin,"ckout")!=0 ){
    @ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a>
  }
  style_finish_page();
  blob_reset(&title);
  blob_reset(&filebody);
  return;
}

/*
** The default logo.
*/
static const unsigned char aLogo[] = {
Changes to src/encode.c.
140
141
142
143
144
145
146








































































147
148
149
150
151
152
153
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







        j = i+1;
        break;
    }
  }
  if( j<i ) blob_append(p, zIn+j, i-j);
}

/*
** Make the given string safe for HTML by converting syntax characters
** into alternatives that do not have the special syntactic meaning.
**
**     <     -->     U+227a
**     >     -->     U+227b
**     &     -->     +
**     "     -->     U+201d
**     '     -->     U+2019
**
** Return a pointer to a new string obtained from fossil_malloc().
*/
char *html_lookalike(const char *z, int n){
  unsigned char c;
  int i = 0;
  int count = 0;
  unsigned char *zOut;
  const unsigned char *zIn = (const unsigned char*)z;

  if( n<0 ) n = strlen(z);
  while( i<n ){
    switch( zIn[i] ){
      case '<':   count += 3;       break;
      case '>':   count += 3;       break;
      case '"':   count += 3;       break;
      case '\'':  count += 3;       break;
      case 0:     n = i;            break;
    }
    i++;
  }
  i = 0;
  zOut = fossil_malloc( count+n+1 );
  if( count==0 ){
    memcpy(zOut, zIn, n);
    zOut[n] = 0;
    return (char*)zOut;
  }
  while( n-->0 ){
    c = *(zIn++);
    switch( c ){
      case '<':
        zOut[i++] = 0xe2;
        zOut[i++] = 0x89;
        zOut[i++] = 0xba;
        break;
      case '>':
        zOut[i++] = 0xe2;
        zOut[i++] = 0x89;
        zOut[i++] = 0xbb;
        break;
      case '&':
        zOut[i++] = '+';
        break;
      case '"':
        zOut[i++] = 0xe2;
        zOut[i++] = 0x80;
        zOut[i++] = 0x9d;
        break;
      case '\'':
        zOut[i++] = 0xe2;
        zOut[i++] = 0x80;
        zOut[i++] = 0x99;
        break;
      default:
        zOut[i++] = c;
        break;
    }
  }
  zOut[i] = 0;
  return (char*)zOut;
}


/*
** Encode a string for HTTP.  This means converting lots of
** characters into the "%HH" where H is a hex digit.  It also
** means converting spaces to "+".
**
** This is the opposite of DeHttpizeString below.
242
243
244
245
246
247
248
249

250
251
252
253
254
255
256
314
315
316
317
318
319
320

321
322
323
324
325
326
327
328







-
+







  *zOut = 0;
  return zRet;
}

/*
** Convert a single HEX digit to an integer
*/
static int AsciiToHex(int c){
int fossil_hexvalue(int c){
  if( c>='a' && c<='f' ){
    c += 10 - 'a';
  }else if( c>='A' && c<='F' ){
    c += 10 - 'A';
  }else if( c>='0' && c<='9' ){
    c -= '0';
  }else{
270
271
272
273
274
275
276
277
278


279
280
281
282
283
284
285
342
343
344
345
346
347
348


349
350
351
352
353
354
355
356
357







-
-
+
+







  if( !z ) return 0;

  i = j = 0;
  while( z[i] ){
    switch( z[i] ){
      case '%':
        if( z[i+1] && z[i+2] ){
          z[j] = AsciiToHex(z[i+1]) << 4;
          z[j] |= AsciiToHex(z[i+2]);
          z[j] = fossil_hexvalue(z[i+1]) << 4;
          z[j] |= fossil_hexvalue(z[i+2]);
          i += 2;
        }
        break;
      case '+':
        z[j] = ' ';
        break;
      default:
727
728
729
730
731
732
733


















734
735
736
737
738
739
740
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







*/
void canonical16(char *z, int n){
  while( *z && n-- ){
    *z = zEncode[zDecode[(*z)&0x7f]&0x1f];
    z++;
  }
}

/*
** Decode hexadecimal into a string and return the new string.  Space to
** hold the string is obtained from fossil_malloc() and should be released
** by the caller.
**
** If the input is not hex, return NULL.
*/
char *decode16_dup(const char *zIn){
  int nIn = (int)strlen(zIn);
  char *zOut;
  if( !validate16(zIn, nIn) ) return 0;
  zOut = fossil_malloc(nIn/2+1);
  decode16((const u8*)zIn, (u8*)zOut, nIn);
  zOut[nIn/2] = 0;
  return zOut;
}


/*
** Decode a string encoded using "quoted-printable".
**
**   (1)  "=" followed by two hex digits becomes a single
**        byte specified by the two digits
**
Changes to src/etag.c.
92
93
94
95
96
97
98


99
100
101
102
103
104
105
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107







+
+








/*
** Generate an ETag
*/
void etag_check(unsigned eFlags, const char *zHash){
  const char *zIfNoneMatch;
  char zBuf[50];
  const int cchETag = 32; /* Not including NULL terminator. */
  int cch;                /* Length of zIfNoneMatch header. */
  assert( zETag[0]==0 );  /* Only call this routine once! */

  if( etagCancelled ) return;

  /* By default, ETagged URLs never expire since the ETag will change
   * when the content changes.  Approximate this policy as 10 years. */
  iMaxAge = 10 * 365 * 24 * 60 * 60;
155
156
157
158
159
160
161
162

163
164
165


166
167




168


169
170
171
172
173
174
175
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







-
+


-
+
+


+
+
+
+
-
+
+







      md5sum_step_text("login: ", -1);
      md5sum_step_text(g.zLogin, -1);
      md5sum_step_text("\n", 1);
    }
  }

  /* Generate the ETag */
  memcpy(zETag, md5sum_finish(0), 33);
  memcpy(zETag, md5sum_finish(0), cchETag+1);

  /* Check to see if the generated ETag matches If-None-Match and
  ** generate a 304 reply if it does. */
  ** generate a 304 reply if it does.  Test both with and without
  ** double quotes. */
  zIfNoneMatch = P("HTTP_IF_NONE_MATCH");
  if( zIfNoneMatch==0 ) return;
  cch = strlen(zIfNoneMatch);
  if( cch==cchETag+2 && zIfNoneMatch[0]=='"' && zIfNoneMatch[cch-1]=='"' ){
    if( memcmp(&zIfNoneMatch[1],zETag,cchETag)!=0 ) return;
  }else{
  if( strcmp(zIfNoneMatch,zETag)!=0 ) return;
    if( strcmp(zIfNoneMatch,zETag)!=0 ) return;
  }

  /* If we get this far, it means that the content has
  ** not changed and we can do a 304 reply */
  cgi_reset_content();
  cgi_set_status(304, "Not Modified");
  cgi_reply();
  db_close(0);
Changes to src/event.c.
227
228
229
230
231
232
233
234

235
236
237
238
239
240
241
227
228
229
230
231
232
233

234
235
236
237
238
239
240
241







-
+







    @ %h(blob_str(&fullbody))
    @ </pre>
  }
  zFullId = db_text(0, "SELECT SUBSTR(tagname,7)"
                       "  FROM tag"
                       " WHERE tagname GLOB 'event-%q*'",
                    zId);
  attachment_list(zFullId, "<hr><h2>Attachments:</h2><ul>");
  attachment_list(zFullId, "<h2>Attachments:</h2>", 1);
  document_emit_js();
  style_finish_page();
  manifest_destroy(pTNote);
}

/*
** Add or update a new tech note to the repository.  rid is id of
380
381
382
383
384
385
386
387

388
389
390
391
392
393
394
380
381
382
383
384
385
386

387
388
389
390
391
392
393
394







-
+







  const char *zTags = P("g");              /* Tags added to this technote */
  const char *zClrFlag = "";               /* "checked" for bg color */
  const char *zClr;                        /* Name of the background color */
  const char *zMimetype = P("mimetype");   /* Mimetype of zBody */
  int isNew = 0;

  if( zBody ){
    zBody = mprintf("%s", zBody);
    zBody = fossil_strdup(zBody);
  }
  login_check_credentials();
  zId = P("name");
  if( zId==0 ){
    zId = db_text(0, "SELECT lower(hex(randomblob(20)))");
    isNew = 1;
  }else{
423
424
425
426
427
428
429
430

431
432
433
434
435
436
437
423
424
425
426
427
428
429

430
431
432
433
434
435
436
437







-
+








  /* Figure out the color */
  if( rid ){
    zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
    if( zClr && zClr[0] ){
      const char * zRequestMethod = P("REQUEST_METHOD");
      if(zRequestMethod && 'G'==zRequestMethod[0]){
        /* Apply saved color by defaut for GET requests
        /* Apply saved color by default for GET requests
        ** (e.g., an Edit menu link).
        */
        zClrFlag = " checked";
      }
    }
  }else{
    zClr = "";
Changes to src/export.c.
26
27
28
29
30
31
32
33

34
35
36
37
38
39
40
26
27
28
29
30
31
32

33
34
35
36
37
38
39
40







-
+







*/
static struct {
  const char *zTrunkName;     /* Name of trunk branch */
} gexport;

#if INTERFACE
/*
** Each line in a git-fast-export "marK" file is an instance of
** Each line in a git-fast-export "mark" file is an instance of
** this object.
*/
struct mark_t {
  char *name;       /* Name of the mark.  Also starts with ":" */
  int rid;          /* Corresponding object in the BLOB table */
  char uuid[65];    /* The GIT hash name for this object */
};
56
57
58
59
60
61
62
63

64
65
66
67
68
69
70
56
57
58
59
60
61
62

63
64
65
66
67
68
69
70







-
+







    printf(" <unknown>");
    return;
  }
  db_static_prepare(&q, "SELECT info FROM user WHERE login=:user");
  db_bind_text(&q, ":user", zUser);
  if( db_step(&q)!=SQLITE_ROW ){
    db_reset(&q);
    zName = mprintf("%s", zUser);
    zName = fossil_strdup(zUser);
    for(i=j=0; zName[i]; i++){
      if( zName[i]!='<' && zName[i]!='>' && zName[i]!='"' ){
        zName[j++] = zName[i];
      }
    }
    zName[j] = 0;
    printf(" %s <%s>", zName, zName);
100
101
102
103
104
105
106
107

108
109
110
111
112
113
114
100
101
102
103
104
105
106

107
108
109
110
111
112
113
114







-
+







     }
     else if( zContact[i]==' ' && !isBracketed ){
        atEmailFirst = i+1;
     }
  }
  if( zContact[i]==0 ){
    /* No email address found. Take as user info if not empty */
    zName = mprintf("%s", zContact[0] ? zContact : zUser);
    zName = fossil_strdup(zContact[0] ? zContact : zUser);
    for(i=j=0; zName[i]; i++){
      if( zName[i]!='<' && zName[i]!='>' && zName[i]!='"' ){
        zName[j++] = zName[i];
      }
    }
    zName[j] = 0;

147
148
149
150
151
152
153
154

155
156
157
158
159
160
161
147
148
149
150
151
152
153

154
155
156
157
158
159
160
161







-
+







     for(i=atEmailFirst-2; i>=0 && zContact[i] && zContact[i]==' '; i--){}
     if( i>=0 ){
         for(j=0; j<i && zContact[j] && zContact[j]==' '; j++){}
         zName = mprintf("%.*s", i-j+1, &zContact[j]);
     }
  }

  if( zName==NULL ) zName = mprintf("%s", zUser);
  if( zName==NULL ) zName = fossil_strdup(zUser);
  for(i=j=0; zName[i]; i++){
     if( zName[i]!='<' && zName[i]!='>' && zName[i]!='"' ){
         zName[j++] = zName[i];
     }
  }
  zName[j] = 0;

170
171
172
173
174
175
176
177

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

177
178
179
180
181
182
183
184







-
+







/*
** Output a sanitized git named reference.
** https://git-scm.com/docs/git-check-ref-format
** This implementation assumes we are only printing
** the branch or tag part of the reference.
*/
static void print_ref(const char *zRef){
  char *zEncoded = mprintf("%s", zRef);
  char *zEncoded = fossil_strdup(zRef);
  int i, w;
  if (zEncoded[0]=='@' && zEncoded[1]=='\0'){
    putchar(REFREPLACEMENT);
    return;
  }
  for(i=0, w=0; zEncoded[i]; i++, w++){
    if( i!=0 ){ /* Two letter tests */
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
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







+
+









+









-
+







**   --rename-trunk NAME          Use NAME as name of exported trunk branch
**   -R|--repository REPO         Export the given REPOSITORY
**
** See also: import
*/
/*
** COMMAND: export*
**
** Usage: %fossil export --git [REPOSITORY]
**
** This command is deprecated.  Use "fossil git export" instead.
*/
void export_cmd(void){
  Stmt q, q2, q3;
  Bag blobs, vers;
  unsigned int unused_mark = 1;
  const char *markfile_in;
  const char *markfile_out;
  const char *zMainBranch = db_main_branch();

  bag_init(&blobs);
  bag_init(&vers);

  find_option("git", 0, 0);   /* Ignore the --git option for now */
  markfile_in = find_option("import-marks", 0, 1);
  markfile_out = find_option("export-marks", 0, 1);

  if( !(gexport.zTrunkName = find_option("rename-trunk", 0, 1)) ){
    gexport.zTrunkName = "trunk";
    gexport.zTrunkName = fossil_strdup(zMainBranch);
  }

  db_find_and_open_repository(0, 2);
  verify_all_options();
  if( g.argc!=2 && g.argc!=3 ){ usage("--git ?REPOSITORY?"); }

  db_multi_exec("CREATE TEMPORARY TABLE oldblob(rid INTEGER PRIMARY KEY)");
625
626
627
628
629
630
631
632

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

635
636
637
638
639
640
641
642







-
+







    const char *zBranch = db_column_text(&q, 4);
    char *zMark;

    bag_insert(&vers, ckinId);
    db_bind_int(&q2, ":rid", ckinId);
    db_step(&q2);
    db_reset(&q2);
    if( zBranch==0 || fossil_strcmp(zBranch, "trunk")==0 ){
    if( zBranch==0 || fossil_strcmp(zBranch, zMainBranch)==0 ){
      zBranch = gexport.zTrunkName;
    }
    zMark = mark_name_from_rid(ckinId, &unused_mark);
    printf("commit refs/heads/");
    print_ref(zBranch);
    printf("\nmark %s\n", zMark);
    free(zMark);
754
755
756
757
758
759
760
761

762
763
764
765
766
767
768
757
758
759
760
761
762
763

764
765
766
767
768
769
770
771







-
+







**        tseq INT                   -- integer total order on check-ins.
**     );
**
** This table contains all check-ins of the repository in topological
** order.  "Topological order" means that every parent check-in comes
** before all of its children.  Topological order is *almost* the same
** thing as "ORDER BY event.mtime".  Differences only arise when there
** are timewarps.  In as much as Git hates timewarps, we have to compute
** are timewarps.  Inasmuch as Git hates timewarps, we have to compute
** a correct topological order when doing an export.
**
** Since mtime is a usually already nearly in topological order, the
** algorithm is to start with mtime, then make adjustments as necessary
** for timewarps.  This is not a great algorithm for the general case,
** but it is very fast for the overwhelmingly common case where there
** are few timewarps.
853
854
855
856
857
858
859
860
861


862
863
864
865
866
867
868
856
857
858
859
860
861
862


863
864
865
866
867
868
869
870
871







-
-
+
+







**    3     Extra details
*/
#define VERB_ERROR  1
#define VERB_NORMAL 2
#define VERB_EXTRA  3
static int gitmirror_verbosity = VERB_NORMAL;

/* The main branch in the Git repository.  The "trunk" branch of
** Fossil is renamed to be this branch name.
/* The main branch in the Git repository.  The main branch of the
** Fossil repository (usually "trunk") is renamed to be this branch name.
*/
static const char *gitmirror_mainbranch = 0;

/*
** Output routine that depends on verbosity
*/
static void gitmirror_message(int iLevel, const char *zFormat, ...){
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
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







-
+
-













+
+
+

















-
+







**
** Return zero on success and non-zero if the export should be stopped.
*/
static int gitmirror_send_checkin(
  FILE *xCmd,           /* Write fast-import text on this pipe */
  int rid,              /* BLOB.RID for the check-in to export */
  const char *zUuid,    /* BLOB.UUID for the check-in to export */
  int *pnLimit,         /* Stop when the counter reaches zero */
  int *pnLimit          /* Stop when the counter reaches zero */
  int fManifest         /* MFESTFLG_* values */
){
  Manifest *pMan;       /* The check-in to be output */
  int i;                /* Loop counter */
  int iParent;          /* Which immediate ancestor is primary.  -1 for none */
  Stmt q;               /* An SQL query */
  char *zBranch;        /* The branch of the check-in */
  char *zMark;          /* The Git-name of the check-in */
  Blob sql;             /* String of SQL for part of the query */
  Blob comment;         /* The comment text for the check-in */
  int nErr = 0;         /* Number of errors */
  int bPhantomOk;       /* True if phantom files should be ignored */
  char buf[24];
  char *zEmail;         /* Contact info for Git committer field */
  int fManifest;        /* Should the manifest files be included? */
  int fPManifest = 0;   /* OR of the manifest files for all parents */
  const char *zMainBranch;

  pMan = manifest_get(rid, CFTYPE_MANIFEST, 0);
  if( pMan==0 ){
    /* Must be a phantom.  Return without doing anything, and in particular
    ** without creating a mark for this check-in. */
    gitmirror_message(VERB_NORMAL, "missing check-in: %s\n", zUuid);
    return 0;
  }

  /* Check to see if any parent logins have not yet been processed, and
  ** if so, create them */
  for(i=0; i<pMan->nParent; i++){
    char *zPMark = gitmirror_find_mark(pMan->azParent[i], 0, 0);
    if( zPMark==0 ){
      int prid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q",
                        pMan->azParent[i]);
      int rc = gitmirror_send_checkin(xCmd, prid, pMan->azParent[i],
                                      pnLimit, fManifest);
                                      pnLimit);
      if( rc || *pnLimit<=0 ){
        manifest_destroy(pMan);
        return 1;
      }
    }
    fossil_free(zPMark);
  }
1144
1145
1146
1147
1148
1149
1150

1151

1152
1153
1154

1155
1156
1157
1158
1159
1160
1161
1149
1150
1151
1152
1153
1154
1155
1156

1157
1158
1159

1160
1161
1162
1163
1164
1165
1166
1167







+
-
+


-
+







  }

  /* Figure out which branch this check-in is a member of */
  zBranch = db_text(0,
    "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=%d",
    TAG_BRANCH, rid
  );
  zMainBranch = db_main_branch();
  if( fossil_strcmp(zBranch,"trunk")==0 ){
  if( fossil_strcmp(zBranch, zMainBranch)==0 ){
    assert( gitmirror_mainbranch!=0 );
    fossil_free(zBranch);
    zBranch = mprintf("%s",gitmirror_mainbranch);
    zBranch = fossil_strdup(gitmirror_mainbranch);
  }else if( zBranch==0 ){
    zBranch = mprintf("unknown");
  }else{
    gitmirror_sanitize_name(zBranch);
  }

  /* Export the check-in */
1211
1212
1213
1214
1215
1216
1217

1218
1219
1220
1221
1222
1223
1224
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231







+







  blob_appendf(&comment, "\n\nFossilOrigin-Name: %s", zUuid);
  fprintf(xCmd, "data %d\n%s\n", blob_strlen(&comment), blob_str(&comment));
  blob_reset(&comment);
  iParent = -1;  /* Which ancestor is the primary parent */
  for(i=0; i<pMan->nParent; i++){
    char *zOther = gitmirror_find_mark(pMan->azParent[i],0,0);
    if( zOther==0 ) continue;
    fPManifest |= db_get_manifest_setting(pMan->azParent[i]);
    if( iParent<0 ){
      iParent = i;
      fprintf(xCmd, "from %s\n", zOther);
    }else{
      fprintf(xCmd, "merge %s\n", zOther);
    }
    fossil_free(zOther);
1267
1268
1269
1270
1271
1272
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
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







+







+
+




+
+








+
+







    fossil_free(zFNQuoted);
  }
  db_finalize(&q);
  manifest_destroy(pMan);
  pMan = 0;

  /* Include Fossil-generated auxiliary files in the check-in */
  fManifest = db_get_manifest_setting(zUuid);
  if( fManifest & MFESTFLG_RAW ){
    Blob manifest;
    content_get(rid, &manifest);
    sterilize_manifest(&manifest, CFTYPE_MANIFEST);
    fprintf(xCmd,"M 100644 inline manifest\ndata %d\n%s\n",
      blob_strlen(&manifest), blob_str(&manifest));
    blob_reset(&manifest);
  }else if( fPManifest & MFESTFLG_RAW ){
    fprintf(xCmd, "D manifest\n");
  }
  if( fManifest & MFESTFLG_UUID ){
    int n = (int)strlen(zUuid);
    fprintf(xCmd,"M 100644 inline manifest.uuid\ndata %d\n%s\n\n", n+1, zUuid);
  }else if( fPManifest & MFESTFLG_UUID ){
    fprintf(xCmd, "D manifest.uuid\n");
  }
  if( fManifest & MFESTFLG_TAGS ){
    Blob tagslist;
    blob_init(&tagslist, 0, 0);
    get_checkin_taglist(rid, &tagslist);
    fprintf(xCmd,"M 100644 inline manifest.tags\ndata %d\n%s\n",
      blob_strlen(&tagslist), blob_str(&tagslist));
    blob_reset(&tagslist);
  }else if( fPManifest & MFESTFLG_TAGS ){
    fprintf(xCmd, "D manifest.tags\n");
  }

  /* The check-in is finished, so decrement the counter */
  (*pnLimit)--;
  return 0;
}

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
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
1423
1424
1425







-


















+







  const char *zAutoPush = 0;      /* Value of the --autopush flag */
  char *zMainBr = 0;              /* Value of the --mainbranch flag */
  char *zPushUrl;                 /* URL to sync the mirror to */
  double rEnd;                    /* time of most recent export */
  int rc;                         /* Result code */
  int bForce;                     /* Do the export and sync even if no changes*/
  int bNeedRepack = 0;            /* True if we should run repack at the end */
  int fManifest;                  /* Current "manifest" setting */
  int bIfExists;                  /* The --if-mirrored flag */
  FILE *xCmd;                     /* Pipe to the "git fast-import" command */
  FILE *pMarks;                   /* Git mark files */
  Stmt q;                         /* Queries */
  char zLine[200];                /* One line of a mark file */

  zDebug = find_option("debug",0,1);
  db_find_and_open_repository(0, 0);
  zLimit = find_option("limit", 0, 1);
  if( zLimit ){
    nLimit = (unsigned int)atoi(zLimit);
    if( nLimit<=0 ) fossil_fatal("--limit must be positive");
  }
  zAutoPush = find_option("autopush",0,1);
  zMainBr = (char*)find_option("mainbranch",0,1);
  bForce = find_option("force","f",0)!=0;
  bIfExists = find_option("if-mirrored",0,0)!=0;
  gitmirror_verbosity = VERB_NORMAL;
  if( g.fQuiet ){ gitmirror_verbosity--; }  /* Global option not repeatable. */
  while( find_option("quiet","q",0)!=0 ){ gitmirror_verbosity--; }
  while( find_option("verbose","v",0)!=0 ){ gitmirror_verbosity++; }
  verify_all_options();
  if( g.argc!=4 && g.argc!=3 ){ usage("export ?MIRROR?"); }
  if( g.argc==4 ){
    Blob mirror;
    file_canonical_name(g.argv[3], &mirror, 0);
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1531
1532
1533
1534
1535
1536
1537



1538
1539
1540
1541
1542
1543
1544







-
-
-







                                        " WHERE key='start'),0.0)")
  ){
    gitmirror_message(VERB_NORMAL, "no changes\n");
    db_commit_transaction();
    return;
  }

  /* Do we need to include manifest files in the clone? */
  fManifest = db_get_manifest_setting();

  /* Change to the MIRROR directory so that the Git commands will work */
  rc = file_chdir(zMirror, 0);
  if( rc ) fossil_fatal("cannot change the working directory to \"%s\"",
                        zMirror);

  /* Start up the git fast-import command */
  if( zDebug ){
1575
1576
1577
1578
1579
1580
1581
1582

1583
1584
1585
1586
1587
1588
1589
1586
1587
1588
1589
1590
1591
1592

1593
1594
1595
1596
1597
1598
1599
1600







-
+







    "SELECT objid, mtime, uuid FROM tomirror ORDER BY mtime"
  );
  while( nLimit && db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    double rMTime = db_column_double(&q, 1);
    const char *zUuid = db_column_text(&q, 2);
    if( rMTime>rEnd ) rEnd = rMTime;
    rc = gitmirror_send_checkin(xCmd, rid, zUuid, &nLimit, fManifest);
    rc = gitmirror_send_checkin(xCmd, rid, zUuid, &nLimit);
    if( rc ) break;
    gitmirror_message(VERB_NORMAL,"%d/%d      \r", nTotal-nLimit, nTotal);
    fflush(stdout);
  }
  db_finalize(&q);
  fprintf(xCmd, "done\n");
  if( zDebug ){
1740
1741
1742
1743
1744
1745
1746
1747

1748
1749
1750
1751
1752
1753
1754
1751
1752
1753
1754
1755
1756
1757

1758
1759
1760
1761
1762
1763
1764
1765







-
+







  int rc;
  char *zSql;
  int bQuiet = 0;
  int bByAll = 0;   /* Undocumented option meaning this command was invoked
                    ** from "fossil all" and should modify output accordingly */

  db_find_and_open_repository(0, 0);
  bQuiet = find_option("quiet","q",0)!=0;
  bQuiet = g.fQuiet;
  bByAll = find_option("by-all",0,0)!=0;
  verify_all_options();
  zMirror = db_get("last-git-export-repo", 0);
  if( zMirror==0 ){
    if( bQuiet ) return;
    if( bByAll ) return;
    fossil_print("Git mirror:  none\n");
Changes to src/extcgi.c.
169
170
171
172
173
174
175

176
177
178
179
180
181
182
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183







+







  int fdFromChild = -1;           /* File descriptor for reading from child */
  FILE *toChild = 0;              /* FILE for sending to child */
  FILE *fromChild = 0;            /* FILE for reading from child */
  int pidChild = 0;               /* Process id of the child */
  int rc;                         /* Reply code from subroutine call */
  int nContent = -1;              /* Content length */
  const char *zPathInfo;          /* Original PATH_INFO value */
  char *zRestrictTag;             /* Tag to restrict specific documents */
  Blob reply;                     /* The reply */
  char zLine[1000];               /* One line of the CGI reply */
  const char *zSrvSw;             /* SERVER_SOFTWARE */

  zPathInfo = P("PATH_INFO");
  login_check_credentials();
  blob_init(&reply, 0, 0);
227
228
229
230
231
232
233




234

235
236
237
238
239
240
241
228
229
230
231
232
233
234
235
236
237
238

239
240
241
242
243
244
245
246







+
+
+
+
-
+







  }
  if( nScript==0 ){
    zFailReason = "path does not match any file or script";
    goto ext_not_found;
  }
  assert( nScript>=nRoot+1 );
  style_set_current_page("ext/%s", &zScript[nRoot+1]);
  zRestrictTag = mprintf("ext/%s", &zScript[nRoot+1]);
  if( robot_restrict(zRestrictTag) ) return;
  fossil_free(zRestrictTag);
  zMime = P("mimetype");
  zMime = mimetype_from_name(zScript);
  if( zMime==0 ) zMime = mimetype_from_name(zScript);
  if( zMime==0 ) zMime = "application/octet-stream";
  if( !file_isexe(zScript, ExtFILE) ){
    /* File is not executable.  Must be a regular file.  In that case,
    ** disallow extra path elements */
    if( zPath[nScript]!=0 ){
      zFailReason = "extra path elements after filename";
      goto ext_not_found;
Changes to src/file.c.
49
50
51
52
53
54
55
56

57
58
59
60
61
62
63
49
50
51
52
53
54
55

56
57
58
59
60
61
62
63







-
+







** used for files that are under management by a Fossil repository.  ExtFILE
** should be used for files that are not under management.  SymFILE is for
** a few special cases such as the "fossil test-tarball" command when we never
** want to follow symlinks.
**
**   ExtFILE      Symbolic links always refer to the object to which the
**                link points.  Symlinks are never recognized as symlinks but
**                instead always appear to the the target object.
**                instead always appear to be the target object.
**
**   SymFILE      Symbolic links always appear to be files whose name is
**                the target pathname of the symbolic link.
**
**   RepoFILE     Like SymFILE if allow-symlinks is true, or like
**                ExtFILE if allow-symlinks is false.  In other words,
**                symbolic links are only recognized as something different
258
259
260
261
262
263
264
265

266
267
268
269
270
271
272
258
259
260
261
262
263
264

265
266
267
268
269
270
271
272







-
+







#if !defined(_WIN32)
  if( db_allow_symlinks() ){
    int i, nName;
    char *zName, zBuf[1000];

    nName = strlen(zLinkFile);
    if( nName>=(int)sizeof(zBuf) ){
      zName = mprintf("%s", zLinkFile);
      zName = fossil_strdup(zLinkFile);
    }else{
      zName = zBuf;
      memcpy(zName, zLinkFile, nName+1);
    }
    nName = file_simplify_name(zName, nName, 0);
    for(i=1; i<nName; i++){
      if( zName[i]=='/' ){
423
424
425
426
427
428
429
430

431
432
433
434
435
436
437
423
424
425
426
427
428
429

430
431
432
433
434
435
436
437







-
+







** does not exist.  Return 2 if zFilename exists but is something
** other than a directory.
*/
int file_isdir(const char *zFilename, int eFType){
  int rc;
  char *zFN;

  zFN = mprintf("%s", zFilename);
  zFN = fossil_strdup(zFilename);
  file_simplify_name(zFN, -1, 0);
  rc = getStat(zFN, eFType);
  if( rc ){
    rc = 0; /* It does not exist at all. */
  }else if( S_ISDIR(fx.fileStat.st_mode) ){
    rc = 1; /* It exists and is a real directory. */
  }else{
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+















+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  if( !zTail ) return 0;
  while( z[0] ){
    if( fossil_isdirsep(z[0]) ) zTail = &z[1];
    z++;
  }
  return zTail;
}

/*
** Return the tail of a command: the basename of the putative executable (which
** could be quoted when containing spaces) and the following arguments.
*/
const char *command_tail(const char *z){
  const char *zTail = z;
  char chQuote = 0;
  if( !zTail ) return 0;
  while( z[0] && (!fossil_isspace(z[0]) || chQuote) ){
    if( z[0]=='"' || z[0]=='\'' ){
      chQuote = (chQuote==z[0]) ? 0 : z[0];
    }
    if( fossil_isdirsep(z[0]) ) zTail = &z[1];
    z++;
  }
  return zTail;
}

/*
** Return the directory of a file path name.  The directory is all components
** except the last one.  For example, the directory of "/a/b/c.d" is "/a/b".
** If there is no directory, NULL is returned; otherwise, the returned memory
** should be freed via fossil_free().
*/
char *file_dirname(const char *z){
  const char *zTail = file_tail(z);
  if( zTail && zTail!=z ){
    return mprintf("%.*s", (int)(zTail-z-1), z);
  }else{
    return 0;
  }
}

/*
** Return the basename of the putative executable in a command (w/o arguments).
** The returned memory should be freed via fossil_free().
*/
char *command_basename(const char *z){
  const char *zTail = command_tail(z);
  const char *zEnd = zTail;
  while( zEnd[0] && !fossil_isspace(zEnd[0]) && zEnd[0]!='"' && zEnd[0]!='\'' ){
    zEnd++;
  }
  if( zEnd ){
    return mprintf("%.*s", (int)(zEnd-zTail), zTail);
  }else{
    return 0;
  }
}

/* SQL Function:  file_dirname(NAME)
**
** Return the directory for NAME
*/
void file_dirname_sql_function(
  sqlite3_context *context,
867
868
869
870
871
872
873
874

875
876
877
878
879
880
881
902
903
904
905
906
907
908

909
910
911
912
913
914
915
916







-
+







  int forceFlag,           /* Delete non-directory objects in the way */
  int errorReturn          /* What to do when an error is seen */
){
  int nName, rc = 0;
  char *zName;

  nName = strlen(zFilename);
  zName = mprintf("%s", zFilename);
  zName = fossil_strdup(zFilename);
  nName = file_simplify_name(zName, nName, 0);
  while( nName>0 && zName[nName-1]!='/' ){ nName--; }
  if( nName>1 ){
    zName[nName-1] = 0;
    if( file_isdir(zName, eFType)!=1 ){
      rc = file_mkfolder(zName, eFType, forceFlag, errorReturn);
      if( rc==0 ){
1240
1241
1242
1243
1244
1245
1246
1247

1248
1249

1250
1251
1252
1253
1254
1255
1256
1275
1276
1277
1278
1279
1280
1281

1282
1283

1284
1285
1286
1287
1288
1289
1290
1291







-
+

-
+







  int i;
  char *z;
  const char *zTail;
  for(i=2; i<g.argc; i++){
    zTail = file_skip_userhost(g.argv[i]);
    if( zTail ){
      fossil_print("... ON REMOTE: %.*s\n", (int)(zTail-g.argv[i]), g.argv[i]);
      z = mprintf("%s", zTail);
      z = fossil_strdup(zTail);
    }else{
      z = mprintf("%s", g.argv[i]);
      z = fossil_strdup(g.argv[i]);
    }
    fossil_print("[%s] -> ", z);
    file_simplify_name(z, -1, 0);
    fossil_print("[%s]\n", z);
    fossil_free(z);
  }
}
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
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
1362
1363
1364
1365

1366
1367
1368

1369
1370
1371
1372
1373
1374
1375







+
-
-
-
-
+
+
+
+
+
+






+


+
+
+
+
+
+
+
+
-
+
+

-







  }else{
    return 0;
  }
}

/*
** Compute a canonical pathname for a file or directory.
**
** Make the name absolute if it is relative.
** Remove redundant / characters
** Remove all /./ path elements.
** Convert /A/../ to just /
**  *  Make the name absolute if it is relative.
**  *  Remove redundant / characters
**  *  Remove all /./ path elements.
**  *  Convert /A/../ to just /
**  *  On windows, add the drive letter prefix.
**
** If the slash parameter is non-zero, the trailing slash, if any,
** is retained.
**
** See also: file_canonical_name_dup()
*/
void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){
  char zPwd[2000];
  blob_zero(pOut);
  if( file_is_absolute_path(zOrigName) ){
#if defined(_WIN32)
    if( fossil_isdirsep(zOrigName[0]) ){
      /* Add the drive letter to the full pathname */
      file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName));
      blob_appendf(pOut, "%.2s%/", zPwd, zOrigName);
    }else
#endif
    {
    blob_appendf(pOut, "%/", zOrigName);
      blob_appendf(pOut, "%/", zOrigName);
    }
  }else{
    char zPwd[2000];
    file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName));
    if( zPwd[0]=='/' && strlen(zPwd)==1 ){
      /* when on '/', don't add an extra '/' */
      if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){
        /* '.' when on '/' mean '/' */
        blob_appendf(pOut, "%/", zPwd);
      }else{
1523
1524
1525
1526
1527
1528
1529







1530
1531
1532
1533
1534
1535
1536
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590







+
+
+
+
+
+
+







/*
** COMMAND: test-which
**
** Usage: %fossil test-which ARGS...
**
** For each argument, search the PATH for the executable with the name
** and print its full pathname.
**
** See also the "which" command (without the "test-" prefix).  The plain
** "which" command is more convenient to use since it provides the -a/-all
** option, and because it is shorter.  The "fossil which" command without
** the "test-" prefix is recommended for day-to-day use.  This command is
** retained because it tests the internal file_fullexename() function
** whereas plain "which" does not.
*/
void test_which_cmd(void){
  int i;
  for(i=2; i<g.argc; i++){
    char *z = file_fullexename(g.argv[i]);
    fossil_print("%z\n", z);
  }
1642
1643
1644
1645
1646
1647
1648



1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661



1662
1663
1664
1665
1666
1667
1668
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







+
+
+













+
+
+







               filenames_are_case_sensitive());
  if( zAllow ){
    g.allowSymlinks = !is_false(zAllow);
  }
  if( zRoot==0 ) zRoot = g.zLocalRoot==0 ? "" : g.zLocalRoot;
  fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks());
  fossil_print("local-root = [%s]\n", zRoot);
  if( g.db==0 ) sqlite3_open(":memory:", &g.db);
  sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
                          file_inode_sql_func, 0, 0);
  for(i=2; i<g.argc; i++){
    char *z;
    emitFileStat(g.argv[i], slashFlag, resetFlag);
    z = file_canonical_name_dup(g.argv[i]);
    fossil_print("  file_canonical_name    = %s\n", z);
    fossil_print("  file_nondir_path       = ");
    if( fossil_strnicmp(zRoot,z,(int)strlen(zRoot))!=0 ){
      fossil_print("(--root is not a prefix of this file)\n");
    }else{
      int n = file_nondir_objects_on_path(zRoot, z);
      fossil_print("%.*s\n", n, z);
    }
    fossil_free(z);
    z = db_text(0, "SELECT inode(%Q)", g.argv[i]);
    fossil_print("  file_inode_sql_func    = \"%s\"\n", z);
    fossil_free(z);
  }
}

/*
** COMMAND: test-canonical-name
**
** Usage: %fossil test-canonical-name FILENAME...
1697
1698
1699
1700
1701
1702
1703
1704

1705
1706



1707
1708
1709
1710
1711
1712
1713
1757
1758
1759
1760
1761
1762
1763

1764
1765

1766
1767
1768
1769
1770
1771
1772
1773
1774
1775







-
+

-
+
+
+







** Return TRUE if the given filename is canonical.
**
** Canonical names are full pathnames using "/" not "\" and which
** contain no "/./" or "/../" terms.
*/
int file_is_canonical(const char *z){
  int i;
  if( z[0]!='/'
  if(
#if defined(_WIN32) || defined(__CYGWIN__)
    && (!fossil_isupper(z[0]) || z[1]!=':' || z[2]!='/')
    !fossil_isupper(z[0]) || z[1]!=':' || !fossil_isdirsep(z[2])
#else
    z[0]!='/'
#endif
  ) return 0;

  for(i=0; z[i]; i++){
    if( z[i]=='\\' ) return 0;
    if( z[i]=='/' ){
      if( z[i+1]=='.' ){
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488










2489
2490
2491
2492
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
2519
2520
2521

2522
2523







2524




2525
2526

2527
2528

2529
2530
2531
2532










2533
2534

2535


2536
2537



2538
2539
2540
2541
2542
2543




2544








2545
2546
2547
2548
2549
2550
2551
2541
2542
2543
2544
2545
2546
2547



2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561

2562
2563
2564
2565
2566
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
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
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
2676
2677
2678
2679
2680
2681







-
-
-
+
+
+
+
+
+
+
+
+
+




-
+
+
+
+
+
+
+



+
+






+

-
-
-
+
+
+
+

-
+
+
+



+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+




+


+
+
+
+
+
+
+
-
+
+
+
+

-
+

-
+

-
-
-
+
+
+
+
+
+
+
+
+
+

-
+

+
+


+
+
+






+
+
+
+
-
+
+
+
+
+
+
+
+







#else
  while( z[0]=='/' && z[1]=='/' ) z++;
#endif
  return z;
}

/*
** Count the number of objects (files and subdirectories) in a given
** directory.  Return the count.  Return -1 if the object is not a
** directory.
** Find the name of all objects (files and subdirectories) in a given
** directory that match a GLOB pattern.  If zGlob is NULL, then return
** all objects.  The list is written into *pazList and the number of
** entries is returned.  If pazList is NULL, then only the count is
** returned.
**
** If zDir is not a directory, *pazList is unchanged and -1 is returned.
**
** Memory used to old *pazList should be freed using a subsequent call
** to file_directory_list_free().
**
** This routine never counts the two "." and ".." special directory
** entries, even if the provided glob would match them.
*/
int file_directory_size(const char *zDir, const char *zGlob, int omitDotFiles){
int file_directory_list(
  const char *zDir,    /* Directory to get a listing of */
  const char *zGlob,   /* Only list objects matching this pattern */
  int omitDotFiles,    /* 0: skip "." and "..", 1: no .-files, 2: keep all */
  int nLimit,          /* Find at most this many files.  0 means "all" */
  char ***pazList      /* OUT:  Write the list here, if not NULL */
){
  void *zNative;
  DIR *d;
  int n = -1;
  int nAlloc = 0;
  if( pazList ) *pazList = 0;
  zNative = fossil_utf8_to_path(zDir,1);
  d = opendir(zNative);
  if( d ){
    struct dirent *pEntry;
    n = 0;
    while( (pEntry=readdir(d))!=0 ){
      char *zUtf8 = 0;
      if( pEntry->d_name[0]==0 ) continue;
      if( pEntry->d_name[0]=='.' &&
          (omitDotFiles
           /* Skip the special "." and ".." entries. */
      if( pEntry->d_name[0]=='.'
       && omitDotFiles<2
       && (omitDotFiles==1
           /* Skip the special "." and ".." entries unless omitDotFiles>=2 */
           || pEntry->d_name[1]==0
           || (pEntry->d_name[1]=='.' && pEntry->d_name[2]==0))){
           || (pEntry->d_name[1]=='.' && pEntry->d_name[2]==0)
          )
      ){
        continue;
      }
      if( zGlob ){
        int rc;
        char *zUtf8 = fossil_path_to_utf8(pEntry->d_name);
        int rc = sqlite3_strglob(zGlob, zUtf8);
        fossil_path_free(zUtf8);
        if( rc ) continue;
      }
        zUtf8 = fossil_path_to_utf8(pEntry->d_name);
        rc = sqlite3_strglob(zGlob, zUtf8);
        if( rc ){
          fossil_path_free(zUtf8);
          continue;
        }
      }
      if( pazList ){
        if( n+1 >= nAlloc ){
          nAlloc = 100 + n;
          *pazList = fossil_realloc(*pazList, nAlloc*sizeof(char*));
        }
        if( zUtf8==0 ){
          zUtf8 = fossil_path_to_utf8(pEntry->d_name);
        }
        (*pazList)[n] = fossil_strdup(zUtf8);
      }
      n++;
      if( zUtf8 ) fossil_path_free(zUtf8);
      if( nLimit>0 && n>=nLimit ) break;
    }
    closedir(d);
  }
  fossil_path_free(zNative);
  if( pazList ) (*pazList)[n] = 0;
  return n;
}
void file_directory_list_free(char **azList){
  char **az;
  if( azList==0 ) return;
  az = azList;
  while( az[0] ){
    fossil_free(az[0]);
    az++;

  }
  fossil_free(azList);
}

/*
** COMMAND: test-dir-size
** COMMAND: test-dir-list
**
** Usage: %fossil test-dir-size NAME [GLOB] [--nodots]
** Usage: %fossil test-dir-list NAME [GLOB] [OPTIONS]
**
** Return the number of objects in the directory NAME.  If GLOB is
** provided, then only count objects that match the GLOB pattern.
** if --nodots is specified, omit files that begin with ".".
** Return the names of up to N objects in the directory NAME.  If GLOB is
** provided, then only show objects that match the GLOB pattern.
**
** This command is intended for testing the file_directory_list() function.
**
** Options:
**
**      --count           Only count files, do not list them.
**      --limit N         Only show the first N files seen
**      --nodots          Do not show or count files that start with '.'
*/
void test_dir_size_cmd(void){
void test_dir_list_cmd(void){
  int omitDotFiles = find_option("nodots",0,0)!=0;
  const char *zLimit = find_option("limit",0,1);
  int countOnly = find_option("count",0,0)!=0;
  const char *zGlob;
  const char *zDir;
  char **azList = 0;
  int nList;

  verify_all_options();
  if( g.argc!=3 && g.argc!=4 ){
    usage("NAME [GLOB] [-nodots]");
  }
  zDir = g.argv[2];
  zGlob = g.argc==4 ? g.argv[3] : 0;
  nList = file_directory_list(zDir, zGlob, omitDotFiles,
              zLimit ? atoi(zLimit) : 0, 
              countOnly ? 0 : &azList);
  if( countOnly ){
  fossil_print("%d\n", file_directory_size(zDir, zGlob, omitDotFiles));
    fossil_print("%d\n", nList);
  }else{
    int i;
    for(i=0; i<nList; i++){
      fossil_print("  %s\n", azList[i]);
    }
  }
  file_directory_list_free(azList);
}

/*
** Internal helper for touch_cmd(). zAbsName must be resolvable as-is
** to an existing file - this function does not expand/normalize
** it. i.e. it "really should" be an absolute path. zTreeName is
** strictly cosmetic: it is used when dryRunFlag, verboseFlag, or
2686
2687
2688
2689
2690
2691
2692
2693

2694
2695
2696
2697
2698
2699
2700
2816
2817
2818
2819
2820
2821
2822

2823
2824
2825
2826
2827
2828
2829
2830







-
+







  int quietFlag = 0;      /* -q|--quiet */
  int timeFlag;           /* -1==--checkin, 1==--checkout, 0==--now */
  i64 nowTime = 0;        /* Timestamp of --now or --checkout */
  Stmt q;
  Blob absBuffer = empty_blob; /* Absolute filename buffer */

  verboseFlag = find_option("verbose","v",0)!=0;
  quietFlag = find_option("quiet","q",0)!=0 || g.fQuiet;
  quietFlag = g.fQuiet;
  dryRunFlag = find_option("dry-run","n",0)!=0;
  zGlobList = find_option("glob", "g",1);
  zGlobFile = find_option("globfile", "G",1);

  if(zGlobList && zGlobFile){
    fossil_fatal("Options -g and -G may not be used together.");
  }
2951
2952
2953
2954
2955
2956
2957

2958
2959
2960
2961
2962
2963
2964
2965
2966
2967



2968
2969
2970






























































3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166







+










+
+
+



+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

/*
** Returns 1 if the given directory contains a file named .fslckout, 2
** if it contains a file named _FOSSIL_, else returns 0.
*/
int dir_has_ckout_db(const char *zDir){
  int rc = 0;
  i64 sz;
  char * zCkoutDb = mprintf("%//.fslckout", zDir);
  if(file_isfile(zCkoutDb, ExtFILE)){
    rc = 1;
  }else{
    fossil_free(zCkoutDb);
    zCkoutDb = mprintf("%//_FOSSIL_", zDir);
    if(file_isfile(zCkoutDb, ExtFILE)){
      rc = 2;
    }
  }
  if( rc && ((sz = file_size(zCkoutDb, ExtFILE))<1024 || (sz%512)!=0) ){
    rc = 0;
  }
  fossil_free(zCkoutDb);
  return rc;
}

/*
** This is the implementation of inode(FILENAME) SQL function.
**
** dev_inode(FILENAME) returns a string.  If FILENAME exists and is
** a regular file, then the return string is of the form:
**
**       DEV/INODE
**
** Where DEV and INODE are the device number and inode number for
** the file.  On Windows, the volume serial number (DEV) and file
** identifier (INODE) are used to compute the value, see comments
** on the win32_file_id() function.
**
** If FILENAME does not exist, then the return is an empty string.
**
** The value of inode() can be used to eliminate files from a list
** that have duplicates because they have differing names due to links.
**
** Code that wants to use this SQL function needs to first register
** it using a call such as the following:
**
**    sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
**                            file_inode_sql_func, 0, 0);
*/
void file_inode_sql_func(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  const char *zFilename;
  assert( argc==1 );
  zFilename = (const char*)sqlite3_value_text(argv[0]);
  if( zFilename==0 || zFilename[0]==0 || file_access(zFilename,F_OK) ){
    sqlite3_result_text(context, "", 0, SQLITE_STATIC);
    return;
  }
#if defined(_WIN32)
  {
    char *zFileId = win32_file_id(zFilename);
    if( zFileId ){
      sqlite3_result_text(context, zFileId, -1, fossil_free);
    }else{
      sqlite3_result_text(context, "", 0, SQLITE_STATIC);
    }
  }
#else
  {
    struct stat buf;
    int rc;
    memset(&buf, 0, sizeof(buf));
    rc = stat(zFilename, &buf);
    if( rc ){
      sqlite3_result_text(context, "", 0, SQLITE_STATIC);
    }else{
      sqlite3_result_text(context,
         mprintf("%lld/%lld", (i64)buf.st_dev, (i64)buf.st_ino), -1,
         fossil_free);
    }
  }
#endif
}
Changes to src/fileedit.c.
46
47
48
49
50
51
52
53

54
55
56
57
58
59
60
46
47
48
49
50
51
52

53
54
55
56
57
58
59
60







-
+







  Blob fileHash;       /* Hash of this->fileContent, using the repo's
                          preferred hash method. */
  Blob comment;        /* Check-in comment text */
  char *zCommentMimetype;  /* Mimetype of comment. May be NULL */
  char *zUser;         /* User name */
  char *zDate;         /* Optionally force this date string (anything
                          supported by date_in_standard_format()).
                          Maybe be NULL. */
                          May be NULL. */
  Blob *pMfOut;        /* If not NULL, checkin_mini() will write a
                          copy of the generated manifest here. This
                          memory is NOT owned by CheckinMiniInfo. */
  int filePerm;        /* Permissions (via file_perm()) of the input
                          file. We need to store this before calling
                          checkin_mini() because the real input file
                          name may differ from the repo-centric
121
122
123
124
125
126
127
128

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

128
129
130
131
132
133
134
135







-
+







** A hint to checkin_mini() to "prefer" creation of a delta manifest.
** It may decide not to for various reasons.
*/
CIMINI_PREFER_DELTA = 1<<8,
/*
** A "stronger hint" to checkin_mini() to prefer creation of a delta
** manifest if it at all can. It will decide not to only if creation
** of a delta is not a realistic option or if it's forbitted by the
** of a delta is not a realistic option or if it's forbidden by the
** forbid-delta-manifests repo config option. For this to work, it
** must be set together with the CIMINI_PREFER_DELTA flag, but the two
** cannot be combined in this enum.
**
** This option is ONLY INTENDED FOR TESTING, used in bypassing
** heuristics which may otherwise disable generation of a delta on the
** grounds of efficiency (e.g. not generating a delta if the parent
398
399
400
401
402
403
404
405

406
407
408
409
410
411
412
398
399
400
401
402
403
404

405
406
407
408
409
410
411
412







-
+







** includes checking for repo locks).
**
** This routine uses the state from the given fully-populated pCI
** argument to add pCI->fileContent to the database, and create and
** save a manifest for that change. Ownership of pCI and its contents
** are unchanged.
**
** This function may may modify pCI as follows:
** This function may modify pCI as follows:
**
** - If one of Manifest pCI->pParent or pCI->zParentUuid are NULL,
**   then the other will be assigned based on its counterpart. Both
**   may not be NULL.
**
** - pCI->zDate is normalized to/replaced with a valid date/time
**   string. If its original value cannot be validated then
823
824
825
826
827
828
829
830

831
832

833
834
835
836
837
838

839
840
841
842
843
844
845
823
824
825
826
827
828
829

830
831

832
833
834
835
836
837

838
839
840
841
842
843
844
845







-
+

-
+





-
+







      fossil_fatal("Non-empty check-in comment is required.");
    }
  }
  db_begin_transaction();
  zFilename = g.argv[2];
  cimi.zFilename = mprintf("%/", zAsFilename ? zAsFilename : zFilename);
  cimi.filePerm = file_perm(zFilename, ExtFILE);
  cimi.zUser = mprintf("%s", zUser ? zUser : login_name());
  cimi.zUser = fossil_strdup(zUser ? zUser : login_name());
  if(zDate){
    cimi.zDate = mprintf("%s", zDate);
    cimi.zDate = fossil_strdup(zDate);
  }
  if(zRevision==0 || zRevision[0]==0){
    if(g.localOpen/*check-out*/){
      zRevision = db_lget("checkout-hash", 0)/*leak*/;
    }else{
      zRevision = "trunk";
      zRevision = db_main_branch();
    }
  }
  name_to_uuid2(zRevision, "ci", &cimi.zParentUuid);
  if(cimi.zParentUuid==0){
    fossil_fatal("Cannot determine version to commit to.");
  }
  blob_read_from_file(&cimi.fileContent, zFilename, ExtFILE);
926
927
928
929
930
931
932
933

934
935
936
937
938
939
940
926
927
928
929
930
931
932

933
934
935
936
937
938
939
940







-
+







                                int vid, int *pFilePerm){
  Stmt stmt = empty_Stmt;
  char * zFileUuid = 0;
  db_prepare(&stmt, "SELECT uuid, perm FROM files_of_checkin "
             "WHERE filename=%Q %s AND checkinID=%d",
             zFilename, filename_collation(), vid);
  if(SQLITE_ROW==db_step(&stmt)){
    zFileUuid = mprintf("%s",db_column_text(&stmt, 0));
    zFileUuid = fossil_strdup(db_column_text(&stmt, 0));
    if(pFilePerm){
      *pFilePerm = mfile_permstr_int(db_column_text(&stmt, 1));
    }
  }
  db_finalize(&stmt);
  return zFileUuid;
}
1185
1186
1187
1188
1189
1190
1191
1192

1193
1194
1195
1196
1197
1198
1199
1185
1186
1187
1188
1189
1190
1191

1192
1193
1194
1195
1196
1197
1198
1199







-
+







  if(zFlag==0 || !*zFlag){
    rc = 400;
    if(bIsMissingArg){
      *bIsMissingArg = 1;
    }
    fail((pErr,"Missing required 'filename' parameter."));
  }
  p->zFilename = mprintf("%s",zFlag);
  p->zFilename = fossil_strdup(zFlag);

  if(0==fileedit_is_editable(p->zFilename)){
    rc = 403;
    fail((pErr,"Filename [%h] is disallowed "
          "by the [fileedit-glob] repository "
          "setting.",
          p->zFilename));
1246
1247
1248
1249
1250
1251
1252
1253

1254
1255
1256
1257
1258
1259
1260
1246
1247
1248
1249
1250
1251
1252

1253
1254
1255
1256
1257
1258
1259
1260







-
+








  zFlag = PT("comment");
  if(zFlag!=0 && *zFlag!=0){
    blob_append(&p->comment, zFlag, -1);
  }
  zFlag = P("comment_mimetype");
  if(zFlag){
    p->zCommentMimetype = mprintf("%s",zFlag);
    p->zCommentMimetype = fossil_strdup(zFlag);
    zFlag = 0;
  }
#define p_int(K) atoi(PD(K,"0"))
  if(p_int("dry_run")!=0){
    p->flags |= CIMINI_DRY_RUN;
  }
  if(p_int("allow_fork")!=0){
1282
1283
1284
1285
1286
1287
1288
1289

1290
1291
1292
1293
1294
1295
1296
1282
1283
1284
1285
1286
1287
1288

1289
1290
1291
1292
1293
1294
1295
1296







-
+







    default: p->flags |= CIMINI_CONVERT_EOL_INHERIT; break;
  }
#undef p_int
  /*
  ** TODO?: date-override date selection field. Maybe use
  ** an input[type=datetime-local].
  */
  p->zUser = mprintf("%s",g.zLogin);
  p->zUser = fossil_strdup(g.zLogin);
  return 0;
end_fail:
#undef fail
  fossil_free(zFileUuid);
  return rc ? rc : 500;
}

Changes to src/finfo.c.
35
36
37
38
39
40
41
42

43
44
45
46
47
48
49
35
36
37
38
39
40
41

42
43
44
45
46
47
48
49







-
+







**
** The -i mode will print various facts about FILENAME, including its
** hash and the check-in and time when the current version of the file
** was created.  Use -v for additional information.  Add the -r VERSION
** option to see similar information about the same file for the check-in
** specified by VERSION.
**
** In the -s mode prints the status as <status> <revision>.  This is
** The -s mode prints the status as <status> <revision>.  This is
** a quick status and does not check for up-to-date-ness of the file.
**
** In the -p mode, there's an optional flag "-r|--revision REVISION".
** The specified version (or the latest checked-out version) is printed
** to stdout.  The -p mode is another form of the "cat" command.
**
** Options:
174
175
176
177
178
179
180

181
182
183
184
185
186
187
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188







+







    Blob fname;
    int rid;
    const char *zFilename;
    const char *zLimit;
    const char *zWidth;
    const char *zOffset;
    int iLimit, iOffset, iBrief, iWidth;
    const char *zMainBranch;

    if( find_option("log","l",0) ){
      /* this is the default, no-op */
    }
    zLimit = find_option("limit","n",1);
    zWidth = find_option("width","W",1);
    iLimit = zLimit ? atoi(zLimit) : -1;
229
230
231
232
233
234
235

236
237
238
239
240
241
242
243
244

245
246
247
248
249
250
251
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245

246
247
248
249
250
251
252
253







+








-
+







        TAG_BRANCH, zFilename, filename_collation(),
        iLimit, iOffset
    );
    blob_zero(&line);
    if( iBrief == 0 ){
      fossil_print("History for %s\n", blob_str(&fname));
    }
    zMainBranch = db_main_branch();
    while( db_step(&q)==SQLITE_ROW ){
      const char *zFileUuid = db_column_text(&q, 0);
      const char *zCiUuid = db_column_text(&q,1);
      const char *zDate = db_column_text(&q, 2);
      const char *zCom = db_column_text(&q, 3);
      const char *zUser = db_column_text(&q, 4);
      const char *zBr = db_column_text(&q, 5);
      char *zOut;
      if( zBr==0 ) zBr = "trunk";
      if( zBr==0 ) zBr = fossil_strdup(zMainBranch);
      if( iBrief == 0 ){
        fossil_print("%s ", zDate);
        zOut = mprintf(
           "[%S] %s (user: %s, artifact: [%S], branch: %s)",
           zCiUuid, zCom, zUser, zFileUuid, zBr);
        comment_print(zOut, zCom, 11, iWidth, get_comment_format());
        fossil_free(zOut);
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
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
408
409
410







+


















+
+







  Stmt qparent;
  int iTableId = timeline_tableid();
  int tmFlags = 0;            /* Viewing mode */
  const char *zStyle;         /* Viewing mode name */
  const char *zMark;          /* Mark this version of the file */
  int selRid = 0;             /* RID of the marked file version */
  int mxfnid;                 /* Maximum filename.fnid value */
  const char *zMainBranch;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
  ridCi = zCI ? name_to_rid_www("ci") : 0;
  if( fnid==0 ){
    style_header("No such file");
  }else if( ridCi==0 ){
    style_header("All files named \"%s\"", zFilename);
  }else{
    style_header("History of %s of %s",zFilename, zCI);
  }
  login_anonymous_available();
  tmFlags = timeline_ss_submenu();
  if( tmFlags & TIMELINE_COLUMNAR ){
    zStyle = "Columnar";
  }else if( tmFlags & TIMELINE_COMPACT ){
    zStyle = "Compact";
  }else if( tmFlags & TIMELINE_SIMPLE ){
    zStyle = "Simple";
  }else if( tmFlags & TIMELINE_VERBOSE ){
    zStyle = "Verbose";
  }else if( tmFlags & TIMELINE_CLASSIC ){
    zStyle = "Classic";
  }else{
    zStyle = "Modern";
  }
496
497
498
499
500
501
502
503

504
505
506
507
508

509
510
511
512
513
514
515
501
502
503
504
505
506
507

508
509
510
511
512

513
514
515
516
517
518
519
520







-
+




-
+







    " LEFT JOIN filename ON filename.fnid=clade.fnid\n"
    "WHERE mlink.fnid=clade.fnid AND mlink.fid=clade.fid\n"
    "  AND event.objid=mlink.mid\n",
    TAG_BRANCH
  );
  if( (zA = P("a"))!=0 ){
    blob_append_sql(&sql, "  AND event.mtime>=%.16g\n",
         symbolic_name_to_mtime(zA,0));
         symbolic_name_to_mtime(zA,0,0));
    url_add_parameter(&url, "a", zA);
  }
  if( (zB = P("b"))!=0 ){
    blob_append_sql(&sql, "  AND event.mtime<=%.16g\n",
         symbolic_name_to_mtime(zB,0));
         symbolic_name_to_mtime(zB,0,1));
    url_add_parameter(&url, "b", zB);
  }
  if( ridFrom ){
    blob_append_sql(&sql,
      "  AND mlink.mid IN (SELECT rid FROM ancestor)\n"
      "GROUP BY mlink.fid\n"
    );
629
630
631
632
633
634
635

636

637
638
639
640



641
642
643
644
645
646
647
634
635
636
637
638
639
640
641

642
643
644
645

646
647
648
649
650
651
652
653
654
655







+
-
+



-
+
+
+







    db_bind_int(&qparent, ":mid", fmid);
    db_bind_int(&qparent, ":fnid", fnid);
    while( db_step(&qparent)==SQLITE_ROW && nParent<count(aParent) ){
      aParent[nParent] = db_column_int64(&qparent, 0);
      nParent++;
    }
    db_reset(&qparent);
    zMainBranch = db_main_branch();
    if( zBr==0 ) zBr = "trunk";
    if( zBr==0 ) zBr = fossil_strdup(zMainBranch);
    if( uBg ){
      zBgClr = user_color(zUser);
    }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
      zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
      zBgClr = strcmp(zBr, zMainBranch)==0 ? "" : hash_color(zBr);
    }else if( zBgClr ){
      zBgClr = reasonable_bg_color(zBgClr,0);
    }
    gidx = graph_add_row(pGraph,
                   frid>0 ? (GraphRowId)frid*(mxfnid+1)+fnid : fpid+1000000000,
                   nParent, 0, aParent, zBr, zBgClr,
                   zUuid, 0);
    if( strncmp(zDate, zPrevDate, 10) ){
      sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate);
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
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







-
+


-
+










+
+
+
+
+








-
-
+
+







        @ <td class="timelineDetailCell">
      }
    }
    if( tmFlags & TIMELINE_COMPACT ){
      cgi_printf("<span class='clutter' id='detail-%d'>",frid);
    }
    cgi_printf("<span class='timeline%sDetail'>", zStyle);
    if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ) cgi_printf("(");
    if( tmFlags & TIMELINE_INLINE ) cgi_printf("(");
    if( zUuid && (tmFlags & TIMELINE_VERBOSE)==0 ){
      @ file:&nbsp;%z(href("%R/file?name=%T&ci=%!S",zFName,zCkin))\
      @ [%S(zUuid)]</a>
      @ %S(zUuid)</a>
      if( fShowId ){
        int srcId = delta_source_rid(frid);
        if( srcId>0 ){
          @ id:&nbsp;%z(href("%R/deltachain/%d",frid))\
          @ %d(frid)&larr;%d(srcId)</a>
        }else{
          @ id:&nbsp;%z(href("%R/deltachain/%d",frid))%d(frid)</a>
        }
      }
    }
    if( tmFlags & TIMELINE_SIMPLE ){
      @ <span class='timelineEllipsis' data-id='%d(frid)' \
      @ id='ellipsis-%d(frid)'>...</span>
      @ <span class='clutter' id='detail-%d(frid)'>
    }
    @ check-in:&nbsp;\
    hyperlink_to_version(zCkin);
    if( fShowId ){
      @ (%d(fmid))
    }
    @ user:&nbsp;\
    hyperlink_to_user(zUser, zDate, ",");
    @ branch:&nbsp;%z(href("%R/timeline?t=%T",zBr))%h(zBr)</a>,
    if( tmFlags & (TIMELINE_COMPACT|TIMELINE_VERBOSE) ){
      @ size:&nbsp;%d(szFile))
    if( tmFlags & TIMELINE_INLINE ){
      @ size:&nbsp;%d(szFile)
    }else{
      @ size:&nbsp;%d(szFile)
    }
    if( g.perm.Hyperlink && zUuid ){
      const char *z = zFName;
      @ <span id='links-%d(frid)'><span class='timelineExtraLinks'>
      @ %z(href("%R/annotate?filename=%h&checkin=%s",z,zCkin))
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
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
838
839







-
-
+
+


















+
+
-
+
+







        }
      }
      zAncLink = href("%R/finfo?name=%T&from=%!S&debug=1",zFName,zCkin);
      @ %z(zAncLink)[ancestry]</a>
    }
    tag_private_status(frid);
    /* End timelineDetail */
    if( tmFlags & TIMELINE_COMPACT ){
      @ </span></span>
    if( tmFlags & TIMELINE_INLINE ){
      @ </span>)</span>
    }else{
      @ </span>
    }
    @ </td></tr>
  }
  db_finalize(&q);
  db_finalize(&qparent);
  if( pGraph ){
    graph_finish(pGraph, 0, TIMELINE_DISJOINT);
    if( pGraph->nErr ){
      graph_free(pGraph);
      pGraph = 0;
    }else{
      @ <tr class="timelineBottom" id="btm-%d(iTableId)">\
      @ <td></td><td></td><td></td></tr>
    }
  }
  @ </table>
  {
    int tmFlags = TIMELINE_GRAPH | TIMELINE_FILEDIFF;
  timeline_output_graph_javascript(pGraph, TIMELINE_FILEDIFF, iTableId);
    timeline_output_graph_javascript(pGraph, tmFlags, iTableId);
  }
  style_finish_page();
}

/*
** WEBPAGE: mlink
** URL: /mlink?name=FILENAME
** URL: /mlink?ci=NAME
Changes to src/forum.c.
57
58
59
60
61
62
63

64
65
66
67
68
69
70
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71







+







*/
struct ForumThread {
  ForumPost *pFirst;     /* First post in chronological order */
  ForumPost *pLast;      /* Last post in chronological order */
  ForumPost *pDisplay;   /* Entries in display order */
  ForumPost *pTail;      /* Last on the display list */
  int mxIndent;          /* Maximum indentation level */
  int nArtifact;         /* Number of forum artifacts in this thread */
};
#endif /* INTERFACE */

/*
** Return true if the forum post with the given rid has been
** subsequently edited.
*/
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
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







-
-
+
+
+
+
+
+
+
















-
+







** value.  Returns 0 if !p. For an edited chain of post, the tag is
** checked on the pEditHead entry, to simplify subsequent unlocking of
** the post.
**
** If bCheckIrt is true then p's thread in-response-to parents are
** checked (recursively) for closure, else only p is checked.
*/
static int forumpost_is_closed(ForumPost *p, int bCheckIrt){
  while(p){
static int forumpost_is_closed(
  ForumThread *pThread,          /* Thread that the post is a member of */
  ForumPost *p,                  /* the forum post */
  int bCheckIrt                  /* True to check In-Reply-To posts */
){
  int mx = pThread->nArtifact+1;
  while( p && (mx--)>0 ){
    if( p->pEditHead ) p = p->pEditHead;
    if( p->iClosed || !bCheckIrt ) return p->iClosed;
    p = p->pIrt;
  }
  return 0;
}

/*
** Given a forum post RID, this function returns true if that post has
** (or inherits) an active "closed" tag. If bCheckIrt is true then
** the post to which the given post responds is also checked
** (recursively), else they are not. When checking in-response-to
** posts, the first one which is closed ends the search.
**
** Note that this function checks _exactly_ the given rid, whereas
** forum post closure/re-opening is always applied to the head of an
** edit chain so that we get consistent implied locking beheavior for
** edit chain so that we get consistent implied locking behavior for
** later versions and responses to arbitrary versions in the
** chain. Even so, the "closed" tag is applied as a propagating tag
** so will apply to all edits in a given chain.
**
** The return value is one of:
**
** - 0 if no "closed" tag is found.
407
408
409
410
411
412
413

414
415
416
417
418
419
420
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427







+







    pPost->pNext = 0;
    if( pThread->pLast==0 ){
      pThread->pFirst = pPost;
    }else{
      pThread->pLast->pNext = pPost;
    }
    pThread->pLast = pPost;
    pThread->nArtifact++;

    /* Find the in-reply-to post.  Default to the topic post if the replied-to
    ** post cannot be found. */
    if( firt ){
      pPost->pIrt = pThread->pFirst;
      for(p=pThread->pFirst; p; p=p->pNext){
        if( p->fpid==firt ){
518
519
520
521
522
523
524

525
526
527
528
529
530
531
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539







+







  froot = db_int(0, "SELECT froot FROM forumpost WHERE fpid=%d", fpid);
  if( froot==0 ){
    fossil_fatal("Not a forum post: \"%s\"", zName);
  }
  fossil_print("fpid  = %d\n", fpid);
  fossil_print("froot = %d\n", froot);
  pThread = forumthread_create(froot, 1);
  fossil_print("count = %d\n", pThread->nArtifact);
  fossil_print("Chronological:\n");
  fossil_print(
/* 0         1         2         3         4         5         6         7    */
/*  123456789 123456789 123456789 123456789 123456789 123456789 123456789 123 */
  " sid  rev  closed      fpid      pIrt pEditPrev pEditTail hash\n");
  for(p=pThread->pFirst; p; p=p->pNext){
    fossil_print("%4d %4d %7d %9d %9d %9d %9d %8.8s\n",
563
564
565
566
567
568
569

570
571
572
573
574
575
576
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585







+







void forumthreadhashlist(void){
  int fpid;
  int froot;
  const char *zName = P("name");
  ForumThread *pThread;
  ForumPost *p;
  char *fuuid;
  Stmt q;

  login_check_credentials();
  if( !g.perm.Admin ){
    return;
  }
  if( zName==0 ){
    webpage_error("Missing \"name=\" query parameter");
597
598
599
600
601
602
603

















604
605
606
607
608
609
610
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  @ <pre>
  pThread = forumthread_create(froot, 1);
  for(p=pThread->pFirst; p; p=p->pNext){
    @ %h(p->zUuid)
  }
  forumthread_delete(pThread);
  @ </pre>
  @ <hr>
  @ <h2>Related FORUMPOST Table Content</h2>
  @ <table border="1" cellpadding="4" cellspacing="0">
  @ <tr><th>fpid<th>froot<th>fprev<th>firt<th>fmtime
  db_prepare(&q, "SELECT fpid, froot, fprev, firt, datetime(fmtime)"
                 "  FROM forumpost"
                 " WHERE froot=%d"
                 " ORDER BY fmtime", froot);
  while( db_step(&q)==SQLITE_ROW ){
    @ <tr><td>%d(db_column_int(&q,0))\
    @ <td>%d(db_column_int(&q,1))\
    @ <td>%d(db_column_int(&q,2))\
    @ <td>%d(db_column_int(&q,3))\
    @ <td>%h(db_column_text(&q,4))</tr>
  }
  @ </table>
  db_finalize(&q);
  style_finish_page();
}

/*
** Render a forum post for display
*/
void forum_render(
723
724
725
726
727
728
729

730
731
732
733
734
735
736
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763







+







}


/*
** Display a single post in a forum thread.
*/
static void forum_display_post(
  ForumThread *pThread, /* The thread that this post is a member of */
  ForumPost *p,         /* Forum post to display */
  int iIndentScale,     /* Indent scale factor */
  int bRaw,             /* True to omit the border */
  int bUnf,             /* True to leave the post unformatted */
  int bHist,            /* True if showing edit history */
  int bSelect,          /* True if this is the selected post */
  char *zQuery          /* Common query string */
745
746
747
748
749
750
751
752

753
754
755

756
757
758
759
760
761
762
772
773
774
775
776
777
778

779
780
781

782
783
784
785
786
787
788
789







-
+


-
+







  int iIndent;          /* Indent level */
  int iClosed;          /* True if (sub)thread is closed */
  const char *zMimetype;/* Formatting MIME type */

  /* Get the manifest for the post.  Abort if not found (e.g. shunned). */
  pManifest = manifest_get(p->fpid, CFTYPE_FORUM, 0);
  if( !pManifest ) return;
  iClosed = forumpost_is_closed(p, 1);
  iClosed = forumpost_is_closed(pThread, p, 1);
  /* When not in raw mode, create the border around the post. */
  if( !bRaw ){
    /* Open the <div> enclosing the post.  Set the class string to mark the post
    /* Open the <div> enclosing the post. Set the class string to mark the post
    ** as selected and/or obsolete. */
    iIndent = (p->pEditHead ? p->pEditHead->nIndent : p->nIndent)-1;
    @ <div id='forum%d(p->fpid)' class='forumTime\
    @ %s(bSelect ? " forumSel" : "")\
    @ %s(iClosed ? " forumClosed" : "")\
    @ %s(p->pEditTail ? " forumObs" : "")' \
    if( iIndent && iIndentScale ){
904
905
906
907
908
909
910
911


912
913
914
915
916
917
918
931
932
933
934
935
936
937

938
939
940
941
942
943
944
945
946







-
+
+







      if( bSelect && forumpost_may_close() && iClosed>=0 ){
        int iHead = forumpost_head_rid(p->fpid);
        @ <form method="post" \
        @  action='%R/forumpost_%s(iClosed > 0 ? "reopen" : "close")'>
        login_insert_csrf_secret();
        @ <input type="hidden" name="fpid" value="%z(rid_to_uuid(iHead))" />
        if( moderation_pending(p->fpid)==0 ){
          @ <input type="submit" value='%s(iClosed ? "Re-open" : "Close")' />
          @ <input type="button" value='%s(iClosed ? "Re-open" : "Close")' \
          @  class='%s(iClosed ? "action-reopen" : "action-close")'/>
        }
        @ </form>
      }
      @ </div>
    }
    @ </div>
  }
1025
1026
1027
1028
1029
1030
1031
1032

1033
1034
1035
1036
1037
1038
1039
1053
1054
1055
1056
1057
1058
1059

1060
1061
1062
1063
1064
1065
1066
1067







-
+







    p = mode==FD_CHRONO ? pThread->pFirst : pThread->pDisplay;
    if( !bHist && p->pEditTail ) p = p->pEditTail;
  }

  /* Display the appropriate subset of posts in sequence. */
  while( p ){
    /* Display the post. */
    forum_display_post(p, iIndentScale, mode==FD_RAW,
    forum_display_post(pThread, p, iIndentScale, mode==FD_RAW,
        bUnf, bHist, p==pSelect, zQuery);

    /* Advance to the next post in the thread. */
    if( mode==FD_CHRONO ){
      /* Chronological mode: display posts (optionally including edits) in their
      ** original commit order. */
      if( bHist ){
1085
1086
1087
1088
1089
1090
1091
1092


1093
1094
1095
1096
1097
1098
1099
1113
1114
1115
1116
1117
1118
1119

1120
1121
1122
1123
1124
1125
1126
1127
1128







-
+
+








/*
** Emit Forum Javascript which applies (or optionally can apply)
** to all forum-related pages. It does not include page-specific
** code (e.g. "forum.js").
*/
static void forum_emit_js(void){
  builtin_fossil_js_bundle_or("copybutton", "pikchr", NULL);
  builtin_fossil_js_bundle_or("copybutton", "pikchr", "confirmer",
                              NULL);
  builtin_request_js("fossil.page.forumpost.js");
}

/*
** WEBPAGE: forumpost
**
** Show a single forum posting. The posting is shown in context with
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
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
1862
1863
1864
1865
1866
1867
1868
1869
1870







1871
1872
1873
1874
1875
1876
















1877
1878
1879
1880
1881
1882
1883
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

1840
1841
1842
1843
1844
1845
1846
1847
1848

1849
1850
1851
1852
1853
1854
1855





1856
1857
1858
1859
1860

1861

1862
1863

1864

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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







-
-
-
+
+
+


















-


-









-







-
-
-
-
-



+
+
-
+
-
+

-

-
+
+







-







-
-
-
-
-



-


-

-
-
-
-
+
+
+
+
+
+
+
+
+
+
+






-



+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  forum_render_debug_options();
  login_insert_csrf_secret();
  @ </form>
  forum_emit_js();
  style_finish_page();
}

/*
** SETTING: forum-close-policy    boolean default=off
** If true, forum moderators may close/re-open forum posts, and reply
** to closed posts. If false, only administrators may do so. Note that
** this only affects the forum web UI, not post-closing tags which
** arrive via the command-line or from synchronization with a remote.
*/
/*
** SETTING: forum-title          width=20 default=Forum
** This is the name or "title" of the Forum for this repository.  The
** default is just "Forum".  But in some setups, admins might want to
** change it to "Developer Forum" or "User Forum" or whatever other name
** seems more appropriate for the particular usage.
*/

/*
** WEBPAGE: setup_forum
**
** Forum configuration and metrics.
*/
void forum_setup(void){
  /* boolean config settings specific to the forum. */
  const char * zSettingsBool[] = {
  "forum-close-policy",
  NULL /* sentinel entry */
  static const char *azForumSettings[] =  {
    "forum-close-policy",
    "forum-title",
  };

  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(g.anon.Setup);
    return;
  }
  style_set_current_feature("forum");
  style_header("Forum Setup");

  @ <h2>Metrics</h2>
  {
    int nPosts = db_int(0, "SELECT COUNT(*) FROM event WHERE type='f'");
    @ <p><a href='%R/forum'>Forum posts</a>:
    @ <a href='%R/timeline?y=f'>%d(nPosts)</a></p>
  }

  @ <h2>Supervisors</h2>
  @ <p>Users with capabilities 's', 'a', or '6'.</p>
  {
    Stmt q = empty_Stmt;
    int nRows = 0;
    db_prepare(&q, "SELECT uid, login, cap FROM user "
                   "WHERE cap GLOB '*[as6]*' ORDER BY login");
    @ <table class='bordered'>
    @ <thead><tr><th>User</th><th>Capabilities</th></tr></thead>
    @ <tbody>
    while( SQLITE_ROW==db_step(&q) ){
      const int iUid = db_column_int(&q, 0);
      const char *zUser = db_column_text(&q, 1);
      const char *zCap = db_column_text(&q, 2);
      ++nRows;
      @ <tr>
      @ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td>
      @ <td>(%h(zCap))</td>
      @ </tr>
    }
    db_finalize(&q);
    @</tbody></table>
    if( 0==nRows ){
      @ No supervisors
    }else{
      @ %d(nRows) supervisor(s)
    }
  }

  @ <h2>Moderators</h2>
  if( db_int(0, "SELECT count(*) FROM user "
                " WHERE cap GLOB '*5*' AND cap NOT GLOB '*[as6]*'")==0 ){
  @ <p>Users with capability '5'.</p>
      @ <p>No non-supervisor moderators
  {
  }else{
    Stmt q = empty_Stmt;
    int nRows = 0;
    db_prepare(&q, "SELECT uid, login, cap FROM user "
               "WHERE cap GLOB '*5*' ORDER BY login");
               "WHERE cap GLOB '*5*' AND cap NOT GLOB '*[as6]*'"
               " ORDER BY login");
    @ <table class='bordered'>
    @ <thead><tr><th>User</th><th>Capabilities</th></tr></thead>
    @ <tbody>
    while( SQLITE_ROW==db_step(&q) ){
      const int iUid = db_column_int(&q, 0);
      const char *zUser = db_column_text(&q, 1);
      const char *zCap = db_column_text(&q, 2);
      ++nRows;
      @ <tr>
      @ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td>
      @ <td>(%h(zCap))</td>
      @ </tr>
    }
    db_finalize(&q);
    @ </tbody></table>
    if( 0==nRows ){
      @ No non-supervisor moderators
    }else{
      @ %d(nRows) moderator(s)
    }
  }

  @ <h2>Settings</h2>
  @ <p>Configuration settings specific to the forum.</p>
  if( P("submit") && cgi_csrf_safe(2) ){
    int i = 0;
    const char *zSetting;
    db_begin_transaction();
    while( (zSetting = zSettingsBool[i++]) ){
      const char *z = P(zSetting);
      if( !z || !z[0] ) z = "off";
      db_set(zSetting/*works-like:"x"*/, z, 0);
    for(i=0; i<ArraySize(azForumSettings); i++){
      char zQP[4];
      const char *z;
      const Setting *pSetting = setting_find(azForumSettings[i]);
      if( pSetting==0 ) continue;
      zQP[0] = 'a'+i;
      zQP[1] = zQP[0];
      zQP[2] = 0;
      z = P(zQP);
      if( z==0 || z[0]==0 ) continue;
      db_set(pSetting->name/*works-like:"x"*/, z, 0);
    }
    db_end_transaction(0);
    @ <p><em>Settings saved.</em></p>
  }
  {
    int i = 0;
    const char *zSetting;
    @ <form action="%R/setup_forum" method="post">
    login_insert_csrf_secret();
    @ <table class='forum-settings-list'><tbody>
    for(i=0; i<ArraySize(azForumSettings); i++){
      char zQP[4];
      const Setting *pSetting = setting_find(azForumSettings[i]);
      if( pSetting==0 ) continue;
      zQP[0] = 'a'+i;
      zQP[1] = zQP[0];
      zQP[2] = 0;
    while( (zSetting = zSettingsBool[i++]) ){
      @ <tr><td>
      onoff_attribute("", zSetting, zSetting/*works-like:"x"*/, 0, 0);
      @ </td><td>
      @ <a href='%R/help?cmd=%h(zSetting)'>%h(zSetting)</a>
      @ </td></tr>
      if( pSetting->width==0 ){
        /* Boolean setting */
        @ <tr><td align="right">
        @ <a href='%R/help/%h(pSetting->name)'>%h(pSetting->name)</a>:
        @ </td><td>
        onoff_attribute("", zQP, pSetting->name/*works-like:"x"*/, 0, 0);
        @ </td></tr>
      }else{
        /* Text value setting */
        @ <tr><td align="right">
        @ <a href='%R/help/%h(pSetting->name)'>%h(pSetting->name)</a>:
        @ </td><td>
        entry_attribute("", 25, pSetting->name, zQP/*works-like:""*/,
                        pSetting->def, 0);
        @ </td></tr>
      }   
    }
    @ </tbody></table>
    @ <input type='submit' name='submit' value='Apply changes'>
    @ </form>
  }

  style_finish_page();
1908
1909
1910
1911
1912
1913
1914

1915

1916
1917
1918
1919
1920
1921
1922
1961
1962
1963
1964
1965
1966
1967
1968

1969
1970
1971
1972
1973
1974
1975
1976







+
-
+







  srchFlags = search_restrict(SRCH_FORUM);
  if( !g.perm.RdForum ){
    login_needed(g.anon.RdForum);
    return;
  }
  cgi_check_for_malice();
  style_set_current_feature("forum");
  style_header("%s%s", db_get("forum-title","Forum"), 
  style_header( "%s", isSearch ? "Forum Search Results" : "Forum" );
                       isSearch ? " Search Results" : "");
  style_submenu_element("Timeline", "%R/timeline?ss=v&y=f&vfx");
  if( g.perm.WrForum ){
    style_submenu_element("New Thread","%R/forumnew");
  }else{
    /* Can't combine this with previous case using the ternary operator
     * because that causes an error yelling about "non-constant format"
     * with some compilers.  I can't see it, since both expressions have
Changes to src/fossil.confirmer.js.
166
167
168
169
170
171
172
173

174
175
176
177
178
179
180
166
167
168
169
170
171
172

173
174
175
176
177
178
179
180







-
+







            childs = childs ? Array.prototype.slice.call(childs.childNodes, 0) : [];
            target.innerText = '';
            childs.forEach((e)=>target.appendChild(e));
          }
        }
        const formatCountdown = (txt, number) => txt + " ["+number+"]";
        if(opt.pinSize && opt.confirmText){
          /* Try to pin the element's width the the greater of its
          /* Try to pin the element's width to the greater of its
             current width or its waiting-on-confirmation width
             to avoid layout reflow when it's activated. */
          const digits = (''+(opt.timeout/1000 || opt.ticks)).length;
          const lblLong = formatCountdown(opt.confirmText, "00000000".substr(0,digits+1));
          const w1 = parseInt(target.getBoundingClientRect().width);
          updateText(lblLong);
          const w2 = parseInt(target.getBoundingClientRect().width);
Changes to src/fossil.copybutton.js.
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
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







-
+
-
-

















-
-
+
+
-
-
-









-







     .style: optional object of properties to copy directly into
     e.style.

     .oncopy: an optional callback function which is added as an event
     listener for the 'text-copied' event (see below). There is
     functionally no difference from setting this option or adding a
     'text-copied' event listener to the element, and this option is
     considered to be a convenience form of that. For the sake of
     considered to be a convenience form of that.
     framework-level consistency, the default value is a callback
     which passes the copy button to fossil.dom.flashOnce().

     Note that this function's own defaultOptions object holds default
     values for some options. Any changes made to that object affect
     any future calls to this function.

     Be aware that clipboard functionality might or might not be
     available in any given environment. If this button appears to
     have no effect, that may be because it is not enabled/available
     in the current platform.

     The copy button emits custom event 'text-copied' after it has
     successfully copied text to the clipboard. The event's "detail"
     member is an object with a "text" property holding the copied
     text. Other properties may be added in the future. The event is
     not fired if copying to the clipboard fails (e.g. is not
     available in the current environment).

     As a special case, the copy button's click handler is suppressed
     (becomes a no-op) for as long as the element has the CSS class
     The copy button's click handler is suppressed (becomes a no-op)
     for as long as the element has the "disabled" attribute.
     "disabled". This allows elements which cannot be disabled via
     HTML attributes, e.g. a SPAN, to act as a copy button while still
     providing a way to disable them.

     Returns the copy-initialized element.

     Example:

     const button = fossil.copyButton('#my-copy-button', {
       copyFromId: 'some-other-element-id'
     });
     button.addEventListener('text-copied',function(ev){
       fossil.dom.flashOnce(ev.target);
       console.debug("Copied text:",ev.detail.text);
     });
  */
  F.copyButton = function f(e, opt){
    if('string'===typeof e){
      e = document.querySelector(e);
    }
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
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







-
+












+
+
+
+





-
+




    );
    D.copyStyle(e, opt.style);
    e.addEventListener(
      'click',
      function(ev){
        ev.preventDefault();
        ev.stopPropagation();
        if(e.classList.contains('disabled')) return;
        if(e.disabled) return;  /* This check is probably redundant. */
        const txt = extract.call(opt);
        if(txt && D.copyTextToClipboard(txt)){
          e.dispatchEvent(new CustomEvent('text-copied',{
            detail: {text: txt}
          }));
        }
      },
      false
    );
    if('function' === typeof opt.oncopy){
      e.addEventListener('text-copied', opt.oncopy, false);
    }
    /* Make sure the <button> contains a single nested <span>. */
    if(e.childElementCount!=1 || e.firstChild.tagName!='SPAN'){
      D.append(D.clearElement(e), D.span());
    }
    return e;
  };

  F.copyButton.defaultOptions = {
    cssClass: 'copy-button',
    oncopy: D.flashOnce.eventHandler,
    oncopy: undefined,
    style: {/*properties copied as-is into element.style*/}
  };
  
})(window.fossil);
Changes to src/fossil.diff.js.
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
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




+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





+
+
+

-
+
+
+
+
+
+
+


-
+

-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+

+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+












-
+







/**
   diff-related JS APIs for fossil.
*/
"use strict";
/* Locate the UI element (if any) into which we can inject some diff-related
   UI controls. */
window.fossil.onPageLoad(function(){
  const potentialParents = window.fossil.page.diffControlContainers = [
    /* CSS selectors for possible parents for injected diff-related UI
       controls. */
    /* Put the most likely pages at the end, as array.pop() is more
       efficient than array.shift() (see loop below). */
    /* /filedit */ 'body.cpage-fileedit #fileedit-tab-diff-buttons',
    /* /wikiedit */ 'body.cpage-wikiedit #wikiedit-tab-diff-buttons',
    /* /fdiff */ 'body.fdiff form div.submenu',
    /* /vdiff */ 'body.vdiff form div.submenu',
    /* /info, /vinfo, /ckout */ 'body.vinfo div.sectionmenu.info-changes-menu'
  ];
  window.fossil.page.diffControlContainer = undefined;
  while( potentialParents.length ){
    if( (window.fossil.page.diffControlContainer
         = document.querySelector(potentialParents.pop())) ){
      break;
    }
  }
});

window.fossil.onPageLoad(function(){
  /**
     Adds toggle checkboxes to each file entry in the diff views for
     /info and similar pages.
  */
  if( !window.fossil.page.diffControlContainer ){
    return;
  }
  const D = window.fossil.dom;
  const isFdiff = !!document.querySelector('body.fdiff');
  const allToggles = [/*collection of all diff-toggle checkboxes*/];
  let checkedCount =
      0 /* When showing more than one diff, keep track of how many
           "show/hide" checkboxes are checked so we can update the
           "show/hide all" label dynamically. */;
  let btnAll /* UI control to show/hide all diffs */;
  /* Install a diff-toggle button for the given diff table element. */
  const addToggle = function(diffElem){
    const sib = diffElem.previousElementSibling,
          btn = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
          ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
    if(!sib) return;
    if(isFdiff) sib.parentElement.insertBefore(
                  D.append(D.div(),btn),sib.nextElementSibling);
    else D.append(sib,btn);
    btn.addEventListener('click', function(){
      diffElem.classList.toggle('hidden');
    const lblToggle = D.label();
    D.append(lblToggle, ckbox, D.text(" show/hide "));
    allToggles.push(ckbox);
    ++checkedCount;
    /* Make all of the available empty space a click zone for the checkbox */
    lblToggle.style.flexGrow = 1;
    lblToggle.style.textAlign = 'right';
    D.append(sib, lblToggle);
    ckbox.addEventListener('change', function(){
      diffElem.classList[this.checked ? 'remove' : 'add']('hidden');
      if(btnAll){
        checkedCount += (this.checked ? 1 : -1);
        btnAll.innerText = (checkedCount < allToggles.length)
          ? "Show diffs" : "Hide diffs";
      }
    }, false);
    /* Extend the toggle click zone to all of the non-hyperlink
       elements in the left of this area (filenames and hashes). */
    sib.firstElementChild.addEventListener('click', (event)=>{
      if( event.target===sib.firstElementChild ){
        /* Don't respond to clicks bubbling via hyperlink children */
        ckbox.click();;
      }
    }, false);
  };
  if( !document.querySelector('body.fdiff') ){
    /* Don't show the diff toggle button for /fdiff because it only
       has a single file to show (and also a different DOM layout). */
  document.querySelectorAll('table.diff').forEach(addToggle);
    document.querySelectorAll('table.diff').forEach(addToggle);
  }
  /**
     Set up a "toggle all diffs" button which toggles all of the
     above-installed checkboxes, but only if more than one diff is
     rendered.
  */
  const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0;
  if(icm) {
    btnAll = D.addClass(D.a("#", "Hide diffs"), "button");
    D.append( icm, btnAll );
    btnAll.addEventListener('click', function(ev){
      ev.preventDefault();
      ev.stopPropagation();
      const show = checkedCount < allToggles.length;
      for( const ckbox of allToggles ){
        /* Toggle all entries to match this new state. We use click()
           instead of ckbox.checked=... so that the on-change event handler
           fires. */
        if(ckbox.checked!==show) ckbox.click();
      }
    }, false);
  }
});

window.fossil.onPageLoad(function(){
  const F = window.fossil, D = F.dom;
  const Diff = F.diff = {
    e:{/*certain cached DOM elements*/},
    config: {
      chunkLoadLines: (
        F.config.diffContextLines * 3
        /*per /chat discussion*/
      ) || 20,
      chunkFetch: {
        /* Default callack handlers for Diff.fetchArtifactChunk(),
        /* Default callback handlers for Diff.fetchArtifactChunk(),
           unless overridden by options passeed to that function. */
        beforesend: function(){},
        aftersend: function(){},
        onerror: function(e){
          console.error("XHR error: ",e);
        }
      }
291
292
293
294
295
296
297
298

299
300
301
302
303
304
305
366
367
368
369
370
371
372

373
374
375
376
377
378
379
380







-
+







          this.$fetchQueue.length = 2;
        }
      }
      return this;
    },

    /**
       Callack for /jchunk responses.
       Callback for /jchunk responses.
    */
    injectResponse: function f(fetchType/*as for fetchChunk()*/,
                               urlParam/*from fetchChunk()*/,
                               lines/*response lines*/){
      if(!lines.length){
        /* No more data to load */
        this.destroy();
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
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







-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
+



-
+






-
+







  const F = window.fossil, D = F.dom, Diff = F.diff;

  /* Look for a parent element to hold the sbs-sync-scroll toggle
     checkbox.  This differs per page. If we don't find one, simply
     elide that toggle and use whatever preference the user last
     specified (defaulting to on). */
  let cbSync /* scroll-sync checkbox */;
  let eToggleParent /* element to put the sync-scroll checkbox in */;
  let eToggleParent = /* element to put the sync-scroll checkbox in */
  const potentialParents = [ /* possible parents for the checkbox */
    /* Put the most likely pages at the end, as array.pop() is more
       efficient than array.shift() (see loop below). */
    /* /filedit */ 'body.cpage-fileedit #fileedit-tab-diff-buttons',
    /* /wikiedit */ 'body.cpage-wikiedit #wikiedit-tab-diff-buttons',
    /* /fdiff */ 'body.fdiff form div.submenu',
    /* /vdiff */ 'body.vdiff form div.submenu',
    /* /info, /vinfo */ 'body.vinfo div.sectionmenu.info-changes-menu'
  ];
  while( potentialParents.length ){
    if( (eToggleParent = document.querySelector(potentialParents.pop())) ){
      break;
      document.querySelector('table.diff.splitdiff')
      ? window.fossil.page.diffControlContainer
      : undefined;
    }
  }
  const keySbsScroll = 'sync-diff-scroll' /* F.storage key */;
  const keySbsScroll = 'sync-diff-scroll' /* F.storage key for persistent user preference */;
  if( eToggleParent ){
    /* Add a checkbox to toggle sbs scroll sync. Remember that in
       order to be UI-consistent in the /vdiff page we have to ensure
       that the checkbox is to the LEFT of of its label. We store the
       that the checkbox is to the LEFT of its label. We store the
       sync-scroll preference in F.storage (not a cookie) so that it
       persists across page loads and different apps. */
    cbSync = D.checkbox(keySbsScroll, F.storage.getBool(keySbsScroll,true));
    D.append(eToggleParent, D.append(
      D.addClass(D.create('span'), 'input-with-label'),
      D.append(D.create('label'),
               cbSync, "Sync side-by-side scrolling")
               cbSync, "Scroll Sync")
    ));
    cbSync.addEventListener('change', function(e){
      F.storage.set(keySbsScroll, e.target.checked);
    });
  }
  const useSync = cbSync ? ()=>cbSync.checked : ()=>F.storage.getBool(keySbsScroll,true);

Changes to src/fossil.dom.js.
15
16
17
18
19
20
21
22

23
24

25
26
27

28
29
30
31
32
33
34
15
16
17
18
19
20
21

22
23

24
25
26

27
28
29
30
31
32
33
34







-
+

-
+


-
+







    },
    createElemFactory: function(eType){
      return function(){
        return document.createElement(eType);
      };
    },
    remove: function(e){
      if(e.forEach){
      if(e?.forEach){
        e.forEach(
          (x)=>x.parentNode.removeChild(x)
          (x)=>x?.parentNode?.removeChild(x)
        );
      }else{
        e.parentNode.removeChild(e);
        e?.parentNode?.removeChild(e);
      }
      return e;
    },
    /**
       Removes all child DOM elements from the given element
       and returns that element.

85
86
87
88
89
90
91

92


93
94
95
96
97
98
99
85
86
87
88
89
90
91
92

93
94
95
96
97
98
99
100
101







+
-
+
+







  */
  dom.label = function(forElem, text){
    const rc = document.createElement('label');
    if(forElem){
      if(forElem instanceof HTMLElement){
        forElem = this.attr(forElem, 'id');
      }
      if(forElem){
      dom.attr(rc, 'for', forElem);
        dom.attr(rc, 'for', forElem);
      }
    }
    if(text) this.append(rc, text);
    return rc;
  };
  /**
     Returns an IMG element with an optional src
     attribute value.
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
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







-
+










-
+







  dom.createElemFactoryWithOptionalParent = function(childType){
    return function(parent){
      const e = this.create(childType);
      if(parent) parent.appendChild(e);
      return e;
    };
  };
  

  dom.table = dom.createElemFactory('table');
  dom.thead = dom.createElemFactoryWithOptionalParent('thead');
  dom.tbody = dom.createElemFactoryWithOptionalParent('tbody');
  dom.tfoot = dom.createElemFactoryWithOptionalParent('tfoot');
  dom.tr = dom.createElemFactoryWithOptionalParent('tr');
  dom.td = dom.createElemFactoryWithOptionalParent('td');
  dom.th = dom.createElemFactoryWithOptionalParent('th');

  /**
     Creates and returns a FIELDSET element, optionaly with a LEGEND
     element added to it. If legendText is an HTMLElement then is is
     element added to it. If legendText is an HTMLElement then it is
     assumed to be a LEGEND and is appended as-is, else it is assumed
     (if truthy) to be a value suitable for passing to
     dom.append(aLegendElement,...).
  */
  dom.fieldset = function(legendText){
    const fs = this.create('fieldset');
    if(legendText){
377
378
379
380
381
382
383
384
385


386
387
388
389
390
391
392
379
380
381
382
383
384
385


386
387
388
389
390
391
392
393
394







-
-
+
+







     Internal impl for addClass(), removeClass().
  */
  const domAddRemoveClass = function f(action,e){
    if(!f.rxSPlus){
      f.rxSPlus = /\s+/;
      f.applyAction = function(e,a,v){
        if(!e || !v
           /*silently skip empty strings/flasy
             values, for user convenience*/) return;
           /*silently skip empty strings/falsy
             values, for usage convenience*/) return;
        else if(e.forEach){
          e.forEach((E)=>E.classList[a](v));
        }else{
          e.classList[a](v);
        }
      };
    }
580
581
582
583
584
585
586

587
588
589
590
591
592
593
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596







+







      }else{
        e.setAttribute(key,val);
      }
    }
    return e;
  };

  /* Impl for dom.enable() and dom.disable(). */
  const enableDisable = function f(enable){
    var i = 1, n = arguments.length;
    for( ; i < n; ++i ){
      let e = arguments[i];
      if(e.forEach){
        e.forEach((x)=>f(enable,x));
      }else{
839
840
841
842
843
844
845
846
847


848
849
850
851
852
853
854
842
843
844
845
846
847
848


849
850
851
852
853
854
855
856
857







-
-
+
+







  };

  /**
     Parses a string as HTML.

     Usages:

     Array (htmlString)
     DOMElement (DOMElement target, htmlString)
     Array parseHtml(htmlString)
     DOMElement parseHtml(DOMElement target, htmlString)

     The first form parses the string as HTML and returns an Array of
     all elements parsed from it. If string is falsy then it returns
     an empty array.

     The second form parses the HTML string and appends all elements
     to the given target element using dom.append(), then returns the
Changes to src/fossil.fetch.js.
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
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







-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+











-
+
+











+
+
+















-
+







   - onload: callback(responseData) (default = output response to the
   console). In the context of the callback, the options object is
   "this", noting that this call may have amended the options object
   with state other than what the caller provided.

   - onerror: callback(Error object) (default = output error message
   to console.error() and fossil.error()). Triggered if the request
   generates any response other than HTTP 200, suffers a connection
   generates any response other than HTTP 200, or if the beforesend()
   error or timeout while awaiting a response, or if the onload()
   handler throws an exception. In the context of the callback, the
   options object is "this". Note that this function is intended to be
   used solely for error reporting, not error recovery. Because
   onerror() may be called if onload() throws, it is up to the caller
   to ensure that their onerror() callback references only state which
   is valid in such a case.
   or onload() handler throws an exception. In the context of the
   callback, the options object is "this". This function is intended
   to be used solely for error reporting, not error recovery. Special
   cases for the Error object:

       1. Timeouts unfortunately show up as a series of 2 events: an
       HTTP 0 followed immediately by an XHR.ontimeout(). The former
       cannot(?) be unambiguously identified as the trigger for the
       pending timeout, so we have no option but to pass it on as-is
       instead of flagging it as a timeout response. The latter will
       trigger the client-provided ontimeout() if it's available (see
       below), else it calls the onerror() callback. An error object
       passed to ontimeout() by fetch() will have (.name='timeout',
       .status=XHR.status).

       2. Else if the response contains a JSON-format exception on the
       server, it will have (.name='json-error',
       status=XHR.status). Any JSON-format result object which has a
       property named "error" is considered to be a server-generated
       error.

       3. Else if it gets a non 2xx HTTP code then it will have
       (.name='http',.status=XHR.status).

       4. If onerror() throws, the exception is suppressed but may
       generate a console error message.

   - ontimeout: callback(Error object). If set, timeout errors are
   reported here, else they are reported through onerror().
   Unfortunately, XHR fires two events for a timeout: an
   onreadystatechange() and an ontimeout(), in that order.  From the
   former, however, we cannot unambiguously identify the error as
   having been caused by a timeout, so clients which set ontimeout()
   will get _two_ callback calls: one with an HTTP error response
   followed immediately by an ontimeout() response. Error objects
   passed to this will have (.name='timeout', .status=xhr.HttpStatus).
   In the context of the callback, the options object is "this", Like
   onerror(), any exceptions thrown by the ontimeout() handler are
   suppressed, but may generate a console error message. The onerror()
   handler is _not_ called in this case.

   - method: 'POST' | 'GET' (default = 'GET'). CASE SENSITIVE!

   - payload: anything acceptable by XHR2.send(ARG) (DOMString,
   Document, FormData, Blob, File, ArrayBuffer), or a plain object or
   array, either of which gets JSON.stringify()'d. If payload is set
   then the method is automatically set to 'POST'. By default XHR2
   will set the content type based on the payload type. If an
   object/array is converted to JSON, the contentType option is
   automatically set to 'application/json', and if JSON.stringify() of
   that value fails then the exception is propagated to this
   function's caller.
   function's caller. (beforesend(), aftersend(), and onerror() are
   NOT triggered in that case.)

   - contentType: Optional request content type when POSTing. Ignored
   if the method is not 'POST'.

   - responseType: optional string. One of ("text", "arraybuffer",
   "blob", or "document") (as specified by XHR2). Default = "text".
   As an extension, it supports "json", which tells it that the
   response is expected to be text and that it should be JSON.parse()d
   before passing it on to the onload() callback. If parsing of such
   an object fails, the onload callback is not called, and the
   onerror() callback is passed the exception from the parsing error.
   If the parsed JSON object has an "error" property, it is assumed to
   be an error string, which is used to populate a new Error object,
   which will gets (.name="json") set on it.

   - urlParams: string|object. If a string, it is assumed to be a
   URI-encoded list of params in the form "key1=val1&key2=val2...",
   with NO leading '?'.  If it is an object, all of its properties get
   converted to that form. Either way, the parameters get appended to
   the URL before submitting the request.

   - responseHeaders: If true, the onload() callback is passed an
   additional argument: a map of all of the response headers. If it's
   a string value, the 2nd argument passed to onload() is instead the
   value of that single header. If it's an array, it's treated as a
   list of headers to return, and the 2nd argument is a map of those
   header values. When a map is passed on, all of its keys are
   lower-cased. When a given header is requested and that header is
   set multiple times, their values are (per the XHR docs)
   concatenated together with ", " between them.
   concatenated together with "," between them.

   - beforesend/aftersend: optional callbacks which are called
   without arguments immediately before the request is submitted
   and immediately after it is received, regardless of success or
   error. In the context of the callback, the options object is
   the "this". These can be used to, e.g., keep track of in-flight
   requests and update the UI accordingly, e.g. disabling/enabling
131
132
133
134
135
136
137
138

139
140
141
142
143
144
145
168
169
170
171
172
173
174

175
176
177
178
179
180
181
182







-
+







        const value = parts.join(': ');
        rc[header.toLowerCase()] = value;
      });
      return rc;
    };
  }
  if('/'===uri[0]) uri = uri.substr(1);
  if(!opt) opt = {};
  if(!opt) opt = {}/* should arguably be Object.create(null) */;
  else if('function'===typeof opt) opt={onload:opt};
  if(!opt.onload) opt.onload = f.onload;
  if(!opt.onerror) opt.onerror = f.onerror;
  if(!opt.beforesend) opt.beforesend = f.beforesend;
  if(!opt.aftersend) opt.aftersend = f.aftersend;
  let payload = opt.payload, jsonResponse = false;
  if(undefined!==payload){
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
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







-
+

-
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
-
+
+











-
+
+
+
+
+



+



+
-
+
+
+

+
+
+
+
+
-
+
+
+
+
+



















-
-
+
+

-
+

-
-
+
+







       list. We use it as a flag to tell us to JSON.parse()
       the response. */
    jsonResponse = true;
    x.responseType = 'text';
  }else{
    x.responseType = opt.responseType||'text';
  }
  x.ontimeout = function(){
  x.ontimeout = function(ev){
    try{opt.aftersend()}catch(e){/*ignore*/}
    opt.onerror(new Error("XHR timeout of "+x.timeout+"ms expired."));
    const err = new Error("XHR timeout of "+x.timeout+"ms expired.");
    err.status = x.status;
    err.name = 'timeout';
    //console.warn("fetch.ontimeout",ev);
    try{
      (opt.ontimeout || opt.onerror)(err);
    }catch(e){
      /*ignore*/
      console.error("fossil.fetch()'s ontimeout() handler threw",e);
    }
  };
  /* Ensure that if onerror() throws, it's ignored. */
  const origOnError = opt.onerror;
  opt.onerror = (arg)=>{
    try{ origOnError.call(this, arg) }
    catch(e){
      /*ignored*/
      console.error("fossil.fetch()'s onerror() threw",e);
    }
  };
  x.onreadystatechange = function(){
  x.onreadystatechange = function(ev){
    //console.warn("onreadystatechange", x.readyState, ev.target.responseText);
    if(XMLHttpRequest.DONE !== x.readyState) return;
    try{opt.aftersend()}catch(e){/*ignore*/}
    if(false && 0===x.status){
      /* For reasons unknown, we _sometimes_ trigger x.status==0 in FF
         when the /chat page starts up, but not in Chrome nor in other
         apps. Insofar as has been determined, this happens before a
         request is actually sent and it appears to have no
         side-effects on the app other than to generate an error
         (i.e. no requests/responses are missing). This is a silly
         workaround which may or may not bite us later. If so, it can
         be removed at the cost of an unsightly console error message
         in FF. */
         in FF.

         2025-04-10: that behavior is now also in Chrome and enabling
         this workaround causes our timeout errors to never arrive.
      */
      return;
    }
    if(200!==x.status){
      //console.warn("Error response",ev.target);
      let err;
      try{
        const j = JSON.parse(x.response);
        if(j.error){
        if(j.error) err = new Error(j.error);
          err = new Error(j.error);
          err.name = 'json.error';
        }
      }catch(ex){/*ignore*/}
      if( !err ){
        /* We can't tell from here whether this was a timeout-capable
           request which timed out on our end or was one which is a
           genuine error. We also don't know whether the server timed
           out the connection before we did. */
      opt.onerror(err || new Error("HTTP response status "+x.status+"."));
        err = new Error("HTTP response status "+x.status+".")
        err.name = 'http';
      }
      err.status = x.status;
      opt.onerror(err);
      return;
    }
    const orh = opt.responseHeaders;
    let head;
    if(true===orh){
      head = f.parseResponseHeaders(x.getAllResponseHeaders());
    }else if('string'===typeof orh){
      head = x.getResponseHeader(orh);
    }else if(orh instanceof Array){
      head = {};
      orh.forEach((s)=>{
        if('string' === typeof s) head[s.toLowerCase()] = x.getResponseHeader(s);
      });
    }
    try{
      const args = [(jsonResponse && x.response)
                    ? JSON.parse(x.response) : x.response];
      if(head) args.push(head);
      opt.onload.apply(opt, args);
    }catch(e){
      opt.onerror(e);
    }catch(err){
      opt.onerror(err);
    }
  };
  }/*onreadystatechange()*/;
  try{opt.beforesend()}
  catch(e){
    opt.onerror(e);
  catch(err){
    opt.onerror(err);
    return;
  }
  x.open(opt.method||'GET', url.join(''), true);
  if('POST'===opt.method && 'string'===typeof opt.contentType){
    x.setRequestHeader('Content-Type',opt.contentType);
  }
  x.timeout = +opt.timeout || f.timeout;
Changes to src/fossil.numbered-lines.js.
19
20
21
22
23
24
25
26
27
28
29

30
31
32
33
34
35
36
37
38
39
40
19
20
21
22
23
24
25




26
27



28
29
30
31
32
33
34







-
-
-
-
+

-
-
-







  if(!tbl) return /* no matching elements */;
  const F = window.fossil, D = F.dom;
  const tdLn = tbl.querySelector('td.line-numbers');
  const urlArgsRaw = (window.location.search||'?')
      .replace(/&?\budc=[^&]*/,'') /* "update display prefs cookie" */
      .replace(/&?\bln=[^&]*/,'') /* inbound line number/range */
      .replace('?&','?');
  var urlArgsDecoded = urlArgsRaw;
  try{urlArgsDecoded = decodeURIComponent(urlArgsRaw);}
  catch{}
  const lineState = { urlArgs: urlArgsDecoded, start: 0, end: 0 };
  const lineState = { urlArgs: urlArgsRaw, start: 0, end: 0 };
  const lineTip = new F.PopupWidget({
    style: {
      cursor: 'pointer'
    },
    refresh: function(){
      const link = this.state.link;
      D.clearElement(link);
      if(lineState.start){
        const ls = [lineState.start];
        if(lineState.end) ls.push(lineState.end);
        link.dataset.url = (
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
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







-
-
+
+





-
+



-
-
+







        );
      }else{
        D.append(link, "No lines selected.");
      }
    },
    init: function(){
      const e = this.e;
      const btnCopy = D.span(),
            link = D.span();
      const btnCopy = D.attr(D.button(), 'id', 'linenum-copy-button');
            link = D.label('linenum-copy-button');
      this.state = {link};
      F.copyButton(btnCopy,{
        copyFromElement: link,
        extractText: ()=>link.dataset.url,
        oncopy: (ev)=>{
          D.flashOnce(ev.target, undefined, ()=>lineTip.hide());
          setTimeout(()=>lineTip.hide(), 400);
          // arguably too snazzy: F.toast.message("Copied link to clipboard.");
        }
      });
      this.e.addEventListener('click', ()=>btnCopy.click(), false);
      D.append(this.e, btnCopy, link)
      D.append(this.e, btnCopy, link);
    }
  });

  tbl.addEventListener('click', ()=>lineTip.hide(), true);
  
  tdLn.addEventListener('click', function f(ev){
    if('SPAN'!==ev.target.tagName) return;
Changes to src/fossil.page.chat.js.
1

2
3
4
5
6
7
8

1
2
3
4
5
6
7
8
-
+







/**
-/**
   This file contains the client-side implementation of fossil's /chat
   application.
*/
window.fossil.onPageLoad(function(){
  const F = window.fossil, D = F.dom;
  const E1 = function(selector){
    const e = document.querySelector(selector);
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
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







+
+
-
-
+
+





-
-
+
+


+
+
+















-
+
+
+







    resized.$disabled = true/*gets deleted when setup is finished*/;
    window.addEventListener('resize', F.debounce(resized, 250), false);
    return resized;
  })();
  fossil.FRK = ForceResizeKludge/*for debugging*/;
  const Chat = ForceResizeKludge.chat = (function(){
    const cs = { // the "Chat" object (result of this function)
      beVerbose: false
      //!!window.location.hostname.match("localhost")
      verboseErrors: false /* if true then certain, mostly extraneous,
                              error messages may be sent to the console. */,
      /* if true then certain, mostly extraneous, error messages and
         log messages may be sent to the console. */,
      playedBeep: false /* used for the beep-once setting */,
      e:{/*map of certain DOM elements.*/
        messageInjectPoint: E1('#message-inject-point'),
        pageTitle: E1('head title'),
        loadOlderToolbar: undefined /* the load-posts toolbar (dynamically created) */,
        inputWrapper: E1("#chat-input-area"),
        inputElementWrapper: E1('#chat-input-line-wrapper'),
        inputArea: E1("#chat-input-area"),
        inputLineWrapper: E1('#chat-input-line-wrapper'),
        fileSelectWrapper: E1('#chat-input-file-area'),
        viewMessages: E1('#chat-messages-wrapper'),
        viewZoom: E1('#chat-zoom'),
        zoomContent: E1('#chat-zoom-content'),
        zoomMarker: E1('#chat-zoom-marker'),
        btnSubmit: E1('#chat-button-submit'),
        btnAttach: E1('#chat-button-attach'),
        inputX: E1('#chat-input-field-x'),
        input1: E1('#chat-input-field-single'),
        inputM: E1('#chat-input-field-multi'),
        inputFile: E1('#chat-input-file'),
        contentDiv: E1('div.content'),
        viewConfig: E1('#chat-config'),
        viewPreview: E1('#chat-preview'),
        previewContent: E1('#chat-preview-content'),
        viewSearch: E1('#chat-search'),
        searchContent: E1('#chat-search-content'),
        btnPreview: E1('#chat-button-preview'),
        views: document.querySelectorAll('.chat-view'),
        activeUserListWrapper: E1('#chat-user-list-wrapper'),
        activeUserList: E1('#chat-user-list')
        activeUserList: E1('#chat-user-list'),
        eMsgPollError: undefined /* current connection error MessageMidget */,
        pollErrorMarker: document.body /* element to toggle 'connection-error' CSS class on */
      },
      me: F.user.name,
      mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50,
      mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/,
      pageIsActive: 'visible'===document.visibilityState,
      changesSincePageHidden: 0,
      notificationBubbleColor: 'white',
177
178
179
180
181
182
183































































































184
185
186
187
188
189
190
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







           new Date((J - 2440587.5) * 86400000) */
      },
      filterState:{
        activeUser: undefined,
        match: function(uname){
          return this.activeUser===uname || !this.activeUser;
        }
      },
      /**
         The timer object is used to control connection throttling
         when connection errors arrise. It starts off with a polling
         delay of $initialDelay ms. If there's a connection error,
         that gets bumped by some value for each subsequent error, up
         to some max value.

         The timing of resetting the delay when service returns is,
         because of the long-poll connection and our lack of low-level
         insight into the connection at this level, a bit wonky.
      */
      timer:{
        /* setTimeout() ID for (delayed) starting a Chat.poll(), so
           that it runs at controlled intervals (which change when a
           connection drops and recovers). */
        tidPendingPoll: undefined,
        tidClearPollErr: undefined /*setTimeout() timer id for
                                     reconnection determination. See
                                     clearPollErrOnWait(). */,
        $initialDelay: 1000 /* initial polling interval (ms) */,
        currentDelay: 1000 /* current polling interval */,
        maxDelay: 60000 * 5 /* max interval when backing off for
                              connection errors */,
        minDelay: 5000 /* minimum delay time for a back-off/retry
                          attempt. */,
        errCount: 0 /* Current poller connection error count */,
        minErrForNotify: 4 /* Don't warn for connection errors until this
                              many have occurred */,
        pollTimeout: (1 && window.location.hostname.match(
          "localhost" /*presumably local dev mode*/
        )) ? 15000
          : (+F.config.chat.pollTimeout>0
             ? (1000 * (F.config.chat.pollTimeout - Math.floor(F.config.chat.pollTimeout * 0.1)))
             /* ^^^^^^^^^^^^ we want our timeouts to be slightly shorter
                than the server's so that we can distingished timed-out
                polls on our end from HTTP errors (if the server times
                out). */
             : 30000),
        /** Returns a random fudge value for reconnect attempt times,
            intended to keep the /chat server from getting hammered if
            all clients which were just disconnected all reconnect at
            the same instant. */
        randomInterval: function(factor){
          return Math.floor(Math.random() * factor);
        },
        /** Increments the reconnection delay, within some min/max range. */
        incrDelay: function(){
          if( this.maxDelay > this.currentDelay ){
            if(this.currentDelay < this.minDelay){
              this.currentDelay = this.minDelay + this.randomInterval(this.minDelay);
            }else{
              this.currentDelay = this.currentDelay*2 + this.randomInterval(this.currentDelay);
            }
          }
          return this.currentDelay;
        },
        /** Resets the delay counter to v || its initial value. */
        resetDelay: function(ms=0){
          return this.currentDelay = ms || this.$initialDelay;
        },
        /** Returns true if the timer is set to delayed mode. */
        isDelayed: function(){
          return (this.currentDelay > this.$initialDelay) ? this.currentDelay : 0;
        },
        /**
           Cancels any in-progress pending-poll timer and starts a new
           one with the given delay, defaulting to this.resetDelay().
        */
        startPendingPollTimer: function(delay){
          this.cancelPendingPollTimer().tidPendingPoll
            = setTimeout( Chat.poll, delay || Chat.timer.resetDelay() );
          return this;
        },
        /**
           Cancels any still-active timer set to trigger the next
           Chat.poll().
        */
        cancelPendingPollTimer: function(){
          if( this.tidPendingPoll ){
            clearTimeout(this.tidPendingPoll);
            this.tidPendingPoll = 0;
          }
          return this;
        },
        /**
           Cancels any pending reconnection attempt back-off timer..
        */
        cancelReconnectCheckTimer: function(){
          if( this.tidClearPollErr ){
            clearTimeout(this.tidClearPollErr);
            this.tidClearPollErr = 0;
          }
          return this;
        }
      },
      /**
         Gets (no args) or sets (1 arg) the current input text field
         value, taking into account single- vs multi-line input. The
         getter returns a trim()'d string and the setter returns this
         object. As a special case, if arguments[0] is a boolean
         value, it behaves like a getter and, if arguments[0]===true
253
254
255
256
257
258
259
260

261
262
263
264
265
266
267
355
356
357
358
359
360
361

362
363
364
365
366
367
368
369







-
+







           network requests. */
      ],
      /** Either scrolls .message-widget element eMsg into view
          immediately or, if it represents an inlined image, delays
          the scroll until the image is loaded, at which point it will
          scroll to either the newest message, if one is set or to
          eMsg (the liklihood is good, at least on initial page load,
          that the the image won't be loaded until other messages have
          that the image won't be loaded until other messages have
          been injected). */
      scheduleScrollOfMsg: function(eMsg){
        if(1===+eMsg.dataset.hasImage){
          eMsg.querySelector('img').addEventListener(
            'load', ()=>(this.e.newestMessage || eMsg).scrollIntoView(false)
          );
        }else{
489
490
491
492
493
494
495




496
497
498
499
500
501
502
503
504
505























506
507
508
509
510
511
512
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







+
+
+
+










+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







         Expects e to be one of the elements in this.e.views.
         The 'hidden' class is removed from e and added to
         all other elements in that list. Returns e.
      */
      setCurrentView: function(e){
        if(e===this.e.currentView){
          return e;
        }
        if( e!==this.e.viewZoom && this.e.zoomedMsg ){
          this.zoomMessage(null, e);
          return this.e.currentView;
        }
        this.e.views.forEach(function(E){
          if(e!==E) D.addClass(E,'hidden');
        });
        this.e.currentView = e;
        if(this.e.currentView.$beforeShow) this.e.currentView.$beforeShow();
        D.removeClass(e,'hidden');
        this.animate(this.e.currentView, 'anim-fade-in-fast');
        return this.e.currentView;
      },

      /**
         Makes message element eMsg the content of this.e.viewZoom.
      */
      zoomMessage: function(eMsg,nextView){
        const marker = this.e.zoomMarker;
        if( !eMsg || eMsg===this.e.zoomedMsg ){
          if( this.e.zoomedMsg ){
            marker.parentNode.insertBefore(this.e.zoomedMsg, marker);
            delete this.e.zoomedMsg;
          }
          this.setCurrentView(nextView || this.e.viewMessages);
          return;
        }
        console.log("zoom message",eMsg);
        if( this.e.zoomedMsg ){
          marker.parentNode.insertBefore(this.e.zoomedMsg, marker);
        }
        this.e.viewMessages.insertBefore(marker, eMsg);
        this.e.zoomContent.appendChild(eMsg);
        this.e.zoomedMsg = eMsg;
        this.setCurrentView(this.e.viewZoom);
      },
      /**
         Updates the "active user list" view if we are not currently
         batch-loading messages and if the active user list UI element
         is active.
      */
      updateActiveUserList: function callee(){
        if(this._isBatchLoading
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
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
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







-
+







-
+









-
+
+




















+
+







-



+
-
+
+



-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+






+







        return this;
      },

      /**
         If animations are enabled, passes its arguments
         to D.addClassBriefly(), else this is a no-op.
         If cb is a function, it is called after the
         CSS class is removed. Returns this object; 
         CSS class is removed. Returns this object;
      */
      animate: function f(e,a,cb){
        if(!f.$disabled){
          D.addClassBriefly(e, a, 0, cb);
        }
        return this;
      }
    };
    }/*Chat object*/;
    cs.e.inputFields = [ cs.e.input1, cs.e.inputM, cs.e.inputX ];
    cs.e.inputFields.$currentIndex = 0;
    cs.e.inputFields.forEach(function(e,ndx){
      if(ndx===cs.e.inputFields.$currentIndex) D.removeClass(e,'hidden');
      else D.addClass(e,'hidden');
    });
    if(D.attr(cs.e.inputX,'contenteditable','plaintext-only').isContentEditable){
      cs.$browserHasPlaintextOnly = true;
    }else{
      /* Only the Chrome family supports contenteditable=plaintext-only */
      /* contenteditable="plaintext-only" is a latecomer, not
         supported in FF until version 136. */
      cs.$browserHasPlaintextOnly = false;
      D.attr(cs.e.inputX,'contenteditable','true');
    }
    cs.animate.$disabled = true;
    F.fetch.beforesend = ()=>cs.ajaxStart();
    F.fetch.aftersend = ()=>cs.ajaxEnd();
    cs.pageTitleOrig = cs.e.pageTitle.innerText;
    const qs = (e)=>document.querySelector(e);
    const argsToArray = function(args){
      return Array.prototype.slice.call(args,0);
    };
    /**
       Reports an error via console.error() and as a toast message.
       Accepts any argument types valid for fossil.toast.error().
    */
    cs.reportError = function(/*msg args*/){
      const args = argsToArray(arguments);
      console.error("chat error:",args);
      F.toast.error.apply(F.toast, args);
    };

    let InternalMsgId = 0;
    /**
       Reports an error in the form of a new message in the chat
       feed. All arguments are appended to the message's content area
       using fossil.dom.append(), so may be of any type supported by
       that function.
    */
    cs.reportErrorAsMessage = function f(/*msg args*/){
      if(undefined === f.$msgid) f.$msgid=0;
      const args = argsToArray(arguments).map(function(v){
        return (v instanceof Error) ? v.message : v;
      });
      if(Chat.beVerbose){
      console.error("chat error:",args);
        console.error("chat error:",args);
      }
      const d = new Date().toISOString(),
            mw = new this.MessageWidget({
              isError: true,
              xfrom: null,
              msgid: "error-"+(++f.$msgid),
              xfrom: undefined,
              msgid: "error-"+(++InternalMsgId),
              mtime: d,
              lmtime: d,
              xmsg: args
            });
      this.injectMessageElem(mw.e.body);
      mw.scrollIntoView();
      return mw;
    };

    /**
       For use by the connection poller to send a "connection
       restored" message.
    */
    cs.reportReconnection = function f(/*msg args*/){
      const args = argsToArray(arguments).map(function(v){
        return (v instanceof Error) ? v.message : v;
      });
      const d = new Date().toISOString(),
            mw = new this.MessageWidget({
              isError: false,
              xfrom: undefined,
              msgid: "reconnect-"+(++InternalMsgId),
              mtime: d,
              lmtime: d,
              xmsg: args
            });
      this.injectMessageElem(mw.e.body);
      mw.scrollIntoView();
      return mw;
    };

    cs.getMessageElemById = function(id){
      return qs('[data-msgid="'+id+'"]');
    };

    /** Finds the last .message-widget element and returns it or
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
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
877
878

879
880
881
882
883
884
885
886
887







-
+




+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+







+
-
+
+







    };

    /**
       LOCALLY deletes a message element by the message ID or passing
       the .message-row element. Returns true if it removes an element,
       else false.
    */
    cs.deleteMessageElem = function(id){
    cs.deleteMessageElem = function(id, silent){
      var e;
      if(id instanceof HTMLElement){
        e = id;
        id = e.dataset.msgid;
        delete e.dataset.msgid;
        if( e?.dataset?.alsoRemove ){
          const xId = e.dataset.alsoRemove;
          delete e.dataset.alsoRemove;
          this.deleteMessageElem( xId );
        }
      }else if(id instanceof Chat.MessageWidget) {
        if( this.e.eMsgPollError === e ){
          this.e.eMsgPollError = undefined;
        }
        if(id.e?.body){
          this.deleteMessageElem(id.e.body);
        }
        return;
      }else{
      } else{
        e = this.getMessageElemById(id);
      }
      if(e && id){
        D.remove(e);
        if(e===this.e.newestMessage){
          this.fetchLastMessageElem();
        }
        if( !silent ){
        F.toast.message("Deleted message "+id+".");
          F.toast.message("Deleted message "+id+".");
        }
      }
      return !!e;
    };

    /**
       Toggles the given message between its parsed and plain-text
       representations. It requires a server round-trip to collect the
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
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







-
+


-












+







             mouse-copying from that field collecting twice as many
             newlines as it should (for unknown reasons). */
          const cpId = 'copy-to-clipboard-'+id;
          /* ^^^ copy button element ID, needed for LABEL element
             pairing.  Recall that we destroy all child elements of
             `content` each time we hit this block, so we can reuse
             that element ID on subsequent toggles. */
          const btnCp = D.attr(D.addClass(D.span(),'copy-button'), 'id', cpId);
          const btnCp = D.attr(D.addClass(D.button(),'copy-button'), 'id', cpId);
          F.copyButton(btnCp, {extractText: ()=>child._xmsgRaw});
          const lblCp = D.label(cpId, "Copy unformatted text");
          lblCp.addEventListener('click',()=>btnCp.click(), false);
          D.append(content, D.append(D.addClass(D.span(), 'nobr'), btnCp, lblCp));
        }
        delete e.$isToggling;
        D.append(content, child);
        return;
      }
      // We need to fetch the plain-text version...
      const self = this;
      F.fetch('chat-fetch-one',{
        urlParams:{ name: id, raw: true},
        responseType: 'json',
        onload: function(msg){
          reportConnectionOkay('chat-fetch-one');
          content.$elems[1] = D.append(D.pre(),msg.xmsg);
          content.$elems[1]._xmsgRaw = msg.xmsg/*used for copy-to-clipboard feature*/;
          self.toggleTextMode(e);
        },
        aftersend:function(){
          delete e.$isToggling;
          Chat.ajaxEnd();
832
833
834
835
836
837
838
839
840
841


842


843
844
845
846
847
848
849
1004
1005
1006
1007
1008
1009
1010

1011
1012
1013
1014

1015
1016
1017
1018
1019
1020
1021
1022
1023







-


+
+
-
+
+







        e = id;
        id = e.dataset.msgid;
      }else{
        e = this.getMessageElemById(id);
      }
      if(!(e instanceof HTMLElement)) return;
      if(this.userMayDelete(e)){
        this.ajaxStart();
        F.fetch("chat-delete/" + id, {
          responseType: 'json',
          onload:(r)=>{
            reportConnectionOkay('chat-delete');
          onload:(r)=>this.deleteMessageElem(r),
            this.deleteMessageElem(r);
          },
          onerror:(err)=>this.reportErrorAsMessage(err)
        });
      }else{
        this.deleteMessageElem(id);
      }
    };
    document.addEventListener('visibilitychange', function(ev){
874
875
876
877
878
879
880
881

882
883
884
885
886
887
888
1048
1049
1050
1051
1052
1053
1054

1055
1056
1057
1058
1059
1060
1061
1062







-
+







        eUser = eUser.parentNode;
      }
      if(eUser==this || !eUser) return false;
      const uname = eUser.dataset.uname;
      let eLast;
      cs.setCurrentView(cs.e.viewMessages);
      if(eUser.classList.contains('selected')){
        /* If curently selected, toggle filter off */
        /* If currently selected, toggle filter off */
        eUser.classList.remove('selected');
        cs.setUserFilter(false);
        delete f.$eSelected;
      }else{
        if(f.$eSelected) f.$eSelected.classList.remove('selected');
        f.$eSelected = eUser;
        eUser.classList.add('selected');
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
1189
1190
1191
1192
1193
1194
1195

1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221







-
+

















+







      const iframe = msgObj.e.iframe;
      const body = iframe.contentWindow.document.querySelector('body');
      if(body && !body.style.fontSize){
        /** _Attempt_ to force the iframe to inherit the message's text size
            if the body has no explicit size set. On desktop systems
            the size is apparently being inherited in that case, but on mobile
            not. */
        body.style.fontSize = window.getComputedStyle(msgObj.e.content);
        body.style.fontSize = window.getComputedStyle(msgObj.e.content).fontSize;
      }
      if('' === iframe.style.maxHeight){
        /* Resize iframe height to fit the content. Workaround: if we
           adjust the iframe height while it's hidden then its height
           is 0, so we must briefly unhide it. */
        const isHidden = iframe.classList.contains('hidden');
        if(isHidden) D.removeClass(iframe, 'hidden');
        iframe.style.maxHeight = iframe.style.height
          = iframe.contentWindow.document.documentElement.scrollHeight + 'px';
        if(isHidden) D.addClass(iframe, 'hidden');
      }
    };

    ctor.prototype = {
      scrollIntoView: function(){
        this.e.content.scrollIntoView();
      },
      //remove: function(silent){Chat.deleteMessageElem(this, silent);},
      setMessage: function(m){
        const ds = this.e.body.dataset;
        ds.timestamp = m.mtime;
        ds.lmtime = m.lmtime;
        ds.msgid = m.msgid;
        ds.xfrom = m.xfrom || '';
        if(m.xfrom === Chat.me){
1203
1204
1205
1206
1207
1208
1209
1210

1211


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
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
1423
1424
1425







-
+

+
+


-


-
+

+
+
+
+
+
+
+
+
+
+













-
+







                   initially caused by Safari being stricter than
                   other browsers on its time string input, and that
                   has since been resolved by emiting a stricter
                   format. */
                // Date doesn't work, so dumb it down...
                D.append(this.e, D.append(D.span(), eMsg.dataset.timestamp," zulu"));
              }
              const toolbar = D.addClass(D.div(), 'toolbar');
              const toolbar = D.addClass(D.div(), 'toolbar', 'hide-in-zoom');
              D.append(this.e, toolbar);
              const self = this;

              const btnDeleteLocal = D.button("Delete locally");
              D.append(toolbar, btnDeleteLocal);
              const self = this;
              btnDeleteLocal.addEventListener('click', function(){
                self.hide();
                Chat.deleteMessageElem(eMsg);
                Chat.deleteMessageElem(eMsg)
              });
              if( eMsg.classList.contains('notification') ){
                const btnDeletePoll = D.button("Delete /chat notifications?");
                D.append(toolbar, btnDeletePoll);
                btnDeletePoll.addEventListener('click', function(){
                  self.hide();
                  Chat.e.viewMessages.querySelectorAll(
                    '.message-widget.notification:not(.resend-message)'
                  ).forEach(e=>Chat.deleteMessageElem(e, true));
                });
              }
              if(Chat.userMayDelete(eMsg)){
                const btnDeleteGlobal = D.button("Delete globally");
                D.append(toolbar, btnDeleteGlobal);
                F.confirmer(btnDeleteGlobal,{
                  pinSize: true,
                  ticks: F.config.confirmerButtonTicks,
                  confirmText: "Confirm delete?",
                  onconfirm:function(){
                    self.hide();
                    Chat.deleteMessage(eMsg);
                  }
                });
              }
              const toolbar3 = D.addClass(D.div(), 'toolbar');
              const toolbar3 = D.addClass(D.div(), 'toolbar', 'hide-in-zoom');
              D.append(this.e, toolbar3);
              D.append(toolbar3, D.button(
                "Locally remove all previous messages",
                function(){
                  self.hide();
                  Chat.mnMsg = +eMsg.dataset.msgid;
                  var e = eMsg.previousElementSibling;
1288
1289
1290
1291
1292
1293
1294





1295
1296
1297
1298
1299
1300
1301
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492







+
+
+
+
+







                            //eMsg, 'anim-flip-v'
                          );
                        })
                    )
                  );
                }/*jump-to button*/
              }
              const btnZoom = D.button("Zoom");
              D.append(toolbar2, btnZoom);
              btnZoom.addEventListener('click', function(){
                Chat.zoomMessage(eMsg);
              });
              const tab = eMsg.querySelector('.message-widget-tab');
              D.append(tab, this.e);
              D.removeClass(this.e, 'hidden');
              Chat.animate(this.e, 'anim-fade-in-fast');
            }/*refresh()*/,
            hide: function(){
              delete this.$eMsg;
1455
1456
1457
1458
1459
1460
1461

1462
1463
1464
1465
1466
1467
1468
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660







+







          urlParams:{
            q: '',
            n: nFetch,
            i: iFirst
          },
          responseType: "json",
          onload:function(jx){
            reportConnectionOkay('chat-query');
            if( bDown ) jx.msgs.reverse();
            jx.msgs.forEach((m) => {
              m.isSearchResult = true;
              var mw = new Chat.MessageWidget(m);
              if( bDown ){
                /* Inject the message below this object's body, or
                   append it to Chat.e.searchContent if this element
1522
1523
1524
1525
1526
1527
1528
1529

1530
1531
1532
1533
1534
1535
1536
1714
1715
1716
1717
1718
1719
1720

1721
1722
1723
1724
1725
1726
1727
1728







-
+







        D.append(dd, D.br(), img);
        const reader = new FileReader();
        reader.onload = (e)=>img.setAttribute('src', e.target.result);
        reader.readAsDataURL(blob);
      }
    };
    Chat.e.inputFile.addEventListener('change', function(ev){
      updateDropZoneContent(this.files && this.files[0] ? this.files[0] : undefined)
      updateDropZoneContent(this?.files[0])
    });
    /* Handle image paste from clipboard. TODO: figure out how we can
       paste non-image binary data as if it had been selected via the
       file selection element. */
    const pasteListener = function(event){
      const items = event.clipboardData.items,
            item = items[0];
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
1632
1633
1634
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
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







-
-
-
-




















+


















-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    };
    ['drop','dragenter','dragleave','dragend'].forEach(
      (k)=>Chat.e.inputX.addEventListener(k, noDragDropEvents, false)
    );
    return bxs;
  })()/*drag/drop/paste*/;

  const tzOffsetToString = function(off){
    const hours = Math.round(off/60), min = Math.round(off % 30);
    return ''+(hours + (min ? '.5' : ''));
  };
  const localTime8601 = function(d){
    return [
      d.getYear()+1900, '-', pad2(d.getMonth()+1), '-', pad2(d.getDate()),
      'T', pad2(d.getHours()),':', pad2(d.getMinutes()),':',pad2(d.getSeconds())
    ].join('');
  };

  /**
     Called by Chat.submitMessage() when message sending failed. Injects a fake message
     containing the content and attachment of the failed message and gives the user buttons
     to discard it or edit and retry.
   */
  const recoverFailedMessage = function(state){
    const w = D.addClass(D.div(), 'failed-message');
    D.append(w, D.append(
      D.span(),"This message was not successfully sent to the server:"
    ));
    if(state.msg){
      const ta = D.textarea();
      ta.value = state.msg;
      ta.setAttribute('readonly','true');
      D.append(w,ta);
    }
    if(state.blob){
      D.append(w,D.append(D.span(),"Attachment: ",(state.blob.name||"unnamed")));
      //console.debug("blob = ",state.blob);
    }
    const buttons = D.addClass(D.div(), 'buttons');
    D.append(w, buttons);
    D.append(buttons, D.button("Discard message?", function(){
      const theMsg = findMessageWidgetParent(w);
      if(theMsg) Chat.deleteMessageElem(theMsg);
    }));
    D.append(buttons, D.button("Edit message and try again?", function(){
      if(state.msg) Chat.inputValue(state.msg);
      if(state.blob) BlobXferState.updateDropZoneContent(state.blob);
      const theMsg = findMessageWidgetParent(w);
      if(theMsg) Chat.deleteMessageElem(theMsg);
    }));
    Chat.reportErrorAsMessage(w);
    D.addClass(Chat.reportErrorAsMessage(w).e.body, "resend-message");
  };

  /* Assume the connection has been established, reset the
     Chat.timer.tidClearPollErr, and (if showMsg and
     !!Chat.e.eMsgPollError) alert the user that the outage appears to
     be over. Also schedule Chat.poll() to run in the very near
     future. */
  const reportConnectionOkay = function(dbgContext, showMsg = true){
    if(Chat.beVerbose){
      console.warn('reportConnectionOkay', dbgContext,
                   'Chat.e.pollErrorMarker classes =',
                   Chat.e.pollErrorMarker.classList,
                   'Chat.timer.tidClearPollErr =',Chat.timer.tidClearPollErr,
                   'Chat.timer =',Chat.timer);
    }
    if( Chat.timer.errCount ){
      D.removeClass(Chat.e.pollErrorMarker, 'connection-error');
      Chat.timer.errCount = 0;
    }
    Chat.timer.cancelReconnectCheckTimer().startPendingPollTimer();
    if( Chat.e.eMsgPollError ) {
      const oldErrMsg = Chat.e.eMsgPollError;
      Chat.e.eMsgPollError = undefined;
      if( showMsg ){
        if(Chat.beVerbose){
          console.log("Poller Connection restored.");
        }
        const m = Chat.reportReconnection("Poller connection restored.");
        if( oldErrMsg ){
          D.remove(oldErrMsg.e?.body.querySelector('button.retry-now'));
        }
        m.e.body.dataset.alsoRemove = oldErrMsg?.e?.body?.dataset?.msgid;
        D.addClass(m.e.body,'poller-connection');
      }
    }
  };

  /**
     Submits the contents of the message input field (if not empty)
     and/or the file attachment field to the server. If both are
     empty, this is a no-op.

1684
1685
1686
1687
1688
1689
1690

1691
1692
1693
1694
1695
1696
1697
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922







+







      payload: fd,
      responseType: 'text',
      onerror:function(err){
        self.reportErrorAsMessage(err);
        recoverFailedMessage(fallback);
      },
      onload:function(txt){
        reportConnectionOkay('chat-send');
        if(!txt) return/*success response*/;
        try{
          const json = JSON.parse(txt);
          self.newContent({msgs:[json]});
        }catch(e){
          self.reportError(e);
        }
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
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







-










+







      };
    }
    if(13 !== ev.keyCode) return;
    const text = Chat.inputValue().trim();
    const ctrlMode = Chat.settings.getBool('edit-ctrl-send', false);
    //console.debug("Enter key event:", ctrlMode, ev.ctrlKey, ev.shiftKey, ev);
    if(ev.shiftKey){
      const compactMode = Chat.settings.getBool('edit-compact-mode', false);
      ev.preventDefault();
      ev.stopPropagation();
      /* Shift-enter will run preview mode UNLESS the input field is empty
         AND (preview or search mode) is active, in which cases it will
         switch back to message view. */
      if(!text &&
         (Chat.e.currentView===Chat.e.viewPreview
          | Chat.e.currentView===Chat.e.viewSearch)){
        Chat.setCurrentView(Chat.e.viewMessages);
      }else if(!text){
        const compactMode = Chat.settings.getBool('edit-compact-mode', false);
        f.$toggleCompact(compactMode);
      }else if(Chat.settings.getBool('edit-shift-enter-preview', true)){
        Chat.e.btnPreview.click();
      }
      return false;
    }
    if(ev.ctrlKey && !text && !BlobXferState.blob){
1866
1867
1868
1869
1870
1871
1872




1873
1874
1875
1876
1877
1878
1879
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108







+
+
+
+







      hint: F.storage.isTransient()
        ? "Local store is unavailable. These settings are transient."
        : ["Most of these settings are persistent via ",
           F.storage.storageImplName(), ": ",
           F.storage.storageHelpDescription()].join('')
    },{
      label: "Editing Options...",
      hint: ["These options are all recommended but some misinteract",
             "with specific browsers or software keyboards so they",
             "are not enabled by default."
            ].join(' '),
      children:[{
        label: "Chat-only mode",
        hint: "Toggle the page between normal fossil view and chat-only view.",
        boolValue: 'chat-only-mode'
      },{
        label: "Ctrl-enter to Send",
        hint: [
2124
2125
2126
2127
2128
2129
2130
2131

2132
2133
2134
2135
2136
2137

2138
2139
2140
2141
2142
2143
2144
2353
2354
2355
2356
2357
2358
2359

2360
2361
2362
2363
2364
2365

2366
2367
2368
2369
2370
2371
2372
2373







-
+





-
+







        const v = Chat.inputValue();
        Chat.inputValue('');
        Chat.e.inputFields.$currentIndex = a[2];
        Chat.inputValue(v);
        D.removeClass(a[0], 'hidden');
        D.addClass(a[1], 'hidden');
      }
      Chat.e.inputElementWrapper.classList[
      Chat.e.inputLineWrapper.classList[
        s.value ? 'add' : 'remove'
      ]('compact');
      Chat.e.inputFields[Chat.e.inputFields.$currentIndex].focus();
    });
    Chat.settings.addListener('edit-ctrl-send',function(s){
      const label = (s.value ? "Ctrl-" : "")+"Enter submits message";
      const label = (s.value ? "Ctrl-" : "")+"Enter submits message.";
      Chat.e.inputFields.forEach((e)=>{
        const v = e.dataset.placeholder0 + " " +label;
        if(e.isContentEditable) e.dataset.placeholder = v;
        else D.attr(e,'placeholder',v);
      });
      Chat.e.btnSubmit.title = label;
    });
2183
2184
2185
2186
2187
2188
2189

2190
2191
2192
2193
2194
2195
2196
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426







+







      fd.append('content', txt);
      fd.append('filename','chat.md'
                /*filename needed for mimetype determination*/);
      fd.append('render_mode',F.page.previewModes.wiki);
      F.fetch('ajax/preview-text',{
        payload: fd,
        onload: function(html){
          reportConnectionOkay('ajax/preview-text');
          Chat.setPreviewText(html);
          F.pikchr.addSrcView(Chat.e.viewPreview.querySelectorAll('svg.pikchr'));
        },
        onerror: function(e){
          F.fetch.onerror(e);
          Chat.setPreviewText("ERROR: "+(
            e.message || 'Unknown error fetching preview!'
2238
2239
2240
2241
2242
2243
2244









2245
2246
2247
2248
2249
2250
2251
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490







+
+
+
+
+
+
+
+
+







    Chat.e.viewSearch.querySelector('button.action-close').addEventListener('click', function(ev){
      ev.preventDefault();
      ev.stopPropagation();
      Chat.setCurrentView(Chat.e.viewMessages);
      return false;
    }, false);
  })()/*search view setup*/;

  (function(){/*Set up the zoom view */
    Chat.e.viewZoom.querySelector('button.action-close').addEventListener('click', function(ev){
      ev.preventDefault();
      ev.stopPropagation();
      Chat.zoomMessage(null);
      return false;
    }, false);
  })()/*zoom view setup*/;

  /** Callback for poll() to inject new content into the page.  jx ==
      the response from /chat-poll. If atEnd is true, the message is
      appended to the end of the chat list (for loading older
      messages), else the beginning (the default). */
  const newcontent = function f(jx,atEnd){
    if(!f.processPost){
2320
2321
2322
2323
2324
2325
2326

2327
2328
2329
2330
2331
2332
2333
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573







+







        },
        responseType: 'json',
        onerror:function(err){
          Chat.reportErrorAsMessage(err);
          Chat._isBatchLoading = false;
        },
        onload:function(x){
          reportConnectionOkay('loadOldMessages()');
          let gotMessages = x.msgs.length;
          newcontent(x,true);
          Chat._isBatchLoading = false;
          Chat.updateActiveUserList();
          if(Chat._gotServerError){
            Chat._gotServerError = false;
            return;
2409
2410
2411
2412
2413
2414
2415

2416
2417
2418
2419
2420
2421
2422
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663







+







        payload: fd,
        responseType: 'json',
        onerror:function(err){
          Chat.setCurrentView(Chat.e.viewMessages);
          Chat.reportErrorAsMessage(err);
        },
        onload:function(jx){
          reportConnectionOkay('submitSearch()');
          let previd = 0;
          D.clearElement(eMsgTgt);
          jx.msgs.forEach((m)=>{
            m.isSearchResult = true;
            const mw = new Chat.MessageWidget(m);
            const spacer = new Chat.SearchCtxLoader({
              first: jx.first,
2442
2443
2444
2445
2446
2447
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
2480
2481




























2482
2483

2484
2485
2486
2487
2488

2489
2490




2491

























2492
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

2519
2520
2521
2522
2523
2524

2525
2526
2527
2528
2529
2530
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729

2730
2731
2732
2733
2734
2735
2736
2737
2738
2739

2740

2741
2742






2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757


2758
2759
2760
2761
2762
2763
2764
2765


2766




2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779


2780
2781




2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810

2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823

2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849

2850
2851
2852
2853
2854
2855


2856
2857
2858
2859
2860
2861
2862
2863
2864
2865


2866
2867
2868
2869


2870

2871
2872
2873
2874
2875
2876
2877
2878

2879
2880
2881

2882
2883
2884
2885
2886
2887

2888
2889
2890
2891
2892
2893
2894







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+








+
-
+
-


-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
-
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
+





+


+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
+





-
-
+
+
+
+
+
+
+
+


-
-
+
+
+
+
-
-
+
-


+





-
+


-
+





-
+






                      term );
          }
        }
      }
    );
  }/*Chat.submitSearch()*/;

  /*
    To be called from F.fetch('chat-poll') beforesend() handler.  If
    we're currently in delayed-retry mode and a connection is
    started, try to reset the delay after N time waiting on that
    connection. The fact that the connection is waiting to respond,
    rather than outright failing, is a good hint that the outage is
    over and we can reset the back-off timer.

    Without this, recovery of a connection error won't be reported
    until after the long-poll completes by either receiving new
    messages or timing out. Once a long-poll is in progress, though,
    we "know" that it's up and running again, so can update the UI and
    connection timer to reflect that. That's the job this function
    does.

    Only one of these asynchronous checks will ever be active
    concurrently and only if Chat.timer.isDelayed() is true. i.e. if
    this timer is active or Chat.timer.isDelayed() is false, this is a
    no-op.
  */
  const chatPollBeforeSend = function(){
    //console.warn('chatPollBeforeSend outer', Chat.timer.tidClearPollErr, Chat.timer.currentDelay);
    if( !Chat.timer.tidClearPollErr && Chat.timer.isDelayed() ){
      Chat.timer.tidClearPollErr = setTimeout(()=>{
        //console.warn('chatPollBeforeSend inner');
        Chat.timer.tidClearPollErr = 0;
        if( poll.running ){
          /* This chat-poll F.fetch() is still underway, so let's
             assume the connection is back up until/unless it times
             out or breaks again. */
          reportConnectionOkay('chatPollBeforeSend', true);
        }
      }, Chat.timer.$initialDelay * 4/*kinda arbitrary: not too long for UI wait and
                                       not too short as to make connection unlikely. */ );
    }
  };

  /**
     Deal with the last poll() response and maybe re-start poll().
  */
  const afterFetch = function f(){
  const afterPollFetch = function f(err){
    if(true===f.isFirstCall){
      f.isFirstCall = false;
      Chat.ajaxEnd();
      Chat.e.viewMessages.classList.remove('loading');
      setTimeout(function(){
        Chat.scrollMessagesTo(1);
      }, 250);
    }
    Chat.timer.cancelPendingPollTimer();
    if(Chat._gotServerError && Chat.intervalTimer){
    if(Chat._gotServerError){
      clearInterval(Chat.intervalTimer);
      Chat.reportErrorAsMessage(
        "Shutting down chat poller due to server-side error. ",
        "Reload this page to reactivate it.");
      delete Chat.intervalTimer;
    }
    poll.running = false;
  };
  afterFetch.isFirstCall = true;
        "Reload this page to reactivate it."
      );
    } else {
      if( err && Chat.beVerbose ){
        console.error("afterPollFetch:",err.name,err.status,err.message);
      }
      if( !err || 'timeout'===err.name/*(probably) long-poll expired*/ ){
        /* Restart the poller immediately. */
        reportConnectionOkay('afterPollFetch '+err, false);
      }else{
        /* Delay a while before trying again, noting that other Chat
           APIs may try and succeed at connections before this timer
           resolves, in which case they'll clear this timeout and the
           UI message about the outage. */
        let delay;
  /**
     FIXME: when polling fails because the remote server is
        D.addClass(Chat.e.pollErrorMarker, 'connection-error');
        if( ++Chat.timer.errCount < Chat.timer.minErrForNotify ){
          delay = Chat.timer.resetDelay(
            (Chat.timer.minDelay * Chat.timer.errCount)
              + Chat.timer.randomInterval(Chat.timer.minDelay)
          );
          if(Chat.beVerbose){
            console.warn("Ignoring polling error #",Chat.timer.errCount,
     reachable but it's not accepting HTTP requests, we should back
     off on polling for a while. e.g. if the remote web server process
                         "for another",delay,"ms" );
     is killed, the poll fails quickly and immediately retries,
     hammering the remote server until the httpd is back up. That
     happens often during development of this application.

          }
        } else {
          delay = Chat.timer.incrDelay();
          //console.warn("afterPollFetch Chat.e.eMsgPollError",Chat.e.eMsgPollError);
          const msg = "Poller connection error. Retrying in "+delay+ " ms.";
          /* Replace the current/newest connection error widget. We could also
             just update its body with the new message, but then its timestamp
             never updates. OTOH, if we replace the message, we lose the
             start time of the outage in the log. It seems more useful to
             update the timestamp so that it doesn't look like it's hung. */
          if( Chat.e.eMsgPollError ){
            Chat.deleteMessageElem(Chat.e.eMsgPollError, false);
          }
     XHR does not offer a direct way of distinguishing between
     HTTP/connection errors, but we can hypothetically use the
          const theMsg = Chat.e.eMsgPollError = Chat.reportErrorAsMessage(msg);
          D.addClass(Chat.e.eMsgPollError.e.body,'poller-connection');
     xhrRequest.status value to do so, with status==0 being a
     connection error. We do not currently have a clean way of passing
     that info back to the fossil.fetch() client, so we'll need to
     hammer on that API a bit to get this working.
          /* Add a "retry now" button */
          const btnDel = D.addClass(D.button("Retry now"), 'retry-now');
          const eParent = Chat.e.eMsgPollError.e.content;
          D.append(eParent, " ", btnDel);
          btnDel.addEventListener('click', function(){
            D.remove(btnDel);
            D.append(eParent, D.text("retrying..."));
            Chat.timer.cancelPendingPollTimer().currentDelay =
              Chat.timer.resetDelay() +
              1  /*workaround for showing the "connection restored"
                   message, as the +1 will cause
                   Chat.timer.isDelayed() to be true.*/;
            poll();
          });
          //Chat.playNewMessageSound();// browser complains b/c this wasn't via human interaction
        }
        Chat.timer.startPendingPollTimer(delay);
      }
    }
  };
  afterPollFetch.isFirstCall = true;

  /**
     Initiates, if it's not already running, a single long-poll
     request to the /chat-poll endpoint. In the handling of that
     response, it end up will psuedo-recursively calling itself via
     the response-handling process. Despite being async, the implied
     returned Promise is meaningless.
  */
  const poll = async function f(){
  const poll = Chat.poll = async function f(){
    if(f.running) return;
    f.running = true;
    Chat._isBatchLoading = f.isFirstCall;
    if(true===f.isFirstCall){
      f.isFirstCall = false;
      f.pendingOnError = undefined;
      Chat.ajaxStart();
      Chat.e.viewMessages.classList.add('loading');
      /*
        We manager onerror() results in poll() in a roundabout
        manner: when an onerror() arrives, we stash it aside
        for a moment before processing it.
    }

        This level of indirection is necessary to be able to
        unambiguously identify client-timeout-specific polling errors
        from other errors. Timeouts are always announced in pairs of
        an HTTP 0 and something we can unambiguously identify as a
        timeout (in that order). When we receive an HTTP error we put
        it into this queue.  If an ontimeout() call arrives before
        this error is handled, this error is ignored.  If, however, an
        HTTP error is seen without an accompanying timeout, we handle
        it from here.

        It's kinda like in the curses C API, where you to match
        ALT-X by first getting an ESC event, then an X event, but
        this one is a lot less explicable. (It's almost certainly a
        mis-handling bug in F.fetch(), but it has so far eluded my
        eyes.)
      */
      f.delayPendingOnError = function(err){
        if( f.pendingOnError ){
          const x = f.pendingOnError;
          f.pendingOnError = undefined;
          afterPollFetch(x);
        }
      };
    }
    F.fetch("chat-poll",{
      timeout: 420 * 1000/*FIXME: get the value from the server*/,
      timeout: Chat.timer.pollTimeout,
      urlParams:{
        name: Chat.mxMsg
      },
      responseType: "json",
      // Disable the ajax start/end handling for this long-polling op:
      beforesend: function(){},
      aftersend: function(){},
      beforesend: chatPollBeforeSend,
      aftersend: function(){
        poll.running = false;
      },
      ontimeout: function(err){
        f.pendingOnError = undefined /*strip preceding non-timeout error, if any*/;
        afterPollFetch(err);
      },
      onerror:function(err){
        Chat._isBatchLoading = false;
        if(Chat.verboseErrors) console.error(err);
        /* ^^^ we don't use Chat.reportError() here b/c the polling
        if(Chat.beVerbose){
          console.error("poll.onerror:",err.name,err.status,JSON.stringify(err));
        }
        f.pendingOnError = err;
           fails exepectedly when it times out, but is then immediately
           resumed, and reportError() produces a loud error message. */
        setTimeout(f.delayPendingOnError, 100);
        afterFetch();
      },
      onload:function(y){
        reportConnectionOkay('poll.onload', true);
        newcontent(y);
        if(Chat._isBatchLoading){
          Chat._isBatchLoading = false;
          Chat.updateActiveUserList();
        }
        afterFetch();
        afterPollFetch();
      }
    });
  };
  }/*poll()*/;
  poll.isFirstCall = true;
  Chat._gotServerError = poll.running = false;
  if( window.fossil.config.chat.fromcli ){
    Chat.chatOnlyMode(true);
  }
  Chat.intervalTimer = setInterval(poll, 1000);
  Chat.timer.startPendingPollTimer();
  delete ForceResizeKludge.$disabled;
  ForceResizeKludge();
  Chat.animate.$disabled = false;
  setTimeout( ()=>Chat.inputFocus(), 0 );
  F.page.chat = Chat/* enables testing the APIs via the dev tools */;
});
Changes to src/fossil.page.fileedit.js.
1147
1148
1149
1150
1151
1152
1153
1154

1155
1156
1157
1158
1159
1160
1161
1147
1148
1149
1150
1151
1152
1153

1154
1155
1156
1157
1158
1159
1160
1161







-
+







      }
    });
    return this;
  };

  /**
     Fetches the page preview based on the contents and settings of
     this page's input fields, and updates the UI with with the
     this page's input fields, and updates the UI with the
     preview.

     Returns this object, noting that the operation is async.
  */
  P.preview = function f(switchToTab){
    if(!affirmHasFile()) return this;
    return this._postPreview(this.fileContent(), function(c){
Changes to src/fossil.page.forumpost.js.
115
116
117
118
119
120
121








122




123
124
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

130
131
132
133
134
135







+
+
+
+
+
+
+
+
-
+
+
+
+


          browser's cancel button while waiting, they'll be stuck with
          an unsubmittable form. */
      setTimeout(()=>{delete form.dataset.submitted}, 7000);
      return;
    };
    document.querySelectorAll("form").forEach(function(form){
      form.addEventListener('submit',formSubmitted);
      form
        .querySelectorAll("input.action-close, input.action-reopen")
        .forEach(function(e){
          F.confirmer(e, {
            confirmText: (e.classList.contains('action-reopen')
                          ? "Confirm re-open"
                          : "Confirm close"),
            onconfirm: ()=>form.submit()
    });
          });
        });
    });

  })/*F.onPageLoad callback*/;
})(window.fossil);
Changes to src/fossil.page.pikchrshow.js.
45
46
47
48
49
50
51
52

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

52
53
54
55
56
57
58
59







-
+








  F.onPageLoad(function() {
    document.body.classList.add('pikchrshow');
    P.e = { /* various DOM elements we work with... */
      previewTarget: E('#pikchrshow-output'),
      previewLegend: E('#pikchrshow-output-wrapper > legend'),
      previewCopyButton: D.attr(
        D.addClass(D.span(),'copy-button'),
        D.addClass(D.button(),'copy-button'),
        'id','preview-copy-button' 
      ),
      previewModeLabel: D.label('preview-copy-button'),
      btnSubmit: E('#pikchr-submit-preview'),
      btnStash: E('#pikchr-stash'),
      btnUnstash: E('#pikchr-unstash'),
      btnClearStash: E('#pikchr-clear-stash'),
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
117
118
119
120
121
122
123

124
125
126
127
128
129
130







-







        return false;
      }
    }, false);

    ////////////////////////////////////////////////////////////
    // Setup clipboard-copy of markup/SVG...
    F.copyButton(P.e.previewCopyButton, {copyFromElement: P.e.taPreviewText});
    P.e.previewModeLabel.addEventListener('click', ()=>P.e.previewCopyButton.click(), false);

    ////////////////////////////////////////////////////////////
    // Set up dark mode simulator...
    P.e.cbDarkMode.addEventListener('change', function(ev){
      if(ev.target.checked) D.addClass(P.e.previewTarget, 'dark-mode');
      else D.removeClass(P.e.previewTarget, 'dark-mode');
    }, false);
346
347
348
349
350
351
352
353

354
355
356
357
358
359
360
345
346
347
348
349
350
351

352
353
354
355
356
357
358
359







-
+







    if(this.response.isError){
      D.append(D.clearElement(preTgt), D.parseHtml(P.response.raw));
      D.addClass(preTgt, 'error');
      this.e.previewModeLabel.innerText = "Error";
      return;
    }
    D.removeClass(preTgt, 'error');
    D.removeClass(this.e.previewCopyButton, 'disabled');
    this.e.previewCopyButton.disabled = false;
    D.removeClass(this.e.markupAlignWrapper, 'hidden');
    D.enable(this.e.previewModeToggle, this.e.markupAlignRadios);
    let label, svg;
    switch(this.previewMode){
    case 0:
      label = "SVG";
      f.showMarkupAlignment(false);
425
426
427
428
429
430
431
432

433
434
435
436
437
438
439
424
425
426
427
428
429
430

431
432
433
434
435
436
437
438







-
+







        P.response.isError = isError;
        D.enable(fp.toDisable);
        P.renderPreview();
      };
    }
    D.disable(fp.toDisable, this.e.previewModeToggle, this.e.markupAlignRadios);
    D.addClass(this.e.markupAlignWrapper, 'hidden');
    D.addClass(this.e.previewCopyButton, 'disabled');
    this.e.previewCopyButton.disabled = true;
    const content = this.e.taContent.value.trim();
    this.response.raw = this.response.rawSvg = undefined;
    this.response.inputText = content;
    const sampleScript = fp.$_sampleScript;
    delete fp.$_sampleScript;
    if(sampleScript && sampleScript.cached){
      fp.updateView(sampleScript.cached, false);
Changes to src/fossil.page.pikchrshowasm.js.
118
119
120
121
122
123
124



125



126
127
128
129
130
131
132
118
119
120
121
122
123
124
125
126
127

128
129
130
131
132
133
134
135
136
137







+
+
+
-
+
+
+







      if(PS._config.hasOwnProperty(k)){
        PS.config[k] = PS._config[k];
      }
    });
    delete PS._config;
  }

  /* Randomize the name of the worker script so that it is never cached.
  ** The Fossil /builtin method will automatically remove the "-v000000000"
  ** part of the filename, resolving it to just "pikchr-worker.js". */
  PS.worker = new Worker('builtin/extsrc/pikchr-worker.js');
  PS.worker = new Worker('builtin/extsrc/pikchr-worker-v'+
                         (Math.floor(Math.random()*10000000000) + 1000000000)+
                        '.js');
  PS.worker.onmessage = (ev)=>PS.runMsgHandlers(ev.data);
  PS.addMsgHandler('stdout', console.log.bind(console));
  PS.addMsgHandler('stderr', console.error.bind(console));

  /** Handles status updates from the Module object. */
  PS.addMsgHandler('module', function f(ev){
    ev = ev.data;
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
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







-
+

-
+







-
+








  PS.e.previewModeLabel.innerText =
    PS.renderModeLabels[PS.renderModes[PS.renderModes.selectedIndex]];

  /**
     The 'pikchr-ready' event is fired (with no payload) when the
     wasm module has finished loading. */
  PS.addMsgHandler('pikchr-ready', function(){
  PS.addMsgHandler('pikchr-ready', function(event){
    PS.clearMsgHandlers('pikchr-ready');
    F.page.onPikchrshowLoaded();
    F.page.onPikchrshowLoaded(event.data);
  });

  /**
     Performs all app initialization which must wait until after the
     worker module is loaded. This function removes itself when it's
     called.
  */
  F.page.onPikchrshowLoaded = function(){
  F.page.onPikchrshowLoaded = function(pikchrVersion){
    delete this.onPikchrshowLoaded;
    // Unhide all elements which start out hidden
    EAll('.initially-hidden').forEach((e)=>e.classList.remove('initially-hidden'));
    const taInput = E('#input');
    const btnClearIn = E('#btn-clear');
    btnClearIn.addEventListener('click',function(){
      taInput.value = '';
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
310
311
312
313
314
315
316

317
318
319
320
321
322
323







-







      modes.selectedIndex = (modes.selectedIndex + 1) % modes.length;
      this.e.previewModeLabel.innerText = this.renderModeLabels[modes[modes.selectedIndex]];
      if(this.e.pikOut.dataset.pikchr){
        this.render(this.e.pikOut.dataset.pikchr);
      }
    }.bind(PS));
    F.copyButton(PS.e.previewCopyButton, {copyFromElement: PS.e.outText});
    PS.e.previewModeLabel.addEventListener('click', ()=>PS.e.previewCopyButton.click(), false);

    PS.addMsgHandler('working',function f(ev){
      switch(ev.data){
          case 'start': /* See notes in preStartWork(). */; return;
          case 'end':
            //preStartWork._.pageTitle.innerText = preStartWork._.pageTitleOrig;
            this.e.btnRender.removeAttribute('disabled');
437
438
439
440
441
442
443



444
445
446
447
448
449
450
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457







+
+
+







         link in the forum. */
      const src = window.sessionStorage.getItem('pikchr-xfer');
      if( src && (new URL(self.location.href).searchParams).has('fromSession') ){
        taInput.value =  src;
        window.sessionStorage.removeItem('pikchr-xfer');
      }
    }
    D.append(E('fieldset.options > div'),
             D.append(D.addClass(D.span(), 'labeled-input'),
                      'pikchr v. '+pikchrVersion));

    PS.e.btnRender.click();

    /** Debounce handler for auto-rendering while typing. */
    const debounceAutoRender = F.debounce(function f(){
      if(!PS._isDirty) return;
      const text = getCurrentText();
522
523
524
525
526
527
528
529

530
531
532
533
534
535
536
529
530
531
532
533
534
535

536
537
538
539
540
541
542
543







-
+








    delete ForceResizeKludge.$disabled;
    ForceResizeKludge();
  }/*onPikchrshowLoaded()*/;


  /**
     Predefined scripts. Each entry is an object:
     Predefined example pikchr scripts. Each entry is an object:

     {
     name: required string,
     code: optional code string. An entry with a falsy code is treated
           like a separator in the resulting SELECT element (a
           disabled OPTION).
     }
Added src/fossil.page.ticket.js.
































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/*
 * This script adds a checkbox to reverse the sorting on any body.tkt
 * pages which contain a .tktCommentArea element.
 */
window.addEventListener( 'load', function() {
  const tgt = document.querySelectorAll('.tktCommentArea');
  if( !tgt ) return;
  const F = globalThis.fossil, D = F.dom;
  let i = 0;
  for(const e of tgt) {
    ++i;
    const childs = e.querySelectorAll('.tktCommentEntry');
    if( !childs || 1===childs.length ) continue;
    const cbReverseKey = 'tktCommentArea:reverse';
    const cbReverse = D.checkbox();
    const cbId = cbReverseKey+':'+i;
    cbReverse.setAttribute('id',cbId);
    const widget = D.append(
      D.div(),
      cbReverse,
      D.label(cbReverse, " Show newest first? ")
    );
    widget.classList.add('newest-first-controls');
    e.parentElement.insertBefore(widget,e);
    const cbReverseIt = ()=>{
      e.classList[cbReverse.checked ? 'add' : 'remove']('reverse');
      F.storage.set(cbReverseKey, cbReverse.checked ? 1 : 0);
    };
    cbReverse.addEventListener('change', cbReverseIt, true);
    cbReverse.checked = !!(+F.storage.get(cbReverseKey, 0));
  };
}); // window.addEventListener( 'load' ...
Changes to src/fossil.page.wikiedit.js.
12
13
14
15
16
17
18
19

20
21
22
23
24
25
26
12
13
14
15
16
17
18

19
20
21
22
23
24
25
26







-
+







     - Event 'wiki-page-loaded': passes on information when it
     loads a wiki (whether from the network or its internal local-edit
     cache), in the form of an "winfo" object:

     {
       name: string,
       mimetype: mimetype string,
       type: "normal" | "tag" | "checkin" | "branch" | "sandbox",
       type: "normal" | "tag" | "checkin" | "branch" | "ticket" | "sandbox",
       version: UUID string or null for a sandbox page or new page,
       parent: parent UUID string or null if no parent,
       isEmpty: true if page has no content (is "deleted").
       content: string, optional in most contexts
     }

     The internal docs and code frequently use the term "winfo", and such
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
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







-
+














-
+







            old = ndx[key];
      const record = old || (ndx[key]={
        name: winfo.name
      });
      record.mimetype = winfo.mimetype;
      record.type = winfo.type;
      record.parent = winfo.parent;
      record.version = winfo.version;      
      record.version = winfo.version;
      record.stashTime = new Date().getTime();
      record.isEmpty = !!winfo.isEmpty;
      record.attachments = winfo.attachments;
      this.storeIndex();
      if(arguments.length>1){
        if(content) delete record.isEmpty;
        F.storage.set(this.contentKey(key), content);
      }
      this._fireStashEvent();
      return this;
    },
    /**
       Returns the stashed content, if any, for the given winfo
       object.
    */       
    */
    stashedContent: function(winfo){
      return F.storage.get(this.contentKey(this.indexKey(winfo)));
    },
    /** Returns true if we have stashed content for the given winfo
        record or page name. */
    hasStashedContent: function(winfo){
      if('string'===typeof winfo) winfo = {name: winfo};
268
269
270
271
272
273
274
275

276
277
278
279
280
281
282
268
269
270
271
272
273
274

275
276
277
278
279
280
281
282







-
+







        console.warn("Pruned oldest local file edit entry:",e);
      }
      if(n) this._fireStashEvent();
    }
  };
  $stash.prune.defaultMaxCount = P.config.defaultMaxStashSize || 10;
  P.$stash = $stash /* we have to expose this for the new-page case :/ */;
  

  /**
     Internal workaround to select the current preview mode
     and fire a change event if the value actually changes
     or if forceEvent is truthy.
  */
  P.selectMimetype = function(modeValue, forceEvent){
    const s = this.e.selectMimetype;
534
535
536
537
538
539
540

541
542
543
544
545
546
547
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548







+







    */
    addNewPage: function(name){
      name = name.trim();
      if(!this.validatePageName(name)) return false;
      var wtype = 'normal';
      if(0===name.indexOf('checkin/')) wtype = 'checkin';
      else if(0===name.indexOf('branch/')) wtype = 'branch';
      else if(0===name.indexOf('ticket/')) wtype = 'ticket';
      else if(0===name.indexOf('tag/')) wtype = 'tag';
      /* ^^^ note that we're not validating that, e.g., checkin/XYZ
         has a full artifact ID after "checkin/". */
      const winfo = {
        name: name, type: wtype, mimetype: 'text/x-markdown',
        version: null, parent: null
      };
571
572
573
574
575
576
577
578

579
580
581
582
583
584
585
572
573
574
575
576
577
578

579
580
581
582
583
584
585
586







-
+







      D.attr(sel, 'size', 12);
      D.option(D.disable(D.clearElement(sel)), undefined, "Loading...");

      /** Set up filter checkboxes for the various types
          of wiki pages... */
      const fsFilter = D.addClass(D.fieldset("Page types"),"page-types-list"),
            fsFilterBody = D.div(),
            filters = ['normal', 'branch/...', 'tag/...', 'checkin/...']
            filters = ['normal', 'branch/...', 'tag/...', 'checkin/...', 'ticket/...']
      ;
      D.append(fsFilter, fsFilterBody);
      D.addClass(fsFilterBody, 'flex-container', 'flex-column', 'stretch');

      // Add filters by page type...
      const self = this;
      const filterByType = function(wtype, show){
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
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







-
+












-
+







      });
    }else{
      P.e.btnSave.addEventListener('click', ()=>doSave(), false);
      P.e.btnSaveClose.addEventListener('click', ()=>doSave(true), false);
    }

    P.e.taEditor.addEventListener('change', ()=>P.notifyOfChange(), false);
    

    P.selectMimetype(false, true);
    P.e.selectMimetype.addEventListener(
      'change',
      function(e){
        if(P.winfo && P.winfo.mimetype !== e.target.value){
          P.winfo.mimetype = e.target.value;
          P._isDirty = true;
          P.stashContentChange(true);
        }
      },
      false
    );
    

    const selectFontSize = E('select[name=editor_font_size]');
    if(selectFontSize){
      selectFontSize.addEventListener(
        "change",function(e){
          const ed = P.e.taEditor;
          ed.className = ed.className.replace(
              /\bfont-size-\d+/g, '' );
1231
1232
1233
1234
1235
1236
1237
1238

1239
1240

1241
1242
1243
1244
1245
1246
1247
1232
1233
1234
1235
1236
1237
1238

1239
1240

1241
1242
1243
1244
1245
1246
1247
1248







-
+

-
+







          encodeURIComponent(wi.name),
          "&file=",
          encodeURIComponent(a.filename)
        ].join(''),
        "raw/"+a.src
      ].forEach(function(url){
        const imgUrl = D.append(D.addClass(D.span(), 'monospace'), url);
        const urlCopy = D.span();
        const urlCopy = D.button();
        const li = D.li(ul);
        D.append(li, urlCopy, " ", imgUrl);
        D.append(li, urlCopy, imgUrl);
        F.copyButton(urlCopy, {copyFromElement: imgUrl});
      });
    });
    return this;
  };

  /** Updates the in-tab title/edit status information */
1454
1455
1456
1457
1458
1459
1460
1461

1462
1463
1464
1465
1466
1467
1468
1455
1456
1457
1458
1459
1460
1461

1462
1463
1464
1465
1466
1467
1468
1469







-
+







      }
    });
    return this;
  };
  
  /**
     Fetches the page preview based on the contents and settings of
     this page's input fields, and updates the UI with with the
     this page's input fields, and updates the UI with the
     preview.

     Returns this object, noting that the operation is async.
  */
  P.preview = function f(switchToTab){
    if(!affirmPageLoaded()) return this;
    return this._postPreview(this.wikiContent(), function(c){
1588
1589
1590
1591
1592
1593
1594
1595

1596
1597
1598
1599
1600
1601
1602
1589
1590
1591
1592
1593
1594
1595

1596
1597
1598
1599
1600
1601
1602
1603







-
+







    ).fetch('wikiajax/save',{
      payload: fd,
      responseType: 'json',
      onload: callee.onload
    });
    return this;
  };
  

  /**
     Updates P.winfo for certain state and stashes P.winfo, with the
     current content fetched via P.wikiContent().

     If passed truthy AND the stash already has stashed content for
     the current page, only the stashed winfo record is updated, else
     both the winfo and content are updated.
Changes to src/fossil.pikchr.js.
52
53
54
55
56
57
58
59

60
61
62
63
64
65
66
52
53
54
55
56
57
58

59
60
61
62
63
64
65
66







-
+







        if(ev.altKey || ev.metaKey || ev.ctrlKey
           /* Every combination of special key (alt, shift, ctrl,
              meta) is handled differently everywhere. Shift is used
              by the browser, Ctrl doesn't work on an iMac, and Alt is
              intercepted by most Linux window managers to control
              window movement! So...  we just listen for *any* of them
              (except Shift) and the user will need to find one which
              works on on their environment. */
              works on their environment. */
           || this.classList.contains('toggle')){
          this.classList.toggle('source');
          ev.stopPropagation();
          ev.preventDefault();
        }
      };
      /**
Changes to src/fossil.popupwidget.js.
284
285
286
287
288
289
290
291

292
293
294
295
296
297
298
284
285
286
287
288
289
290

291
292
293
294
295
296
297
298







-
+







    );
    return F.toast;
  };

  F.toast = {
    config: {
      position: { x: 5, y: 5 /*viewport-relative, pixels*/ },
      displayTimeMs: 3000
      displayTimeMs: 5000
    },
    /**
       Convenience wrapper around a PopupWidget which pops up a shared
       PopupWidget instance to show toast-style messages (commonly
       seen on Android). Its arguments may be anything suitable for
       passing to fossil.dom.append(), and each argument is first
       append()ed to the toast widget, then the widget is shown for
Changes to src/fossil.wikiedit-wysiwyg.js.
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
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







-
+









-
+







      D.removeClass(setDocMode.toHide, 'hidden');
    }
    oDoc.focus();
  }

  ////////////////////////////////////////////////////////////////////////
  // A hook which can be activated via a site skin to plug this editor
  // in to the wikiedit page.
  // into the wikiedit page.
  F.page.wysiwyg = {
    // only for debugging: oDoc: oDoc,
    /*
      Replaces wikiedit's default editor widget with this wysiwyg
      editor.

      Must either be called via an onPageLoad handler via the site
      skin's footer or else it can be called manually from the dev
      tools console. Calling it too early (e.g. in the page footer
      outside of an an onPageLoad handler) will crash because wikiedit
      outside of an onPageLoad handler) will crash because wikiedit
      has not been initialized.
    */
    init: function(){
      initDoc();
      const content = F.page.wikiContent() || '';
      var isDirty = false /* keep from stashing too often */;
      F.page.setContentMethods(
Changes to src/fshell.c.
19
20
21
22
23
24
25
26

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

26
27
28
29
30
31
32
33







-
+







**
** The fossil shell prompts for lines of user input, then parses each line
** after the fashion of a standard Bourne shell and forks a child process
** to run the corresponding Fossil command.  This only works on Unix.
**
** The "fossil shell" command is intended for use with SEE-enabled fossil.
** It allows multiple commands to be issued without having to reenter the
** crypto phasephrase for each command.
** crypto passphrase for each command.
*/
#include "config.h"
#include "fshell.h"
#include <ctype.h>

#ifndef _WIN32
# include "linenoise.h"
Changes to src/fuzz.c.
58
59
60
61
62
63
64

65
66
67
68
69
70
71
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72







+







/*
** Type of fuzzing:
*/
#define FUZZ_WIKI       0      /* The Fossil-Wiki formatter */
#define FUZZ_MARKDOWN   1      /* The Markdown formatter */
#define FUZZ_ARTIFACT   2      /* Fuzz the artifact parser */
#define FUZZ_WIKI2      3      /* FOSSIL_WIKI and FOSSIL_MARKDOWN */
#define FUZZ_COMFORMAT  4      /* comment_print() */
#endif

/* The type of fuzzing to do */
static int eFuzzType = FUZZ_WIKI;

/* The fuzzer invokes this routine once for each fuzzer input
*/
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
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







-
+


+
+
+
+
+
+
+




















+
+







      Blob title = BLOB_INITIALIZER;
      wiki_convert(&in, &out, 0);
      blob_reset(&out);
      markdown_to_html(&in, &title, &out);
      blob_reset(&title);
      break;
    }
    case FUZZ_ARTIFACT:
    case FUZZ_ARTIFACT: {
      fossil_fatal("FUZZ_ARTIFACT is not implemented.");
      break;
    }
    case FUZZ_COMFORMAT: {
      if( nByte>=3 && aData[1]!=0 && memchr(&aData[1], 0, nByte-1)!=0 ){
        int flags = (int)aData[0];
        comment_print((const char*)&aData[1],0,15,80,flags);
      }
    }
  }
  blob_reset(&in);
  blob_reset(&out);
  return 0;
}

/*
** Check fuzzer command-line options.
*/
static void fuzzer_options(void){
  const char *zType;
  db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
  db_multi_exec("PRAGMA query_only=1;");
  zType = find_option("fuzztype",0,1);
  if( zType==0 || fossil_strcmp(zType,"wiki")==0 ){
    eFuzzType = FUZZ_WIKI;
  }else if( fossil_strcmp(zType,"markdown")==0 ){
    eFuzzType = FUZZ_MARKDOWN;
  }else if( fossil_strcmp(zType,"wiki2")==0 ){
    eFuzzType = FUZZ_WIKI2;
  }else if( fossil_strcmp(zType,"comformat")==0 ){
    eFuzzType = FUZZ_COMFORMAT;
  }else{
    fossil_fatal("unknown fuzz type: \"%s\"", zType);
  }
}

/* Libfuzzer invokes this routine once prior to start-up to
** process command-line options.
137
138
139
140
141
142
143

144
145
146
147
148
149
150
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161







+







/*
** COMMAND: test-fuzz
**
** Usage: %fossil test-fuzz [-fuzztype TYPE] INPUTFILE...
**
** Run a fuzz test using INPUTFILE as the test data.  TYPE can be one of:
**
**     comformat             Fuzz the comment_print() routine
**     wiki                  Fuzz the Fossil-wiki translator
**     markdown              Fuzz the markdown translator
**     artifact              Fuzz the artifact parser
**     wiki2                 Fuzz the Fossil-wiki and markdown translator
*/
void fuzz_command(void){
  Blob in;
Changes to src/glob.c.
27
28
29
30
31
32
33
34

35
36
37
38
39
40
41
27
28
29
30
31
32
33

34
35
36
37
38
39
40
41







-
+







** zGlobList.  For example:
**
**    zVal:       "x"
**    zGlobList:  "*.o,*.obj"
**
**    Result:     "(x GLOB '*.o' OR x GLOB '*.obj')"
**
** Commas and whitespace are considered to be element delimters.  Each
** Commas and whitespace are considered to be element delimiters.  Each
** element of the GLOB list may optionally be enclosed in either '...' or
** "...".  This allows commas and/or whitespace to be used in the elements
** themselves.
**
** The returned string is owned by the caller, who must fossil_free()
** it.
*/
55
56
57
58
59
60
61
62

63
64
65
66
67
68
69
55
56
57
58
59
60
61

62
63
64
65
66
67
68
69







-
+







    if( zGlobList[0]==0 ) break;
    if( zGlobList[0]=='\'' || zGlobList[0]=='"' ){
      cTerm = zGlobList[0];
      zGlobList++;
    }else{
      cTerm = ',';
    }
    /* Find the next delimter (or the end of the string). */
    /* Find the next delimiter (or the end of the string). */
    for(i=0; zGlobList[i] && zGlobList[i]!=cTerm; i++){
      if( cTerm!=',' ) continue; /* If quoted, keep going. */
      if( fossil_isspace(zGlobList[i]) ) break; /* If space, stop. */
    }
    blob_appendf(&expr, "%s%s GLOB '%#q'", zSep, zVal, i, zGlobList);
    zSep = " OR ";
    if( cTerm!=',' && zGlobList[i] ) i++;
Changes to src/graph.c.
49
50
51
52
53
54
55
56

57
58
59
60
61
62
63
64

65
66
67
68
69
70
71
49
50
51
52
53
54
55

56
57
58
59
60
61
62
63

64
65
66
67
68
69
70
71







-
+







-
+








#if INTERFACE

/*
** The type of integer identifiers for rows of the graph.
**
** For a normal /timeline graph, the identifiers are never that big
** an an ordinary 32-bit int will work fine.  But for the /finfo page,
** an ordinary 32-bit int will work fine.  But for the /finfo page,
** the identifier is a combination of the BLOB.RID and the FILENAME.FNID
** values, and so it can become quite large for repos that have both many
** check-ins and many files.  For this reason, we make the identifier
** a 64-bit integer, to dramatically reduce the risk of an overflow.
*/
typedef sqlite3_int64 GraphRowId;

#define GR_MAX_RAIL   40      /* Max number of "rails" to display */
#define GR_MAX_RAIL   64      /* Max number of "rails" to display */

/* The graph appears vertically beside a timeline.  Each row in the
** timeline corresponds to a row in the graph.  GraphRow.idx is 0 for
** the top-most row and increases moving down.  Hence (in the absence of
** time skew) parents have a larger index than their children.
**
** The nParent field is -1 for entires that do not participate in the graph
82
83
84
85
86
87
88
89

90
91
92
93
94
95
96
82
83
84
85
86
87
88

89
90
91
92
93
94
95
96







-
+







  char *zBgClr;               /* Background Color */
  char zUuid[HNAME_MAX+1];    /* Check-in for file ID */

  GraphRow *pNext;            /* Next row down in the list of all rows */
  GraphRow *pPrev;            /* Previous row */

  int idx;                    /* Row index.  Top row is smallest. */
  int idxTop;                 /* Direct descendent highest up on the graph */
  int idxTop;                 /* Direct descendant highest up on the graph */
  GraphRow *pChild;           /* Child immediately above this node */
  u8 isDup;                   /* True if this is duplicate of a prior entry */
  u8 isLeaf;                  /* True if this is a leaf node */
  u8 isStepParent;            /* pChild is actually a step-child. The thick
                              ** arrow up to the child is dashed, not solid */
  u8 hasNormalOutMerge;       /* Is parent of at laest 1 non-cherrypick merge */
  u8 timeWarp;                /* Child is earlier in time */
116
117
118
119
120
121
122

123
124
125

126
127
128
129
130
131
132
116
117
118
119
120
121
122
123
124
125

126
127
128
129
130
131
132
133







+


-
+







  GraphRow *pLast;           /* Last row in the list. Bottom row of graph. */
  int nBranch;               /* Number of distinct branches */
  char **azBranch;           /* Names of the branches */
  int nRow;                  /* Number of rows */
  int nHash;                 /* Number of slots in apHash[] */
  u8 hasOffsetMergeRiser;    /* Merge arrow from leaf goes up on a different
                             ** rail that the node */
  u8 bOverfull;              /* Unable to allocate sufficient rails */
  u64 mergeRail;             /* Rails used for merge lines */
  GraphRow **apHash;         /* Hash table of GraphRow objects.  Key: rid */
  u8 aiRailMap[GR_MAX_RAIL]; /* Mapping of rails to actually columns */
  u8 aiRailMap[GR_MAX_RAIL+1]; /* Mapping of rails to actually columns */
};

#endif

/* The N-th bit */
#define BIT(N)  (((u64)1)<<(N))

223
224
225
226
227
228
229
230

231
232
233
234
235
236
237
224
225
226
227
228
229
230

231
232
233
234
235
236
237
238







-
+







static char *persistBranchName(GraphContext *p, const char *zBranch){
  int i;
  for(i=0; i<p->nBranch; i++){
    if( fossil_strcmp(zBranch, p->azBranch[i])==0 ) return p->azBranch[i];
  }
  p->nBranch++;
  p->azBranch = fossil_realloc(p->azBranch, sizeof(char*)*p->nBranch);
  p->azBranch[p->nBranch-1] = mprintf("%s", zBranch);
  p->azBranch[p->nBranch-1] = fossil_strdup(zBranch);
  return p->azBranch[p->nBranch-1];
}

/*
** Add a new row to the graph context.  Rows are added from top to bottom.
*/
int graph_add_row(
334
335
336
337
338
339
340
341








342
343
344
345
346
347
348
335
336
337
338
339
340
341

342
343
344
345
346
347
348
349
350
351
352
353
354
355
356







-
+
+
+
+
+
+
+
+







      if( dist<0 ) dist = -dist;
      if( dist<iBestDist ){
        iBestDist = dist;
        iBest = i;
      }
    }
  }
  if( iBestDist>1000 ) p->nErr++;
  if( iBestDist>1000 ){
    p->bOverfull = 1;
    iBest = GR_MAX_RAIL;
  }
  if( iBest>GR_MAX_RAIL ){
    p->bOverfull = 1;
    iBest = GR_MAX_RAIL;
  }
  if( iBest>p->mxRail ) p->mxRail = iBest;
  if( bMergeRail ) p->mergeRail |= BIT(iBest);
  return iBest;
}

/*
** Assign all children of node pBottom to the same rail as pBottom.
493
494
495
496
497
498
499
500





501
502
503
504
505

506
507
508
509
510
511
512
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







-
+
+
+
+
+





+







** The tmFlags parameter is zero or more of the TIMELINE_* constants.
** Only the following are honored:
**
**       TIMELINE_DISJOINT:    Omit descenders
**       TIMELINE_FILLGAPS:    Use step-children
**       TIMELINE_XMERGE:      Omit off-graph merge lines
*/
void graph_finish(GraphContext *p, const char *zLeftBranch, u32 tmFlags){
void graph_finish(
  GraphContext *p,                /* The graph to be laid out */
  Matcher *pLeftBranch,           /* Compares true for left-most branch */
  u32 tmFlags                     /* TIMELINE flags */
){
  GraphRow *pRow, *pDesc, *pDup, *pLoop, *pParent;
  int i, j;
  u64 mask;
  int hasDup = 0;      /* True if one or more isDup entries */
  const char *zTrunk;
  const char *zMainBranch;
  u8 *aMap;            /* Copy of p->aiRailMap */
  int omitDescenders = (tmFlags & TIMELINE_DISJOINT)!=0;
  int nTimewarp = 0;
  int riserMargin = (tmFlags & TIMELINE_DISJOINT) ? 0 : RISER_MARGIN;

  /* If mergeRiserFrom[X]==Y that means rail X holds a merge riser
  ** coming up from the bottom of the graph from off-screen check-in Y
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
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







-
+

+
-
+








-
+







      pRow->idxTop = pChild->idxTop;
    }
  }

  /* Identify rows where the primary parent is off screen.  Assign
  ** each to a rail and draw descenders downward.
  **
  ** Strive to put the "trunk" branch on far left.
  ** Strive to put the main branch (usually "trunk") on far left.
  */
  zMainBranch = db_main_branch();
  zTrunk = persistBranchName(p, "trunk");
  zTrunk = persistBranchName(p, zMainBranch);
  for(i=0; i<2; i++){
    for(pRow=p->pLast; pRow; pRow=pRow->pPrev){
      if( i==0 && pRow->zBranch!=zTrunk ) continue;
      if( pRow->iRail>=0 ) continue;
      if( pRow->isDup ) continue;
      if( pRow->nParent<0 ) continue;
      if( pRow->nParent==0 || hashFind(p,pRow->aParent[0])==0 ){
        pRow->iRail = findFreeRail(p, pRow->idxTop, pRow->idx+riserMargin,0,0);
        if( p->mxRail>=GR_MAX_RAIL ) return;
        /* if( p->mxRail>=GR_MAX_RAIL ) return; */
        mask = BIT(pRow->iRail);
        if( !omitDescenders ){
          int n = RISER_MARGIN;
          pRow->bDescender = pRow->nParent>0;
          for(pLoop=pRow; pLoop && (n--)>0; pLoop=pLoop->pNext){
            pLoop->railInUse |= mask;
          }
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
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







-
+
+
+
+








-
+






+
+
+
+

-
+
+
+
+







      continue;
    }else{
      assert( pRow->nParent>0 );
      parentRid = pRow->aParent[0];
      pParent = hashFind(p, parentRid);
      if( pParent==0 ){
        pRow->iRail = ++p->mxRail;
        if( p->mxRail>=GR_MAX_RAIL ) return;
        if( p->mxRail>=GR_MAX_RAIL ){
          pRow->iRail = p->mxRail = GR_MAX_RAIL;
          p->bOverfull = 1;
        }
        pRow->railInUse = BIT(pRow->iRail);
        continue;
      }
      if( pParent->idx>pRow->idx ){
        /* Common case:  Child occurs after parent and is above the
        ** parent in the timeline */
        pRow->iRail = findFreeRail(p, pRow->idxTop, pParent->idx,
                                   pParent->iRail, 0);
        if( p->mxRail>=GR_MAX_RAIL ) return;
        /* if( p->mxRail>=GR_MAX_RAIL ) return; */
        pParent->aiRiser[pRow->iRail] = pRow->idx;
      }else{
        /* Timewarp case:  Child occurs earlier in time than parent and
        ** appears below the parent in the timeline. */
        int iDownRail = ++p->mxRail;
        if( iDownRail<1 ) iDownRail = ++p->mxRail;
        if( p->mxRail>GR_MAX_RAIL ){
          iDownRail = p->mxRail = GR_MAX_RAIL;
          p->bOverfull = 1;
        }
        pRow->iRail = ++p->mxRail;
        if( p->mxRail>=GR_MAX_RAIL ) return;
        if( p->mxRail>=GR_MAX_RAIL ){
          pRow->iRail = p->mxRail = GR_MAX_RAIL;
          p->bOverfull = 1;
        }
        pRow->railInUse = BIT(pRow->iRail);
        pParent->aiRiser[iDownRail] = pRow->idx;
        mask = BIT(iDownRail);
        for(pLoop=p->pFirst; pLoop; pLoop=pLoop->pNext){
          pLoop->railInUse |= mask;
        }
      }
818
819
820
821
822
823
824
825

826
827
828
829
830
831
832
842
843
844
845
846
847
848

849
850
851
852
853
854
855
856







-
+







          if( mergeRiserFrom[j]==parentRid ){
            iMrail = j;
            break;
          }
        }
        if( iMrail==-1 ){
          iMrail = findFreeRail(p, pRow->idx, p->pLast->idx, 0, 1);
          if( p->mxRail>=GR_MAX_RAIL ) return;
          /*if( p->mxRail>=GR_MAX_RAIL ) return;*/
          mergeRiserFrom[iMrail] = parentRid;
        }
        iReuseIdx = p->nRow+1;
        iReuseRail = iMrail;
        mask = BIT(iMrail);
        if( i>=pRow->nNonCherrypick ){
          pRow->mergeIn[iMrail] = 2;
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
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
920
921
922
923
924
925







-
+















-
+





-
+














-
+







          }else{
            pDesc->hasNormalOutMerge = 1;
            pDesc->mergeUpto = pDesc->idx;
          }
        }else{
          /* Create a new merge for an on-screen node */
          createMergeRiser(p, pDesc, pRow, isCherrypick);
          if( p->mxRail>=GR_MAX_RAIL ) return;
          /* if( p->mxRail>=GR_MAX_RAIL ) return; */
          if( iReuseIdx<0
           && pDesc->nMergeChild==1
           && (pDesc->iRail!=pDesc->mergeOut || pDesc->isLeaf)
          ){
            iReuseIdx = pDesc->idx;
            iReuseRail = pDesc->mergeOut;
          }
        }
      }
    }
  }

  /*
  ** Insert merge rails from primaries to duplicates.
  */
  if( hasDup ){
  if( hasDup && p->mxRail<GR_MAX_RAIL ){
    int dupRail;
    int mxRail;
    find_max_rail(p);
    mxRail = p->mxRail;
    dupRail = mxRail+1;
    if( p->mxRail>=GR_MAX_RAIL ) return;
    /* if( p->mxRail>=GR_MAX_RAIL ) return; */
    for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
      if( !pRow->isDup ) continue;
      pRow->iRail = dupRail;
      pDesc = hashFind(p, pRow->rid);
      assert( pDesc!=0 && pDesc!=pRow );
      createMergeRiser(p, pDesc, pRow, 0);
      if( pDesc->mergeOut>mxRail ) mxRail = pDesc->mergeOut;
    }
    if( dupRail<=mxRail ){
      dupRail = mxRail+1;
      for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
        if( pRow->isDup ) pRow->iRail = dupRail;
      }
    }
    if( mxRail>=GR_MAX_RAIL ) return;
    /* if( mxRail>=GR_MAX_RAIL ) return; */
  }

  /*
  ** Find the maximum rail number.
  */
  find_max_rail(p);

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
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
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







-
-
+
+










+











-
-
-
+
+
+
+
-

-
-
+
+
+
+
+














+













-
+
+
+
+




+
-
-
+
+
+
+
+







        pRoot->aiRiser[iFrom] = -1;
      }
    }
  }

  /*
  ** Compute the rail mapping that tries to put the branch named
  ** zLeftBranch at the left margin.  Other branches that merge
  ** with zLeftBranch are to the right with merge rails in between.
  ** pLeftBranch at the left margin.  Other branches that merge
  ** with pLeftBranch are to the right with merge rails in between.
  **
  ** aMap[X]=Y means that the X-th rail is drawn as the Y-th rail.
  **
  ** Do not move rails around if there are timewarps, because that can
  ** seriously mess up the display of timewarps.  Timewarps should be
  ** rare so this should not be a serious limitation to the algorithm.
  */
  aMap = p->aiRailMap;
  for(i=0; i<=p->mxRail; i++) aMap[i] = i; /* Set up a default mapping */
  if( nTimewarp==0 ){
    int kk;
    /* Priority bits:
    **
    **    0x04      The preferred branch
    **
    **    0x02      A merge rail - a rail that contains merge lines into
    **              the preferred branch.  Only applies if a preferred branch
    **              is defined.  This improves the display of r=BRANCH
    **              options to /timeline.
    **
    **    0x01      A rail that merges with the preferred branch
    */
    u8 aPriority[GR_MAX_RAIL];
    memset(aPriority, 0, p->mxRail+1);
    if( zLeftBranch ){
    u16 aPriority[GR_MAX_RAIL];
    int mxMatch = 0;
    memset(aPriority, 0, (p->mxRail+1)*sizeof(aPriority[0]));
    if( pLeftBranch ){
      char *zLeft = persistBranchName(p, zLeftBranch);
      for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
        if( pRow->zBranch==zLeft ){
          aPriority[pRow->iRail] |= 4;
        int iMatch = match_text(pLeftBranch, pRow->zBranch);
        if( iMatch>0 ){
          if( iMatch>10 ) iMatch = 10;
          aPriority[pRow->iRail] |= 1<<(iMatch+1);
          if( mxMatch<iMatch ) mxMatch = iMatch;
          for(i=0; i<=p->mxRail; i++){
            if( pRow->mergeIn[i] ) aPriority[i] |= 1;
          }
          if( pRow->mergeOut>=0 ) aPriority[pRow->mergeOut] |= 1;
        }
      }
      for(i=0; i<=p->mxRail; i++){
        if( p->mergeRail & BIT(i) ){
          aPriority[i] |= 2;
        }
      }
    }else{
      j = 1;
      aPriority[0] = 4;
      mxMatch = 1;
      for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
        if( pRow->iRail==0 ){
          for(i=0; i<=p->mxRail; i++){
            if( pRow->mergeIn[i] ) aPriority[i] |= 1;
          }
          if( pRow->mergeOut>=0 ) aPriority[pRow->mergeOut] |= 1;
        }
      }
    }

#if 0
    fprintf(stderr,"mergeRail: 0x%llx\n", p->mergeRail);
    fprintf(stderr,"Priority:");
    for(i=0; i<=p->mxRail; i++) fprintf(stderr," %d", aPriority[i]);
    for(i=0; i<=p->mxRail; i++){
        fprintf(stderr," %x.%x",
                aPriority[i]/4, aPriority[i]&3);
    }
    fprintf(stderr,"\n");
#endif

    j = 0;
    for(kk=4; kk<=1<<(mxMatch+1); kk*=2){
    for(i=0; i<=p->mxRail; i++){
      if( aPriority[i]>=4 ) aMap[i] = j++;
      for(i=0; i<=p->mxRail; i++){
        if( aPriority[i]>=kk && aPriority[i]<kk*2 ){
          aMap[i] = j++;
        }
      }
    }
    for(i=p->mxRail; i>=0; i--){
      if( aPriority[i]==3 ) aMap[i] = j++;
    }
    for(i=0; i<=p->mxRail; i++){
      if( aPriority[i]==1 || aPriority[i]==2 ) aMap[i] = j++;
    }
Changes to src/graph.js.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1
2
3
4
5
6
7
8
9
10
11
12
13

14
15
16
17
18
19
20













-







/* This module contains javascript needed to render timeline graphs in Fossil.
**
** There can be multiple graphs on a single webpage, but this script is only
** loaded once.  
**
** Prior to sourcing this script, there should be a separate
** <script type='application/json' id='timeline-data-NN'> for each graph,
** each containing JSON like this:
**
**   { "iTableId": INTEGER,         // Table sequence number (NN)
**     "circleNodes": BOOLEAN,      // True for circle nodes. False for squares
**     "showArrowheads": BOOLEAN,   // True for arrowheads. False to omit
**     "iRailPitch": INTEGER,       // Spacing between vertical lines (px)
**     "colorGraph": BOOLEAN,       // True to put color on graph lines
**     "nomo": BOOLEAN,             // True to join merge lines with rails
**     "iTopRow": INTEGER,          // Index of top-most row in the graph
**     "omitDescenders": BOOLEAN,   // Omit ancestor lines off bottom of screen
**     "fileDiff": BOOLEAN,         // True for file diff. False for check-in
**     "scrollToSelect": BOOLEAN,   // Scroll to selection on first render
**     "nrail": INTEGER,            // Number of vertical "rails"
**     "baseUrl": TEXT,             // Top-level URL
720
721
722
723
724
725
726
727







728
729
730
731
732

733
734
735
736

737
738
739
740
741
742
743
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







-
+
+
+
+
+
+
+




-
+



-
+







    var n = x.length;
    for(var i=0; i<n; i++) {x[i].style.display = value;}
  }
  function changeDisplayById(id,value){
    var x = document.getElementById(id);
    if(x) x.style.display=value;
  }
  function toggleDetail(){
  function toggleDetail(evt){
    /* Ignore clicks to hyperlinks and other "click-responsive" HTML elements.
    ** This click-handler is set for <SPAN> elements with the CSS class names
    ** "timelineEllipsis" and "timelineCompactComment", which are part of the
    ** "Compact" and "Simple" views. */
    var xClickyHTML = /^(?:A|AREA|BUTTON|INPUT|LABEL|SELECT|TEXTAREA|DETAILS)$/;
    if( xClickyHTML.test(evt.target.tagName) ) return;
    var id = parseInt(this.getAttribute('data-id'))
    var x = document.getElementById("detail-"+id);
    if( x.style.display=="inline" ){
      x.style.display="none";
      changeDisplayById("ellipsis-"+id,"inline");
      document.getElementById("ellipsis-"+id).textContent = "...";
      changeDisplayById("links-"+id,"none");
    }else{
      x.style.display="inline";
      changeDisplayById("ellipsis-"+id,"none");
      document.getElementById("ellipsis-"+id).textContent = "←";
      changeDisplayById("links-"+id,"inline");
    }
    checkHeight();
  }
  function scrollToSelected(){
    var x = document.getElementsByClassName('timelineSelected');
    if(x[0]){
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
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







-
-
+
+




-
+
+
+



-
+
+
+







  }else{
    function checkHeight(){}
  }
  if( tx.scrollToSelect ){
    scrollToSelected();
  }

  /* Set the onclick= attributes for elements of the "Compact" display
  ** mode so that clicking turns the details on and off.
  /* Set the onclick= attributes for elements of the "Compact" and
  ** "Simple" views so that clicking turns the details on and off.
  */
  var lx = topObj.getElementsByClassName('timelineEllipsis');
  var i;
  for(i=0; i<lx.length; i++){
    if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail;
    if( lx[i].hasAttribute('data-id') ){
      lx[i].addEventListener('click',toggleDetail);
    }
  }
  lx = topObj.getElementsByClassName('timelineCompactComment');
  for(i=0; i<lx.length; i++){
    if( lx[i].hasAttribute('data-id') ) lx[i].onclick = toggleDetail;
    if( lx[i].hasAttribute('data-id') ){
      lx[i].addEventListener('click',toggleDetail);
    }
  }
  if( window.innerWidth<400 ){
    /* On narrow displays, shift the date from the first column to the
    ** third column, to make the first column narrower */
    lx = topObj.getElementsByClassName('timelineDateRow');
    for(i=0; i<lx.length; i++){
      var rx = lx[i];
Changes to src/hname.c.
197
198
199
200
201
202
203
204

205
206
207
208
209
210
211
197
198
199
200
201
202
203

204
205
206
207
208
209
210
211







-
+







  return 0;
}

/*
** Return the default hash policy for repositories that do not currently
** have an assigned hash policy.
**
** Make the default HPOLICY_AUTO if there are SHA1 artficates but no SHA3
** Make the default HPOLICY_AUTO if there are SHA1 artifacts but no SHA3
** artifacts in the repository.  Make the default HPOLICY_SHA3 if there
** are one or more SHA3 artifacts or if the repository is initially empty.
*/
int hname_default_policy(void){
  if( db_exists("SELECT 1 FROM blob WHERE length(uuid)>40")
   || !db_exists("SELECT 1 FROM blob WHERE length(uuid)==40")
  ){
Changes to src/hook.c.
27
28
29
30
31
32
33
34

35
36
37
38
39
40
41
27
28
29
30
31
32
33

34
35
36
37
38
39
40
41







-
+







**                            "cmd": "command-to-run",  // command to run
**                            "seq": 50                 // run in this order
**                         }
**
**     hook-last-rcvid     The last rcvid for which post-receive hooks were
**                         run.
**
**     hook-embargo        Do not run hooks again before this julianday.
**     hook-embargo        Do not run hooks again before this Julian day.
**
** For "after-receive" hooks, a list of the received artifacts is sent
** into the command via standard input.  Each line of input begins with
** the hash of the artifact and continues with a description of the
** interpretation of the artifact.
*/
#include "config.h"
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
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







-
+




-
+





-
+







-
+



-
+




-
+






-
+







/*
** COMMAND: hook*
**
** Usage: %fossil hook COMMAND ...
**
** Commands include:
**
** >  fossil hook add --command COMMAND --type TYPE --sequence NUMBER
** > fossil hook add --command COMMAND --type TYPE --sequence NUMBER
**
**        Create a new hook.  The --command and --type arguments are
**        required.  --sequence is optional.
**
** >  fossil hook delete ID ...
** > fossil hook delete ID ...
**
**        Delete one or more hooks by their IDs.  ID can be "all"
**        to delete all hooks.  Caution:  There is no "undo" for
**        this operation.  Deleted hooks are permanently lost.
**
** >  fossil hook edit --command COMMAND --type TYPE --sequence NUMBER ID ...
** > fossil hook edit --command COMMAND --type TYPE --sequence NUMBER ID ...
**
**        Make changes to one or more existing hooks.  The ID argument
**        is either a hook-id, or a list of hook-ids, or the keyword
**        "all".  For example, to disable hook number 2, use:
**
**            fossil hook edit --type disabled 2
**
** >  fossil hook list
** > fossil hook list
**
**        Show all current hooks
**
** >  fossil hook status
** > fossil hook status
**
**        Print the values of CONFIG table entries that are relevant to
**        hook processing.  Used for debugging.
**
** >  fossil hook test [OPTIONS] ID
** > fossil hook test [OPTIONS] ID
**
**        Run the hook script given by ID for testing purposes.
**        Options:
**
**            --dry-run          Print the script on stdout rather than run it
**            --base-rcvid N     Pretend that the hook-last-rcvid value is N
**            --new-rcvid M      Pretend that the last rcvid valud is M
**            --new-rcvid M      Pretend that the last rcvid value is M
**            --aux-file NAME    NAME is substituted for %A in the script
**
**        The --base-rcvid and --new-rcvid options are silently ignored if
**        the hook type is not "after-receive".  The default values for
**        --base-rcvid and --new-rcvid cause the last receive to be processed.
*/
void hook_cmd(void){
Changes to src/href.js.
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
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





-
-
+
+















+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+







/* As an anti-robot defense, <a> elements are initially coded with the
** href= set to the honeypot, and <form> elements are initialized with
** action= set to the login page.  The real values for href= and action=
** are held in data-href= and data-action=.  The following code moves
** data-href= into href= and data-action= into action= for all
** <a> and <form> elements, after delay and maybe also after mouse
** movement is seen.
** <a> and <form> elements, after CSS has been loaded, and after a delay,
** and maybe also after mouse movement is seen.
**
** Before sourcing this script, create a separate <script> element
** (with type='application/json' to avoid Content Security Policy issues)
** containing:
**
**     {"delay":MILLISECONDS, "mouseover":BOOLEAN}
**
** The <script> must have an id='href-data'.  DELAY is the number 
** milliseconds delay prior to populating href= and action=.  If the
** mouseover boolean is true, then the href= rewrite is further delayed
** until the first mousedown event that occurs after the timer expires.
*/
var antiRobot = 0;
function antiRobotGo(){
  if( antiRobot!=3 ) return;
  var z = window.getComputedStyle(document.body).zIndex;
  if( z==='0' || z===0 ){
  antiRobot = 7;
  var anchors = document.getElementsByTagName("a");
  for(var i=0; i<anchors.length; i++){
    var j = anchors[i];
    if(j.hasAttribute("data-href")) j.href=j.getAttribute("data-href");
  }
  var forms = document.getElementsByTagName("form");
  for(var i=0; i<forms.length; i++){
    var j = forms[i];
    if(j.hasAttribute("data-action")) j.action=j.getAttribute("data-action");
    antiRobot = 7;
    var anchors = document.getElementsByTagName("a");
    for(var i=0; i<anchors.length; i++){
      var j = anchors[i];
      if(j.hasAttribute("data-href")) j.href=j.getAttribute("data-href");
    }
    var forms = document.getElementsByTagName("form");
    for(var i=0; i<forms.length; i++){
      var j = forms[i];
      if(j.hasAttribute("data-action")) j.action=j.getAttribute("data-action");
    }
  }
}
function antiRobotDefense(){
  var x = document.getElementById("href-data");
  var jx = x.textContent || x.innerText;
  var g = JSON.parse(jx);
  if( g.mouseover ){
54
55
56
57
58
59
60

61
62
63
57
58
59
60
61
62
63
64
65
66
67







+



    setTimeout(function(){
      antiRobot |= 1;
      antiRobotGo();
    }, g.delay)
  }else{
    antiRobot |= 1;
  }
  window.addEventListener('load',antiRobotGo);
  antiRobotGo();
}
antiRobotDefense();
Changes to src/http.c.
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
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







-
-
+
+
+
+
+

-
-
+
+
+

-
+








-
+


-
+








/*
** 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.
** of all payload that follows the login card.  Randomness for the
** NONCE must be provided in the payload (in xfer.c) (e.g. by
** appending a timestamp or random bytes as a comment line to the
** payload).  SIGNATURE is the sha1 checksum of the nonce followed by
** the fossil-hashed version of the user's password.
**
** Write the constructed login card into pLogin.  pLogin is initialized
** by this routine.
** Write the constructed login card into pLogin. The result does not
** have an EOL added to it because which type of EOL it needs has to
** be determined later.  pLogin is initialized by this routine.
*/
static void http_build_login_card(Blob *pPayload, Blob *pLogin){
static void http_build_login_card(Blob * const pPayload, Blob * const 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" */
     return;  /* No login card for users "nobody" and "anonymous" */
  }
  if( g.url.isSsh ){
     return;  /* If no login card for SSH: */
     return;  /* 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 ){
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
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







-
+








-
+
+




+



+


+
+
+
+
+
+
+
-
-
+
+
-












+
+
+
+
+
+







    }
    fossil_free(g.url.passwd);
    g.url.passwd = fossil_strdup(zPw);
  }

  blob_append(&pw, zPw, -1);
  sha1sum_blob(&pw, &sig);
  blob_appendf(pLogin, "login %F %b %b\n", zLogin, &nonce, &sig);
  blob_appendf(pLogin, "login %F %b %b", zLogin, &nonce, &sig);
  blob_reset(&pw);
  blob_reset(&sig);
  blob_reset(&nonce);
}

/*
** Construct an appropriate HTTP request header.  Write the header
** into pHdr.  This routine initializes the pHdr blob.  pPayload is
** the complete payload (including the login card) already compressed.
** the complete payload (including the login card if pLogin is NULL or
** empty) already compressed.
*/
static void http_build_header(
  Blob *pPayload,              /* the payload that will be sent */
  Blob *pHdr,                  /* construct the header here */
  Blob *pLogin,                /* Login card header value or NULL */
  const char *zAltMimetype     /* Alternative mimetype */
){
  int nPayload = pPayload ? blob_size(pPayload) : 0;
  const char *zPath;

  blob_zero(pHdr);
  if( g.url.subpath ){
    zPath = g.url.subpath;
  }else if( g.url.path==0 || g.url.path[0]==0 ){
    zPath = "/";
  }else{
    zPath = g.url.path;
  }
  blob_appendf(pHdr, "%s %s%s HTTP/1.0\r\n",
               nPayload>0 ? "POST" : "GET", g.url.path,
  blob_appendf(pHdr, "%s %s HTTP/1.0\r\n",
               nPayload>0 ? "POST" : "GET", zPath);
               g.url.path[0]==0 ? "/" : "");
  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.syncInfo.fLoginCardMode>0
      && nPayload>0 && pLogin && blob_size(pLogin) ){
    /* Add sync login card via a transient cookie. We can only do this
       if we know the remote supports it. */
    blob_appendf(pHdr, "Cookie: x-f-l-c=%T\r\n", blob_str(pLogin));
  }
  if( nPayload ){
    if( zAltMimetype ){
      blob_appendf(pHdr, "Content-Type: %s\r\n", zAltMimetype);
    }else 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");
225
226
227
228
229
230
231
232

233
234
235
236
237
238
239
244
245
246
247
248
249
250

251
252
253
254
255
256
257
258







-
+







  if( save_httpauth_prompt() ){
    set_httpauth(zHttpAuth);
  }
  return zHttpAuth;
}

/*
** Send content pSend to the the server identified by g.url using the
** Send content pSend to the server identified by g.url using the
** external program given by g.zHttpCmd.  Capture the reply from that
** program and load it into pReply.
**
** This routine implements the --transport-command option for "fossil sync".
*/
static int http_exchange_external(
  Blob *pSend,                /* Message to be sent */
332
333
334
335
336
337
338
339

340
341
342
343
344
345
346
351
352
353
354
355
356
357

358
359
360
361
362
363
364
365







-
+







      fossil_print("%-20s yes\n", zHost);
    }
    db_finalize(&s);
    db_swap_connections();
  }
}

/* Add an approprate PATH= argument to the SSH command under construction
/* Add an appropriate PATH= argument to the SSH command under construction
** in pCmd.
**
** About This Feature
** ==================
**
** On some ssh servers (Macs in particular are guilty of this) the PATH
** variable in the shell that runs the command that is sent to the remote
383
384
385
386
387
388
389
390

391
392
393
394
395
396
397
402
403
404
405
406
407
408

409
410
411
412
413
414
415
416







-
+







**
**   *  The ssh_needs_path_argument() function above.
**   *  The test-ssh-needs-path command that shows the settings
**      that cache whether or not a PATH= is needed for a particular
**      HOSTNAME.
*/
void ssh_add_path_argument(Blob *pCmd){
  blob_append_escaped_arg(pCmd, 
  blob_append_escaped_arg(pCmd,
     "PATH=$HOME/bin:/usr/local/bin:/opt/homebrew/bin:$PATH", 1);
}

/*
** Return the complete text of the last HTTP reply as saved in the
** http-reply-N.txt file.  This only works if run using --httptrace.
** Without the --httptrace option, this routine returns a NULL pointer.
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
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







+









-

+



-

+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




-
+







  }

  /* Activate the PATH= auxiliary argument to the ssh command if that
  ** is called for.
  */
  if( g.url.isSsh
   && (g.url.flags & URL_SSH_RETRY)==0
   && g.db!=0
   && ssh_needs_path_argument(g.url.hostname, -1)
  ){
    g.url.flags |= URL_SSH_PATH;
  }

  if( transport_open(&g.url) ){
    fossil_warning("%s", transport_errmsg(&g.url));
    return 1;
  }

  /* Construct the login card and prepare the complete payload */
  blob_zero(&login);
  if( blob_size(pSend)==0 ){
    blob_zero(&payload);
  }else{
    blob_zero(&login);
    if( mHttpFlags & HTTP_USE_LOGIN ) http_build_login_card(pSend, &login);
    if( g.syncInfo.fLoginCardMode ){
      /* The login card will be sent via an HTTP header and/or URL flag. */
    if( g.fHttpTrace || (mHttpFlags & HTTP_NOCOMPRESS)!=0 ){
      payload = login;
      blob_append(&payload, blob_buffer(pSend), blob_size(pSend));
    }else{
      blob_compress2(&login, pSend, &payload);
      blob_reset(&login);
      if( g.fHttpTrace || (mHttpFlags & HTTP_NOCOMPRESS)!=0 ){
        /* Maintenance note: we cannot blob_swap(pSend,&payload) here
        ** because the HTTP 401 and redirect response handling below
        ** needs pSend unmodified. payload won't be modified after
        ** this point, so we can make it a proxy for pSend for
        ** zero heap memory. */
        blob_init(&payload, blob_buffer(pSend), blob_size(pSend));
      }else{
        blob_compress(pSend, &payload);
      }
    }else{
      /* Prepend the login card (if set) to the payload */
      if( blob_size(&login) ){
        blob_append_char(&login, '\n');
      }
      if( g.fHttpTrace || (mHttpFlags & HTTP_NOCOMPRESS)!=0 ){
        payload = login;
        login = empty_blob/*transfer ownership*/;
        blob_append(&payload, blob_buffer(pSend), blob_size(pSend));
      }else{
        blob_compress2(&login, pSend, &payload);
        blob_reset(&login);
      }
    }
  }

  /* Construct the HTTP request header */
  http_build_header(&payload, &hdr, zAltMimetype);
  http_build_header(&payload, &hdr, &login, zAltMimetype);

  /* When tracing, write the transmitted HTTP message both to standard
  ** output and into a file.  The file can then be used to drive the
  ** server-side like this:
  **
  **      ./fossil test-http <http-request-1.txt
  */
510
511
512
513
514
515
516



517
518
519
520
521
522
523
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564







+
+
+







                  blob_size(&hdr), blob_size(&payload));
  }
  transport_send(&g.url, &hdr);
  transport_send(&g.url, &payload);
  blob_reset(&hdr);
  blob_reset(&payload);
  transport_flip(&g.url);
  if( mHttpFlags & HTTP_VERBOSE ){
    fossil_print("IP-Address: %s\n", g.zIpAddr);
  }

  /*
  ** Read and interpret the server reply
  */
  closeConnection = 1;
  iLength = -1;
  iHttpVersion = -1;
538
539
540
541
542
543
544

545


546
547
548
549
550
551
552
579
580
581
582
583
584
585
586

587
588
589
590
591
592
593
594
595







+
-
+
+







                               maxRedirect, zAltMimetype);
        }
      }
      if( rc!=200 && rc!=301 && rc!=302 && rc!=307 && rc!=308 ){
        int ii;
        for(ii=7; zLine[ii] && zLine[ii]!=' '; ii++){}
        while( zLine[ii]==' ' ) ii++;
        if( (mHttpFlags & HTTP_QUIET)==0 ){
        fossil_warning("server says: %s", &zLine[ii]);
          fossil_warning("server says: %s", &zLine[ii]);
        }
        goto write_err;
      }
      if( iHttpVersion==0 ){
        closeConnection = 1;
      }else{
        closeConnection = 0;
      }
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
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







+


+
-
+
+
















+
-
-
+
+
+



+


+
-
+
+



+
-
+
+







+
+
-
+
+







      }else if( sqlite3_strlike("%keep-alive%", &zLine[11], 0)==0 ){
        closeConnection = 0;
      }
    }else if( ( rc==301 || rc==302 || rc==307 || rc==308 ) &&
                fossil_strnicmp(zLine, "location:", 9)==0 ){
      int i, j;
      int wasHttps;
      int priorUrlFlags;

      if ( --maxRedirect == 0){
        if( (mHttpFlags & HTTP_QUIET)==0 ){
        fossil_warning("redirect limit exceeded");
          fossil_warning("redirect limit exceeded");
        }
        goto write_err;
      }
      for(i=9; zLine[i] && zLine[i]==' '; i++){}
      if( zLine[i]==0 ){
        fossil_warning("malformed redirect: %s", zLine);
        goto write_err;
      }
      j = strlen(zLine) - 1;
      while( j>4 && fossil_strcmp(&zLine[j-4],"/xfer")==0 ){
         j -= 4;
         zLine[j] = 0;
      }
      if( (mHttpFlags & HTTP_QUIET)==0 ){
        fossil_print("redirect with status %d to %s\n", rc, &zLine[i]);
      }
      if( g.url.isFile || g.url.isSsh ){
        if( (mHttpFlags & HTTP_QUIET)==0 ){
        fossil_warning("cannot redirect from %s to %s", g.url.canonical,
                       &zLine[i]);
          fossil_warning("cannot redirect from %s to %s", g.url.canonical,
                         &zLine[i]);
        }
        goto write_err;
      }
      wasHttps = g.url.isHttps;
      priorUrlFlags = g.url.flags;
      url_parse(&zLine[i], 0);
      if( wasHttps && !g.url.isHttps ){
        if( (mHttpFlags & HTTP_QUIET)==0 ){
        fossil_warning("cannot redirect from HTTPS to HTTP");
          fossil_warning("cannot redirect from HTTPS to HTTP");
        }
        goto write_err;
      }
      if( g.url.isSsh || g.url.isFile ){
        if( (mHttpFlags & HTTP_QUIET)==0 ){
        fossil_warning("cannot redirect to %s", &zLine[i]);
          fossil_warning("cannot redirect to %s", &zLine[i]);
        }
        goto write_err;
      }
      transport_close(&g.url);
      transport_global_shutdown(&g.url);
      fSeenHttpAuth = 0;
      if( g.zHttpAuth ) free(g.zHttpAuth);
      g.zHttpAuth = get_httpauth();
      if( (rc==301 || rc==308) && (priorUrlFlags & URL_REMEMBER)!=0 ){
        g.url.flags |= URL_REMEMBER;
      if( rc==301 || rc==308 ) url_remember();
        url_remember();
      }
      return http_exchange(pSend, pReply, mHttpFlags,
                           maxRedirect, zAltMimetype);
    }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 ){
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
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







+
-
-
-
-
-
-
+
+
+
+
+
+
+


-
+







+
+
-
+









+



















+




-
+




+










-
-
-













+
+
+
-
+
+
+
+







    */
    if( g.url.isSsh                         /* This is an SSH: sync */
     && (g.url.flags & URL_SSH_EXE)==0      /* Does not have ?fossil=.... */
     && (g.url.flags & URL_SSH_RETRY)==0    /* Not retried already */
    ){
      /* Retry after flipping the SSH_PATH setting */
      transport_close(&g.url);
      if( (mHttpFlags & HTTP_QUIET)==0 ){
      fossil_print(
        "First attempt to run fossil on %s using SSH failed.\n"
        "Retrying %s the PATH= argument.\n",
        g.url.hostname,
        (g.url.flags & URL_SSH_PATH)!=0 ? "without" : "with"
      );
        fossil_print(
          "First attempt to run fossil on %s using SSH failed.\n"
          "Retrying %s the PATH= argument.\n",
          g.url.hostname,
          (g.url.flags & URL_SSH_PATH)!=0 ? "without" : "with"
        );
      }
      g.url.flags ^= URL_SSH_PATH|URL_SSH_RETRY;
      rc = http_exchange(pSend,pReply,mHttpFlags,0,zAltMimetype);
      if( rc==0 ){
      if( rc==0 && g.db!=0 ){
        (void)ssh_needs_path_argument(g.url.hostname,
                                (g.url.flags & URL_SSH_PATH)!=0);
      }
      return rc;
    }else{
      /* The problem could not be corrected by retrying.  Report the
      ** the error. */
      if( mHttpFlags & HTTP_QUIET ){
        /* no-op */
      if( g.url.isSsh && !g.fSshTrace ){
      }else if( g.url.isSsh && !g.fSshTrace ){
        fossil_warning("server did not reply: "
                       " rerun with --sshtrace for diagnostics");
      }else{
        fossil_warning("server did not reply");
      }
      goto write_err;
    }
  }
  if( rc!=200 ){
    if( mHttpFlags & HTTP_QUIET ) goto write_err;
    fossil_warning("\"location:\" missing from %d redirect reply", rc);
    goto write_err;
  }

  /*
  ** Extract the reply payload that follows the header
  */
  blob_zero(pReply);
  if( iLength==0 ){
    /* No content to read */
  }else if( iLength>0 ){
    /* Read content of a known length */
    int iRecvLen;         /* Received length of the reply payload */
    blob_resize(pReply, iLength);
    iRecvLen = transport_receive(&g.url, blob_buffer(pReply), iLength);
    if( mHttpFlags & HTTP_VERBOSE ){
      fossil_print("Reply received: %d of %d bytes\n", iRecvLen, iLength);
    }
    if( iRecvLen != iLength ){
      if( mHttpFlags & HTTP_QUIET ) goto write_err;
      fossil_warning("response truncated: got %d bytes of %d",
                     iRecvLen, iLength);
      goto write_err;
    }
  }else if( closeConnection ){
  }else{
    /* Read content until end-of-file */
    int iRecvLen;         /* Received length of the reply payload */
    unsigned int nReq = 1000;
    unsigned int nPrior = 0;
    closeConnection = 1;
    do{
      nReq *= 2;
      blob_resize(pReply, nPrior+nReq);
      iRecvLen = transport_receive(&g.url, &pReply->aData[nPrior], (int)nReq);
      nPrior += iRecvLen;
      pReply->nUsed = nPrior;
    }while( iRecvLen==nReq && nReq<0x20000000 );
    if( mHttpFlags & HTTP_VERBOSE ){
      fossil_print("Reply received: %u bytes (w/o content-length)\n", nPrior);
    }
  }else{
    assert( iLength<0 && !closeConnection );
    fossil_warning("\"content-length\" missing from %d keep-alive reply", rc);
  }
  if( isError ){
    char *z;
    int i, j;
    z = blob_str(pReply);
    for(i=j=0; z[i]; i++, j++){
      if( z[i]=='<' ){
        while( z[i] && z[i]!='>' ) i++;
        if( z[i]==0 ) break;
      }
      z[j] = z[i];
    }
    z[j] = 0;
    if( mHttpFlags & HTTP_QUIET ){
      /* no-op */
    }else if( mHttpFlags & HTTP_VERBOSE ){
    fossil_warning("server sends error: %s", z);
      fossil_warning("server sends error: %s", z);
    }else{
      fossil_warning("server sends error");
    }
    goto write_err;
  }
  if( isCompressed ) blob_uncompress(pReply, pReply);

  /*
  ** Close the connection to the server if appropriate.
  **
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
809

810




811
812
813
814
815
816
817
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
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
877
878
879
880
881
882
883
884

885
886
887
888
889
890
891
892
893
894
895







+










-
+













+







+








+

+





+
+
+














+
-
+
+
+
+







  }
  return 0;

  /*
  ** Jump to here if an error is seen.
  */
write_err:
  g.iResultCode = 1;
  transport_close(&g.url);
  return 1;
}

/*
** COMMAND: test-httpmsg
**
** Usage: %fossil test-httpmsg ?OPTIONS? URL ?PAYLOAD? ?OUTPUT?
**
** Send an HTTP message to URL and get the reply. PAYLOAD is a file containing
** the payload, or "-" to read payload from standard input.  a POST message
** the payload, or "-" to read payload from standard input.  A POST message
** is sent if PAYLOAD is specified and is non-empty.  If PAYLOAD is omitted
** or is an empty file, then a GET message is sent.
**
** If a second filename (OUTPUT) is given after PAYLOAD, then the reply
** is written into that second file instead of being written on standard
** output.  Use the "--out OUTPUT" option to specify an output file for
** a GET request where there is no PAYLOAD.
**
** Options:
**     --compress                 Use ZLIB compression on the payload
**     --mimetype TYPE            Mimetype of the payload
**     --no-cert-verify           Disable TLS cert verification
**     --out FILE                 Store the reply in FILE
**     --subpath PATH             HTTP request path for ssh: and file: URLs
**     -v                         Verbose output
**     --xfer                     PAYLOAD in a Fossil xfer protocol message
*/
void test_httpmsg_command(void){
  const char *zMimetype;
  const char *zInFile;
  const char *zOutFile;
  const char *zSubpath;
  Blob in, out;
  unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS;

  zMimetype = find_option("mimetype",0,1);
  zOutFile = find_option("out","o",1);
  if( find_option("verbose","v",0)!=0 ) mHttpFlags |= HTTP_VERBOSE;
  if( find_option("compress",0,0)!=0 ) mHttpFlags &= ~HTTP_NOCOMPRESS;
  if( find_option("no-cert-verify",0,0)!=0 ){
    #ifdef FOSSIL_ENABLE_SSL
    ssl_disable_cert_verification();
    #endif
  }
  if( find_option("xfer",0,0)!=0 ){
    mHttpFlags |= HTTP_USE_LOGIN;
    mHttpFlags &= ~HTTP_GENERIC;
  }
  if( find_option("ipv4",0,0) ) g.eIPvers = 1;
  if( find_option("ipv6",0,0) ) g.eIPvers = 2;
  zSubpath = find_option("subpath",0,1);
  verify_all_options();
  if( g.argc<3 || g.argc>5 ){
    usage("URL ?PAYLOAD? ?OUTPUT?");
  }
  zInFile = g.argc>=4 ? g.argv[3] : 0;
  if( g.argc==5 ){
    if( zOutFile ){
      fossil_fatal("output file specified twice: \"--out %s\" and \"%s\"",
        zOutFile, g.argv[4]);
    }
    zOutFile = g.argv[4];
  }
  url_parse(g.argv[2], 0);
  if( g.url.protocol[0]!='h' ){
    if( zSubpath==0 ){
    fossil_fatal("the %s command supports only http: and https:", g.argv[1]);
      fossil_fatal("the --subpath option is required for %s://",g.url.protocol);
    }else{
      g.url.subpath = fossil_strdup(zSubpath);
    }
  }
  if( zInFile ){
    blob_read_from_file(&in, zInFile, ExtFILE);
    if( zMimetype==0 && (mHttpFlags & HTTP_GENERIC)!=0 ){
      if( fossil_strcmp(zInFile,"-")==0 ){
        zMimetype = "application/x-unknown";
      }else{
Changes to src/http_socket.c.
85
86
87
88
89
90
91







92
93
94
95
96
97
98
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105







+
+
+
+
+
+
+







** Return the current socket error message
*/
char *socket_errmsg(void){
  char *zResult = socketErrMsg;
  socketErrMsg = 0;
  return zResult;
}

/*
** Return the file descriptor for the open socket.
*/
int socket_get_fd(){
  return iSocket;
}

/*
** Call this routine once before any other use of the socket interface.
** This routine does initial configuration of the socket module.
*/
void socket_global_init(void){
  if( socketIsInit==0 ){
151
152
153
154
155
156
157

158




159
160
161
162
163
164
165
158
159
160
161
162
163
164
165

166
167
168
169
170
171
172
173
174
175
176







+
-
+
+
+
+







  struct addrinfo hints;
  char zPort[30];
  char zRemote[NI_MAXHOST];

  socket_global_init();
  socket_close();
  memset(&hints, 0, sizeof(struct addrinfo));
  switch( g.eIPvers ){
  hints.ai_family = g.fIPv4 ? AF_INET : AF_UNSPEC;
    default:  hints.ai_family = AF_UNSPEC;    break;
    case 1:   hints.ai_family = AF_INET;      break;
    case 2:   hints.ai_family = AF_INET6;     break;
  }
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_protocol = IPPROTO_TCP;
  sqlite3_snprintf(sizeof(zPort),zPort,"%d", pUrlData->port);
  rc = getaddrinfo(pUrlData->name, zPort, &hints, &ai);
  if( rc ){
    socket_set_errmsg("getaddrinfo() fails: %s", gai_strerror(rc));
    goto end_socket_open;
173
174
175
176
177
178
179
180

181
182
183
184
185
186
187
184
185
186
187
188
189
190

191
192
193
194
195
196
197
198







-
+







    }
    rc = getnameinfo(p->ai_addr, p->ai_addrlen, zRemote, sizeof(zRemote),
                     0, 0, NI_NUMERICHOST);
    if( rc ){
      socket_set_errmsg("getnameinfo() failed: %s", gai_strerror(rc));
      goto end_socket_open;
    }
    g.zIpAddr = mprintf("%s", zRemote);
    g.zIpAddr = fossil_strdup(zRemote);
    break;
  }
  if( p==0 ){
    socket_set_errmsg("cannot connect to host %s:%d", pUrlData->name,
                      pUrlData->port);
    rc = 1;
  }
Changes to src/http_ssl.c.
218
219
220
221
222
223
224



225

226
227
228
229

230
231
232
233
234
235
236
218
219
220
221
222
223
224
225
226
227

228
229
230
231

232
233
234
235
236
237
238
239







+
+
+
-
+



-
+







                                           int showUtc){
  assert( showUtc==0 || showUtc==1 );
  if( !ASN1_TIME_check(asn1_time) ){
    return mprintf("Bad time value");
  }else{
    char res[20];
    char *pr = res;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    #define ASN1_STRING_get0_data ASN1_STRING_data
#endif
    const char *pt = (char *)asn1_time->data;
    const char *pt = (const char *)ASN1_STRING_get0_data(asn1_time);
    /*                   0123456789 1234
    **  UTCTime:         YYMMDDHHMMSSZ      (YY >= 50 ? 19YY : 20YY)
    **  GeneralizedTime: YYYYMMDDHHMMSSZ */
    if( asn1_time->length < 15 ){
    if( ASN1_STRING_length(asn1_time) < 15 ){
      /* UTCTime, fill out century digits */
      *pr++ = pt[0]>='5' ? '1' : '2';
      *pr++ = pt[0]>='5' ? '9' : '0';
    }else{
      /* GeneralizedTime, copy century digits and advance source */
      *pr++ = pt[0]; *pr++ = pt[1];
      pt += 2;
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
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







-
+



















-
+







  }
}

/*
** Call this routine once before any other use of the SSL interface.
** This routine does initial configuration of the SSL module.
*/
static void ssl_global_init_client(int bDebug){
static void ssl_global_init_client(void){
  const char *identityFile;

  if( sslIsInit==0 ){
    const char *zFile;
    const char *zCaFile = 0;
    const char *zCaDirectory = 0;
    int i;

    SSL_library_init();
    SSL_load_error_strings();
    OpenSSL_add_all_algorithms();
    sslCtx = SSL_CTX_new(SSLv23_client_method());
    /* Disable SSLv2 and SSLv3 */
    SSL_CTX_set_options(sslCtx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);

    /* Find the trust store */
    zFile = 0;
    for(i=0; zFile==0 && i<5; i++){
      switch( i ){
        case 0: /* First priority is environmentn variables */
        case 0: /* First priority is environment variables */
          zFile = fossil_getenv(X509_get_default_cert_file_env());
          break;
        case 1:
          zFile = fossil_getenv(X509_get_default_cert_dir_env());
          break;
        case 2:
          if( !g.repositoryOpen ) db_open_config(0,0);
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
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
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
381
382
383
384
385
386
387
388







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-


















+
-
+
+













-
-
-





-
-
-
-




















-
-
-
-
-
-
-
-
-
-
+

-











+







        }
        case 2: { /* file */
          zCaFile = zFile;
          zCaDirectory = 0;
          break;
        }
      }
      if( zFile ) break;
    }
    if( bDebug ){
      fossil_print("case-0:  X509_get_default_cert_file_env = %s\n",
                   X509_get_default_cert_file_env());
      fossil_print("case-1:  X509_get_default_cert_dir_env = %s\n",
                   X509_get_default_cert_dir_env());
      fossil_print("case-2:  ssl-ca-location = %s\n",
             g.repositoryOpen ? db_get("ssl-ca-location","(none)") : "(none)");
      fossil_print("case-3:  X509_get_default_cert_file = %s\n",
                   X509_get_default_cert_file());
      fossil_print("case-4:  X509_get_default_cert_dir = %s\n",
                   X509_get_default_cert_dir());
      if( i>=5 ){
        fossil_print("No trust store found.\n");
      }else{
        fossil_print("case-used    = %d\n"
                     "zCaFile      = %s\n"
                     "zCaDirectory = %s\n", i, zCaFile, zCaDirectory);
      }
    }
    if( zFile==0 ){
      /* fossil_fatal("Cannot find a trust store"); */
    }else if( SSL_CTX_load_verify_locations(sslCtx, zCaFile, zCaDirectory)==0 ){
      fossil_fatal("Cannot load CA root certificates from %s", zFile);
    }

/* Enable OpenSSL to use the Windows system ROOT certificate store to search for
** certificates missing in the file and directory trust stores already loaded by
** `SSL_CTX_load_verify_locations()'.
** This feature was introduced with OpenSSL 3.2.0, and may be enabled by default
** for future versions of OpenSSL, and explicit initialization may be redundant.
** NOTE TO HACKERS TWEAKING THEIR OPENSSL CONFIGURATION:
** The following OpenSSL configuration options must not be used for this feature
** to be available: `no-autoalginit', `no-winstore'. The Fossil makefiles do not
** currently set these options when building OpenSSL for Windows. */
#if defined(_WIN32)
#if OPENSSL_VERSION_NUMBER >= 0x030200000
    if( SSLeay()!=0x30500000  /* Don't use for 3.5.0 due to a bug */
    if( SSL_CTX_load_verify_store(sslCtx, "org.openssl.winstore:")==0 ){
     && SSL_CTX_load_verify_store(sslCtx, "org.openssl.winstore:")==0
    ){
      fossil_print("NOTICE: Failed to load the Windows root certificates.\n");
    }
#endif /* OPENSSL_VERSION_NUMBER >= 0x030200000 */
#endif /* _WIN32 */

    /* Load client SSL identity, preferring the filename specified on the
    ** command line */
    if( g.zSSLIdentity!=0 ){
      identityFile = g.zSSLIdentity;
    }else{
      identityFile = db_get("ssl-identity", 0);
    }
    if( identityFile!=0 && identityFile[0]!='\0' ){
      if( bDebug ){
        fossil_print("identifyFile = %s\n", identityFile);
      }
      if( SSL_CTX_use_certificate_chain_file(sslCtx,identityFile)!=1
       || SSL_CTX_use_PrivateKey_file(sslCtx,identityFile,SSL_FILETYPE_PEM)!=1
      ){
        fossil_fatal("Could not load SSL identity from %s", identityFile);
      }
    }else{
      if( bDebug ){
        fossil_print("No identify file found.\n");
      }
    }
    /* Register a callback to tell the user what to do when the server asks
    ** for a cert */
    SSL_CTX_set_client_cert_cb(sslCtx, ssl_client_cert_callback);

    sslIsInit = 1;
  }else{
    assert( sslIsInit==1 );
  }
}

/*
** Call this routine to shutdown the SSL module prior to program exit.
*/
void ssl_global_shutdown(void){
  if( sslIsInit ){
    SSL_CTX_free(sslCtx);
    ssl_clear_errmsg();
    sslIsInit = 0;
  }
}

/*
** COMMAND: test-trust-store
**
** Show the trust store that is used by OpenSSL. 
*/
void test_openssl_trust_store(void){
  ssl_global_init_client(1);
  ssl_global_shutdown();
  socket_global_shutdown();
}


/*
** Close the currently open client SSL connection.  If no connection is open,
** this routine is a no-op.
*/
void ssl_close_client(void){
  if( iBio!=NULL ){
    (void)BIO_reset(iBio);
    BIO_free_all(iBio);
    iBio = NULL;
  }
  socket_close();
}

/* See RFC2817 for details */
static int establish_proxy_tunnel(UrlData *pUrlData, BIO *bio){
  int rc, httpVerMin;
  char *bbuf;
  Blob snd, reply;
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
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







+

-
-
+
+
-
-
-
-
-
-
+
-
-
-
-
-
-
+
+
+
+
+


+




-
-
-
+
+
+
-
-
-
+
-
-
-
+
-
-
+
-
-
+
-
+




















-
-
-
-
-
-
-
-
-
-
-
-
-
-
+

+
-
+
-







**    pUrlData->port  TCP/IP port to use.  Ex: 80
**
** Return the number of errors.
*/
int ssl_open_client(UrlData *pUrlData){
  X509 *cert;
  const char *zRemoteHost;
  BIO *sBio;

  ssl_global_init_client(0);
  if( pUrlData->useProxy ){
  ssl_global_init_client();
  if( socket_open(pUrlData) ){
    int rc;
    char *connStr = mprintf("%s:%d", g.url.name, pUrlData->port);
    BIO *sBio = BIO_new_connect(connStr);
    free(connStr);
    if( BIO_do_connect(sBio)<=0 ){
      ssl_set_errmsg("SSL: cannot connect to proxy %s:%d (%s)",
    ssl_set_errmsg("SSL: cannot open socket (%s)", socket_errmsg());
            pUrlData->name, pUrlData->port,
            ERR_reason_error_string(ERR_get_error()));
      ssl_close_client();
      return 1;
    }
    rc = establish_proxy_tunnel(pUrlData, sBio);
    return 1;
  }
  sBio = BIO_new_socket(socket_get_fd(), 0);
  if( pUrlData->useProxy ){
    int rc = establish_proxy_tunnel(pUrlData, sBio);
    if( rc<200||rc>299 ){
      ssl_set_errmsg("SSL: proxy connect failed with HTTP status code %d", rc);
      ssl_close_client();
      return 1;
    }

    pUrlData->path = pUrlData->proxyUrlPath;

    iBio = BIO_new_ssl(sslCtx, 1);
    BIO_push(iBio, sBio);
  }
  iBio = BIO_new_ssl(sslCtx, 1);
  BIO_push(iBio, sBio);
    zRemoteHost = pUrlData->hostname;
  }else{
    iBio = BIO_new_ssl_connect(sslCtx);
  BIO_set_ssl(sBio, ssl, BIO_NOCLOSE);
    zRemoteHost = pUrlData->name;
  }
  if( iBio==NULL ) {
  BIO_set_ssl_mode(iBio, 1);
    ssl_set_errmsg("SSL: cannot open SSL (%s)",
                    ERR_reason_error_string(ERR_get_error()));
  BIO_get_ssl(iBio, &ssl);
    return 1;
  }

  BIO_get_ssl(iBio, &ssl);
  zRemoteHost = pUrlData->useProxy ? pUrlData->hostname : pUrlData->name;

#if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT)
  if( !SSL_set_tlsext_host_name(ssl, zRemoteHost)){
    fossil_warning("WARNING: failed to set server name indication (SNI), "
                  "continuing without it.\n");
  }
#endif

  SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
#if OPENSSL_VERSION_NUMBER >= 0x010002000
  if( !sslNoCertVerify ){
    X509_VERIFY_PARAM *param = 0;
    param = SSL_get0_param(ssl);
    if( !X509_VERIFY_PARAM_set1_host(param, zRemoteHost, strlen(zRemoteHost)) ){
      fossil_fatal("failed to set hostname.");
    }
    /* SSL_set_verify(ssl, SSL_VERIFY_PEER, 0); */
  }
#endif

  if( !pUrlData->useProxy ){
    char *connStr = mprintf("%s:%d", pUrlData->name, pUrlData->port);
    BIO_set_conn_hostname(iBio, connStr);
    free(connStr);
    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_client();
      return 1;
    }
  }

  if( BIO_do_handshake(iBio)<=0 ) {
  if( BIO_do_handshake(iBio)<=0 ){
    ssl_set_errmsg("Error establishing SSL connection %s:%d (%s)",
        zRemoteHost,
        pUrlData->useProxy?pUrlData->hostname:pUrlData->name,
        pUrlData->useProxy ? pUrlData->proxyOrigPort : pUrlData->port,
        pUrlData->useProxy?pUrlData->proxyOrigPort:pUrlData->port,
        ERR_reason_error_string(ERR_get_error()));
    ssl_close_client();
    return 1;
  }
  /* Check if certificate is valid */
  cert = SSL_get_peer_certificate(ssl);

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
592
593
594
595
596
597
598






















599
600
601
602
603
604
605







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







        db_open_config(0,0);
        ssl_remember_certificate_exception(pUrlData, zHash);
      }
      blob_reset(&ans);
    }
  }

  /* Set the Global.zIpAddr variable to the server we are talking to.
  ** This is used to populate the ipaddr column of the rcvfrom table,
  ** if any files are received from the server.
  */
  {
  /* As soon as libressl implements
  ** BIO_ADDR_hostname_string/BIO_get_conn_address.
  ** check here for the correct LIBRESSL_VERSION_NUMBER too. For now: disable
  */
#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x10100000L \
      && !defined(LIBRESSL_VERSION_NUMBER)
    char *ip = BIO_ADDR_hostname_string(BIO_get_conn_address(iBio),1);
    g.zIpAddr = mprintf("%s", ip);
    OPENSSL_free(ip);
#else
    /* IPv4 only code */
    const unsigned char *ip;
    ip = (const unsigned char*)BIO_ptr_ctrl(iBio,BIO_C_GET_CONNECT,2);
    g.zIpAddr = mprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
#endif
  }

  X509_free(cert);
  return 0;
}

/*
** Remember that the cert with the given hash is acceptable for
** use with pUrlData->name.
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
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







-
-
+
+









-
-
-
+
+
+

-
-
-
+
+
+

-
-
+
+







static void trust_location_usable(const char *zPath, const char **pzStore){
  if( *pzStore!=0 ) return;
  if( file_isdir(zPath, ExtFILE)>0 ) *pzStore = zPath;
}
#endif /* FOSSIL_ENABLE_SSL */

/*
** COMMAND: tls-config*
** COMMAND: ssl-config
** COMMAND: tls-config*                       abbrv-subcom
** COMMAND: ssl-config                        abbrv-subcom
**
** Usage: %fossil ssl-config [SUBCOMMAND] [OPTIONS...] [ARGS...]
**
** This command is used to view or modify the TLS (Transport Layer
** Security) configuration for Fossil.  TLS (formerly SSL) is the
** encryption technology used for secure HTTPS transport.
**
** Sub-commands:
**
**   remove-exception DOMAINS    Remove TLS cert exceptions for the domains
**                               listed.  Or remove them all if the --all
**                               option is specified.
**    remove-exception DOMAINS    Remove TLS cert exceptions for the domains
**                                listed.  Or remove them all if the --all
**                                option is specified.
**
**   scrub ?--force?             Remove all SSL configuration data from the
**                               repository. Use --force to omit the
**                               confirmation.
**    scrub ?--force?             Remove all SSL configuration data from the
**                                repository. Use --force to omit the
**                                confirmation.
**
**   show ?-v?                   Show the TLS configuration. Add -v to see
**                               additional explanation
**    show ?-v?                   Show the TLS configuration. Add -v to see
**                                additional explanation
*/
void test_tlsconfig_info(void){
  const char *zCmd;
  size_t nCmd;
  int nHit = 0;

  db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
1021
1022
1023
1024
1025
1026
1027
1028
1029


1030
1031
1032
1033
1034
1035
1036
945
946
947
948
949
950
951


952
953
954
955
956
957
958
959
960







-
-
+
+







    fossil_print("OpenSSL-version:      (none)\n");
    if( verbose ){
      fossil_print("\n"
         "  The OpenSSL library is not used by this build of Fossil\n\n"
      );
    }
#else
    fossil_print("OpenSSL-version:      %s  (0x%09x)\n",
         SSLeay_version(SSLEAY_VERSION), OPENSSL_VERSION_NUMBER);
    fossil_print("OpenSSL-version:      %s  (0x%09llx)\n",
         SSLeay_version(SSLEAY_VERSION), (unsigned long long)SSLeay());
    if( verbose ){
      fossil_print("\n"
         "  The version of the OpenSSL library being used\n"
         "  by this instance of Fossil.  Version 3.0.0 or\n"
         "  later is recommended.\n\n"
      );
    }
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092


1093
1094
1095
1096

1097
1098
1099



1100
1101
1102
1103
1104
1105
1106
1007
1008
1009
1010
1011
1012
1013



1014
1015


1016
1017
1018



1019
1020
1021
1022
1023
1024
1025
1026
1027
1028







-
-
-
+
+
-
-


+
-
-
-
+
+
+







         "    the identity of servers for \"https:\" URLs. These values\n"
         "    come into play when Fossil is used as a TLS client.  These\n"
         "    values are built into your OpenSSL library.\n\n"
      );
    }

#if defined(_WIN32)
#if OPENSSL_VERSION_NUMBER >= 0x030200000
    fossil_print("  OpenSSL-winstore:   Yes\n");
#else /* OPENSSL_VERSION_NUMBER >= 0x030200000 */
    fossil_print("  OpenSSL-winstore:   %s\n",
         (SSLeay()>=0x30200000 && SSLeay()!=0x30500000) ? "Yes" : "No");
    fossil_print("  OpenSSL-winstore:   No\n");
#endif /* OPENSSL_VERSION_NUMBER >= 0x030200000 */
    if( verbose ){
      fossil_print("\n"
         "    OpenSSL 3.2.0, or newer, but not version 3.5.0 due to a bug,\n"
         "    OpenSSL 3.2.0, or newer, use the root certificates managed by\n"
         "    the Windows operating system. The installed root certificates\n"
         "    are listed by the command:\n\n"
         "    are able to use the root certificates managed by the Windows\n"
         "    operating system. The installed root certificates are listed\n"
         "    by the command:\n\n"
         "        certutil -store \"ROOT\"\n\n"
      );
    }
#endif /* _WIN32 */

    if( zUsed==0 ) zUsed = "";
    fossil_print("  Trust store used:   %s\n", zUsed);
1254
1255
1256
1257
1258
1259
1260
1261

1262
1263
1264
1265
1176
1177
1178
1179
1180
1181
1182

1183
1184
1185
1186
1187







-
+




** Return the OpenSSL version number being used.  Space to hold
** this name is obtained from fossil_malloc() and should be
** freed by the caller.
*/
char *fossil_openssl_version(void){
#if defined(FOSSIL_ENABLE_SSL)
  return mprintf("%s (0x%09x)\n",
         SSLeay_version(SSLEAY_VERSION), OPENSSL_VERSION_NUMBER);
         SSLeay_version(SSLEAY_VERSION), (sqlite3_uint64)SSLeay());
#else
  return mprintf("none");
#endif
}
Changes to src/http_transport.c.
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
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







+
+
-
+
+














-
+



















-
+







#endif

/*
** Initialize a Blob to the name of the configured SSH command.
*/
void transport_ssh_command(Blob *p){
  char *zSsh;        /* The base SSH command */
  zSsh = g.zSshCmd;
  if( zSsh==0 || zSsh[0]==0 ){
  zSsh = db_get("ssh-command", zDefaultSshCmd);
    zSsh = db_get("ssh-command", zDefaultSshCmd);
  }
  blob_init(p, zSsh, -1);
}

/*
** SSH initialization of the transport layer
*/
int transport_ssh_open(UrlData *pUrlData){
  /* For SSH we need to create and run SSH fossil http
  ** to talk to the remote machine.
  */
  Blob zCmd;         /* The SSH command */
  char *zHost;       /* The host name to contact */

  fossil_free(g.zIpAddr);
  g.zIpAddr = mprintf("%s", pUrlData->name);
  g.zIpAddr = fossil_strdup(pUrlData->name);
  transport_ssh_command(&zCmd);
  if( pUrlData->port!=pUrlData->dfltPort && pUrlData->port ){
    blob_appendf(&zCmd, " -p %d", pUrlData->port);
  }
  blob_appendf(&zCmd, " -T --");  /* End of switches */
  if( pUrlData->user && pUrlData->user[0] ){
    zHost = mprintf("%s@%s", pUrlData->user, pUrlData->name);
    blob_append_escaped_arg(&zCmd, zHost, 0);
    fossil_free(zHost);
  }else{
    blob_append_escaped_arg(&zCmd, pUrlData->name, 0);
  }
  if( (pUrlData->flags & URL_SSH_EXE)!=0
   && !is_safe_fossil_command(pUrlData->fossil)
  ){
    fossil_fatal("the ssh:// URL is asking to run an unsafe command [%s] on "
                 "the server.", pUrlData->fossil);
  }
  if( (pUrlData->flags & URL_SSH_EXE)==0
   && (pUrlData->flags & URL_SSH_PATH)!=0 
   && (pUrlData->flags & URL_SSH_PATH)!=0
  ){
    ssh_add_path_argument(&zCmd);
  }
  blob_append_escaped_arg(&zCmd, pUrlData->fossil, 1);
  blob_append(&zCmd, " test-http", 10);
  if( pUrlData->path && pUrlData->path[0] ){
    blob_append_escaped_arg(&zCmd, pUrlData->path, 1);
240
241
242
243
244
245
246
247

248
249
250
251
252
253
254
243
244
245
246
247
248
249

250
251
252
253
254
255
256
257







-
+







    transport.isOpen = 0;
  }
}

/*
** Send content over the wire.
*/
void transport_send(UrlData *pUrlData, Blob *toSend){
void transport_send(UrlData const *pUrlData, const Blob *toSend){
  char *z = blob_buffer(toSend);
  int n = blob_size(toSend);
  transport.nSent += n;
  if( pUrlData->isSsh ){
    fwrite(z, 1, n, sshOut);
    fflush(sshOut);
  }else if( pUrlData->isHttps ){
Changes to src/import.c.
566
567
568
569
570
571
572
573
574


575
576
577
578
579
580
581
566
567
568
569
570
571
572


573
574
575
576
577
578
579
580
581







-
-
+
+







      /* The argument to the "commit" line might match either of these
      ** patterns:
      **
      **   (A)  refs/heads/BRANCHNAME
      **   (B)  refs/tags/TAGNAME
      **
      ** If pattern A is used, then the branchname used is as shown.
      ** Except, the "master" branch which is the default branch name in
      ** Git is changed to "trunk" which is the default name in Fossil.
      ** Except, the "master" branch which is the default branch name in Git
      ** is changed to the default main branch name in Fossil (usually "trunk")
      ** If the pattern is B, then the new commit should be on the same
      ** branch as its parent.  And, we might need to add the TAGNAME
      ** tag to the new commit.  However, if there are multiple instances
      ** of pattern B with the same TAGNAME, then only put the tag on the
      ** last commit that holds that tag.
      **
      ** None of the above is explained in the git-fast-export
893
894
895
896
897
898
899
900

901
902
903
904
905
906
907
893
894
895
896
897
898
899

900
901
902
903
904
905
906
907







-
+







  if( feof(pIn) ) return 0;
  do{
    char *sep;
    if( zLine[0]=='\n' ) break;
    rec->nHeaders += 1;
    rec->aHeaders = fossil_realloc(rec->aHeaders,
      sizeof(rec->aHeaders[0])*rec->nHeaders);
    rec->aHeaders[rec->nHeaders-1].zKey = mprintf("%s", zLine);
    rec->aHeaders[rec->nHeaders-1].zKey = fossil_strdup(zLine);
    sep = strchr(rec->aHeaders[rec->nHeaders-1].zKey, ':');
    if( !sep ){
      trim_newline(zLine);
      fossil_fatal("bad header line: [%s]", zLine);
    }
    *sep = 0;
    rec->aHeaders[rec->nHeaders-1].zVal = sep+1;
1261
1262
1263
1264
1265
1266
1267

1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281

1282
1283

1284
1285
1286
1287
1288
1289

1290
1291
1292
1293
1294
1295
1296
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
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







+














+

-
+





-
+







/*
** Extract the branch or tag that the given path is on. Return the branch ID.
** Return 0 if not a branch, tag, or trunk, or if ignored by --ignore-tree.
*/
static int svn_parse_path(char *zPath, char **zFile, int *type){
  char *zBranch = 0;
  int branchId = 0;
  const char *zMainBranch;
  if( gsvn.azIgnTree ){
    const char **pzIgnTree;
    unsigned nPath = strlen(zPath);
    for( pzIgnTree = gsvn.azIgnTree; *pzIgnTree; ++pzIgnTree ){
      const char *zIgn = *pzIgnTree;
      unsigned nIgn = strlen(zIgn);
      if( strncmp(zPath, zIgn, nIgn) == 0
       && ( nPath == nIgn || (nPath > nIgn && zPath[nIgn] == '/')) ){
        return 0;
      }
    }
  }
  *type = SVN_UNKNOWN;
  *zFile = 0;
  zMainBranch = db_main_branch();
  if( gsvn.lenTrunk==0 ){
    zBranch = "trunk";
    zBranch = fossil_strdup(zMainBranch);
    *zFile = zPath;
    *type = SVN_TRUNK;
  }else
  if( strncmp(zPath, gsvn.zTrunk, gsvn.lenTrunk-1)==0 ){
    if( zPath[gsvn.lenTrunk-1]=='/' || zPath[gsvn.lenTrunk-1]==0 ){
      zBranch = "trunk";
      zBranch = fossil_strdup(zMainBranch);
      *zFile = zPath+gsvn.lenTrunk;
      *type = SVN_TRUNK;
    }else{
      zBranch = 0;
      *type = SVN_UNKNOWN;
    }
  }else{
1424
1425
1426
1427
1428
1429
1430
1431
1432


1433
1434
1435
1436
1437
1438
1439
1426
1427
1428
1429
1430
1431
1432


1433
1434
1435
1436
1437
1438
1439
1440
1441







-
-
+
+







        fossil_free(gsvn.zUser);
        fossil_free(gsvn.zComment);
        fossil_free(gsvn.zDate);
        bag_clear(&gsvn.newBranches);
      }
      /* start new revision */
      gsvn.rev = atoi(zTemp);
      gsvn.zUser = mprintf("%s", svn_find_prop(rec, "svn:author"));
      gsvn.zComment = mprintf("%s", svn_find_prop(rec, "svn:log"));
      gsvn.zUser = fossil_strdup(svn_find_prop(rec, "svn:author"));
      gsvn.zComment = fossil_strdup(svn_find_prop(rec, "svn:log"));
      zDate = svn_find_prop(rec, "svn:date");
      if( zDate ){
        gsvn.zDate = date_in_standard_format(zDate);
      }else{
        gsvn.zDate = date_in_standard_format("now");
      }
      db_bind_int(&addRev, ":rev", gsvn.rev);
1768
1769
1770
1771
1772
1773
1774
1775

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

1777
1778
1779
1780
1781
1782
1783
1784







-
+







       }else{
         *renOpt->varPre = renOpt->zDefaultPre;
         *renOpt->varSuf = renOpt->zDefaultSuf;
       }
    }
  }
  if( !(gimport.zTrunkName = find_option("rename-trunk", 0, 1)) ){
    gimport.zTrunkName = "trunk";
    gimport.zTrunkName = fossil_strdup(db_main_branch());
  }

  if( svnFlag ){
    /* Get --svn related options here, so verify_all_options() fails when
     * svn-only options are specified with --git
     */
    const char *zIgnTree;
Changes to src/info.c.
255
256
257
258
259
260
261
262

263
264
265
266
267
268


269
270
271
272
273
274
275
255
256
257
258
259
260
261

262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277







-
+






+
+







      }else{
        z = blob_str(&vx);
      }
      fossil_print("fossil:       %z\n", file_fullexename(g.nameOfExe));
      fossil_print("version:      %s", z);
      blob_reset(&vx);
    }
  }else{
  }else if( g.repositoryOpen ){
    int rid;
    rid = name_to_rid(g.argv[2]);
    if( rid==0 ){
      fossil_fatal("no such object: %s", g.argv[2]);
    }
    show_common_info(rid, "hash:", 1, 1);
  }else{
    fossil_fatal("Could not find or open a Fossil repository");
  }
}

/*
** Show the context graph (immediate parents and children) for
** check-in rid and rid2
*/
318
319
320
321
322
323
324

325
326
327
328
329
330
331
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334







+







         |TIMELINE_GRAPH
         |TIMELINE_FILLGAPS
         |TIMELINE_NOSCROLL
         |TIMELINE_XMERGE
         |TIMELINE_CHPICK,
       0, 0, 0, rid, rid2, 0);
  db_finalize(&q);
  blob_reset(&sql);
}


/*
** Append the difference between artifacts to the output
*/
static void append_diff(
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

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

447


448
449
450
451
452
453
454
455
456
457
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
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
447
448
449
450
451
452

453

454
455
456
457
458
459
460

461
462
463
464
465
466
467







-
+
+
+


















+




+
+




-
+


-
+



-
+






-
+

-
+


-
+











-
+


-
+




+

-
+
-

+

+
+


-







  const char *zName,    /* Name of the file that has changed */
  const char *zOld,     /* blob.uuid before change.  NULL for added files */
  const char *zNew,     /* blob.uuid after change.  NULL for deletes */
  const char *zOldName, /* Prior name.  NULL if no name change. */
  DiffConfig *pCfg,     /* Flags for text_diff() or NULL to omit all */
  int mperm             /* executable or symlink permission for zNew */
){
  @ <p>
  @ <div class='file-change-line'><span>
    /* Maintenance reminder: the extra level of SPAN is for
    ** arranging new elements via JS. */
  if( !g.perm.Hyperlink ){
    if( zNew==0 ){
      @ Deleted %h(zName).
    }else if( zOld==0 ){
      @ Added %h(zName).
    }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
      @ Name change from %h(zOldName) to %h(zName).
    }else if( fossil_strcmp(zNew, zOld)==0 ){
      if( mperm==PERM_EXE ){
        @ %h(zName) became executable.
      }else if( mperm==PERM_LNK ){
        @ %h(zName) became a symlink.
      }else{
        @ %h(zName) became a regular file.
      }
    }else{
      @ Changes to %h(zName).
    }
    @ </span></div>
    if( pCfg ){
      append_diff(zOld, zNew, pCfg);
    }
  }else{
    const char *zCkin2 =
      mprintf(validate16(zCkin, -1) ? "%!S" : "%T"/*works-like:"%s"*/, zCkin);
    if( zOld && zNew ){
      if( fossil_strcmp(zOld, zNew)!=0 ){
        if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
          @ Renamed and modified
          @ %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zOldName,zOld,zCkin))\
          @ %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zOldName,zOld,zCkin2))\
          @ %h(zOldName)</a>
          @ %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>
          @ to %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\
          @ to %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\
          @ %h(zName)</a>
          @ %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
        }else{
          @ Modified %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\
          @ Modified %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\
          @ %h(zName)</a>
          @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>
          @ to %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
        }
      }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
        @ Name change
        @ from %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zOldName,zOld,zCkin))\
        @ from %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zOldName,zOld,zCkin2))\
        @ %h(zOldName)</a>
        @ to %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\
        @ to %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\
        @ %h(zName)</a>.
      }else{
        @ %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\
        @ %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\
        @ %h(zName)</a> became
        if( mperm==PERM_EXE ){
          @ executable with contents
        }else if( mperm==PERM_LNK ){
          @ a symlink with target
        }else{
          @ a regular file with contents
        }
        @ %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
      }
    }else if( zOld ){
      @ Deleted %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zOld,zCkin))\
      @ Deleted %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zOld,zCkin2))\
      @ %h(zName)</a> version %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>.
    }else{
      @ Added %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\
      @ Added %z(href("%R/finfo?name=%T&m=%!S&ci=%s",zName,zNew,zCkin2))\
      @ %h(zName)</a> version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
    }
    if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
      if( pCfg ){
        @ </span></div>
        append_diff(zOld, zNew, pCfg);
      }else{ 
      }else{
        @ &nbsp;&nbsp;
        @ %z(href("%R/fdiff?v1=%!S&v2=%!S",zOld,zNew))[diff]</a>
        @ </span></div>
      }
    }else{
      @ </span></div>
    }
  }
  @ </p>
}

/*
** Generate javascript to enhance HTML diffs.
*/
void append_diff_javascript(int diffType){
  if( diffType==0 ) return;
600
601
602
603
604
605
606



























































































































































































































































































607
608
609
610
611
612
613
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
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
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC");
  db_prepare(&q, "%s", blob_sql_text(&sql));
  www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL,
                     0, 0, 0, rid, 0, 0);
  db_finalize(&q);
  style_finish_page();
}

/*
** Render a web-page diff of the changes in the working check-out
*/
static void ckout_normal_diff(int vid){
  int diffType;               /* 0: no diff,  1: unified,  2: side-by-side */
  DiffConfig DCfg,*pCfg;      /* Diff details */
  const char *zW;             /* The "w" query parameter */
  int nChng;                  /* Number of changes */
  Stmt q;

  diffType = preferred_diff_type();
  pCfg = construct_diff_flags(diffType, &DCfg);
  nChng = db_int(0, "SELECT count(*) FROM vfile"
                    " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid);
  if( nChng==0 ){
    @ <p>No uncommitted changes</p>
    return;
  }
  db_prepare(&q,
       /*   0         1        2        3       4    5       6 */
    "SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid"
    "  FROM vfile LEFT JOIN blob USING(rid)"
    " WHERE vid=%d"
    "   AND (deleted OR chnged OR rid==0)"
    " ORDER BY pathname /*scan*/",
    vid
  );
  if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
    DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
  }else{
    DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
  }
  @ <div class="section" id="changes_section">Changes</div>
  DCfg.diffFlags |= DIFF_NUMSTAT; /* Show stats in the 'Changes' section */
  @ <div class="sectionmenu info-changes-menu">
  zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
  if( diffType!=1 ){
    @ %z(chref("button","%R?diff=1%s",zW))Unified&nbsp;Diff</a>
  }
  if( diffType!=2 ){
    @ %z(chref("button","%R?diff=2%s",zW))Side-by-Side&nbsp;Diff</a>
  }
  if( diffType!=0 ){
    if( *zW ){
      @ %z(chref("button","%R?diff=%d",diffType))\
      @ Show&nbsp;Whitespace&nbsp;Changes</a>
    }else{
      @ %z(chref("button","%R?diff=%d&w",diffType))Ignore&nbsp;Whitespace</a>
    }
  }
  @ </div>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zTreename = db_column_text(&q,0);
    int isDeleted = db_column_int(&q, 1);
    int isChnged = db_column_int(&q,2);
    int isNew = db_column_int(&q,3);
    int srcid = db_column_int(&q, 4);
    int isLink = db_column_int(&q, 5);
    const char *zUuid = db_column_text(&q, 6);
    int showDiff = 1;

    DCfg.diffFlags &= (~DIFF_FILE_MASK);
    @ <div class='file-change-line'><span>
    if( isDeleted ){
      @ DELETED %h(zTreename)
      DCfg.diffFlags |= DIFF_FILE_DELETED;
      showDiff = 0;
    }else if( file_access(zTreename, F_OK) ){
      @ MISSING %h(zTreename)
      showDiff = 0;
    }else if( isNew ){
      @ ADDED %h(zTreename)
      DCfg.diffFlags |= DIFF_FILE_ADDED;
      srcid = 0;
      showDiff = 0;
    }else if( isChnged==3 ){
      @ ADDED_BY_MERGE %h(zTreename)
      DCfg.diffFlags |= DIFF_FILE_ADDED;
      srcid = 0;
      showDiff = 0;
    }else if( isChnged==5 ){
      @ ADDED_BY_INTEGRATE %h(zTreename)
      DCfg.diffFlags |= DIFF_FILE_ADDED;
      srcid = 0;
      showDiff = 0;
    }else{
      @ CHANGED %h(zTreename)
    }
    @ </span></div>
    if( showDiff && pCfg ){
      Blob old, new;
      if( !isLink != !file_islink(zTreename) ){
        @ %s(DIFF_CANNOT_COMPUTE_SYMLINK)
        continue;
      }
      if( srcid>0 ){
        content_get(srcid, &old);
        pCfg->zLeftHash = zUuid;
      }else{
        blob_zero(&old);
        pCfg->zLeftHash = 0;
      }
      blob_read_from_file(&new, zTreename, ExtFILE);
      text_diff(&old, &new, cgi_output_blob(), pCfg);
      blob_reset(&old);
      blob_reset(&new);
    }
  }
  db_finalize(&q);
  @ <script nonce='%h(style_nonce())'>;/* info.c:%d(__LINE__) */
  @ document.getElementById('changes_section').textContent =  'Changes ' +
  @   '(%d(g.diffCnt[0]) file' + (%d(g.diffCnt[0])===1 ? '' : 's') + ': ' +
  @   '+%d(g.diffCnt[1]) ' +
  @   '−%d(g.diffCnt[2]))'
  @ </script>
  append_diff_javascript(diffType);
}

/*
** Render a web-page diff of the changes in the working check-out to
** an external reference.
*/
static void ckout_external_base_diff(int vid, const char *zExBase){
  int diffType;               /* 0: no diff,  1: unified,  2: side-by-side */
  DiffConfig DCfg,*pCfg;      /* Diff details */
  const char *zW;             /* The "w" query parameter */
  Stmt q;

  diffType = preferred_diff_type();
  pCfg = construct_diff_flags(diffType, &DCfg);
  db_prepare(&q,
    "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", vid
  );
  if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
    DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
  }else{
    DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
  }
  @ <div class="section" id="changes_section">Changes</div>
  DCfg.diffFlags |= DIFF_NUMSTAT; /* Show stats in the 'Changes' section */
  @ <div class="sectionmenu info-changes-menu">
  zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
  if( diffType!=1 ){
    @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\
    @ Unified&nbsp;Diff</a>
  }
  if( diffType!=2 ){
    @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\
    @ Side-by-Side&nbsp;Diff</a>
  }
  if( diffType!=0 ){
    if( *zW ){
      @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\
      @ Show&nbsp;Whitespace&nbsp;Changes</a>
    }else{
      @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\
      @ Ignore&nbsp;Whitespace</a>
    }
  }
  @ </div>
  while( db_step(&q)==SQLITE_ROW ){
    const char *zFile;  /* Name of file in the repository */
    char *zLhs;         /* Full name of left-hand side file */
    char *zRhs;         /* Full name of right-hand side file */
    Blob rhs;           /* Full text of RHS */
    Blob lhs;           /* Full text of LHS */

    zFile = db_column_text(&q,0);
    zLhs = mprintf("%s/%s", zExBase, zFile);
    zRhs = mprintf("%s%s", g.zLocalRoot, zFile);
    if( file_size(zLhs, ExtFILE)<0 ){
      @ <div class='file-change-line'><span>
      @ Missing from external baseline: %h(zFile)
      @ </span></div>
    }else{
      blob_read_from_file(&lhs, zLhs, ExtFILE);
      blob_read_from_file(&rhs, zRhs, ExtFILE);
      if( blob_size(&lhs)!=blob_size(&rhs)
       || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0
      ){
        @ <div class='file-change-line'><span>
        @ Changes to %h(zFile)
        @ </span></div>
        if( pCfg ){
          char *zFullFN;
          char *zHexFN;
          zFullFN = file_canonical_name_dup(zLhs);
          zHexFN = mprintf("x%H", zFullFN);
          fossil_free(zFullFN);
          pCfg->zLeftHash = zHexFN;
          text_diff(&lhs, &rhs, cgi_output_blob(), pCfg);
          pCfg->zLeftHash = 0;
          fossil_free(zHexFN);
        }
      }
      blob_reset(&lhs);
      blob_reset(&rhs);
    }
    fossil_free(zLhs);
    fossil_free(zRhs);
  }
  db_finalize(&q);
  @ <script nonce='%h(style_nonce())'>;/* info.c:%d(__LINE__) */
  @ document.getElementById('changes_section').textContent =  'Changes ' +
  @   '(%d(g.diffCnt[0]) file' + (%d(g.diffCnt[0])===1 ? '' : 's') + ': ' +
  @   '+%d(g.diffCnt[1]) ' +
  @   '−%d(g.diffCnt[2]))'
  @ </script>
  append_diff_javascript(diffType);
}

/*
** WEBPAGE: ckout
**
** Show information about the current checkout.  This page only functions
** if the web server is run on a loopback interface (in other words, was
** started using "fossil ui" or similar) from within an open check-out.
**
** If the "exbase=PATH" query parameter is provided, then the diff shown
** uses the files in PATH as the baseline.  This is the same as using
** the "--from PATH" argument to the "fossil diff" command-line.  In fact,
** when using "fossil ui --from PATH", the --from argument becomes the value
** of the exbase query parameter for the start page.  Note that if PATH
** is a pure hexadecimal string, it is decoded first before being used as
** the pathname.  Real pathnames should contain at least one directory
** separator character.
**
** Other query parameters related to diffs are also accepted.
*/
void ckout_page(void){
  int vid;
  const char *zHome;          /* Home directory */
  int nHome;
  const char *zExBase;
  char *zHostname;
  char *zCwd;

  if( !cgi_is_loopback(g.zIpAddr) || !db_open_local(0) ){
    cgi_redirectf("%R/home");
    return;
  }
  file_chdir(g.zLocalRoot, 0);
  vid = db_lget_int("checkout", 0);
  db_unprotect(PROTECT_ALL);
  vfile_check_signature(vid, CKSIG_ENOTFILE);
  db_protect_pop();
  style_set_current_feature("vinfo");
  zHostname = fossil_hostname();
  zCwd = file_getcwd(0,0);
  zHome = fossil_getenv("HOME");
  if( zHome ){
    nHome = (int)strlen(zHome);
    if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){
      zCwd = mprintf("~%s", zCwd+nHome);
    }
  }else{
    nHome = 0;
  }
  if( zHostname ){
    style_header("Checkout Status: %h on %h", zCwd, zHostname);
  }else{
    style_header("Checkout Status: %h", zCwd);
  }
  render_checkin_context(vid, 0, 0, 0);
  @ <hr>
  zExBase = P("exbase");
  if( zExBase && zExBase[0] ){
    char *zPath = decode16_dup(zExBase);
    char *zCBase = file_canonical_name_dup(zPath?zPath:zExBase);
    if( nHome && strncmp(zCBase, zHome, nHome)==0 && zCBase[nHome]=='/' ){
      @ <p>Using external baseline: ~%h(zCBase+nHome)</p>
    }else{
      @ <p>Using external baseline: %h(zCBase)</p>
    }
    ckout_external_base_diff(vid, zCBase);
    fossil_free(zCBase);
    fossil_free(zPath);
  }else{
    ckout_normal_diff(vid);
  }
  style_finish_page();
}

/*
** WEBPAGE: vinfo
** WEBPAGE: ci
** URL:  /ci/ARTIFACTID
**  OR:  /ci?name=ARTIFACTID
**
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
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







-















-
+








-
-
+
+







  const char *zName;   /* Name of the check-in to be displayed */
  const char *zUuid;   /* Hash of zName, found via blob.uuid */
  const char *zParent; /* Hash of the parent check-in (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 */
  const char *zBrName;          /* Branch name */
  DiffConfig DCfg,*pCfg;        /* Type of diff */

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  style_set_current_feature("vinfo");
  zName = P("name");
  rid = name_to_rid_www("name");
  if( rid==0 ){
    style_header("Check-in Information Error");
    @ No such object: %h(zName)
    style_finish_page();
    return;
  }
  zRe = P("regex");
  if( zRe ) re_compile(&pRe, zRe, 0);
  if( zRe ) fossil_re_compile(&pRe, zRe, 0);
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  zParent = db_text(0,
    "SELECT uuid FROM plink, blob"
    " WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim",
    rid
  );
  isLeaf = !db_exists("SELECT 1 FROM plink WHERE pid=%d", rid);
  db_prepare(&q1,
     "SELECT uuid, datetime(mtime,toLocal()), user, comment,"
     "       datetime(omtime,toLocal()), mtime"
     "SELECT uuid, datetime(mtime,toLocal(),'subsec'), user, comment,"
     "       datetime(omtime,toLocal(),'subsec'), mtime"
     "  FROM blob, event"
     " WHERE blob.rid=%d"
     "   AND event.objid=%d",
     rid, rid
  );
  zBrName = branch_of_rid(rid);

675
676
677
678
679
680
681
682

683
684
685
686
687
688
689
967
968
969
970
971
972
973

974
975
976
977
978
979
980
981







-
+







    const char *zComment;
    const char *zDate;
    const char *zOrigDate;
    int okWiki = 0;
    Blob wiki_read_links = BLOB_INITIALIZER;
    Blob wiki_add_links = BLOB_INITIALIZER;

    Th_Store("current_checkin", zName);
    Th_StoreUnsafe("current_checkin", zName);
    style_header("Check-in [%S]", zUuid);
    login_anonymous_available();
    zEUser = db_text(0,
                   "SELECT value FROM tagxref"
                   " WHERE tagid=%d AND rid=%d AND tagtype>0",
                    TAG_USER, rid);
    zEComment = db_text(0,
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
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







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
-
+







    @ <div class="accordion_panel">
    @ <table class="label-value">
    @ <tr><th>Comment:</th><td class="infoComment">\
    @ %!W(zEComment?zEComment:zComment)</td></tr>

    /* The Download: line */
    if( g.perm.Zip  ){
      char *zPJ = db_get("short-project-name", 0);
      char *zUrl;
      Blob projName;
      int jj;
      if( zPJ==0 ) zPJ = db_get("project-name", "unnamed");
      blob_zero(&projName);
      blob_append(&projName, zPJ, -1);
      blob_trim(&projName);
      zPJ = blob_str(&projName);
      for(jj=0; zPJ[jj]; jj++){
        if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){
          zPJ[jj] = '_';
        }
      }
      zUrl = mprintf("%R/tarball/%S/%t-%S.tar.gz", zUuid, zPJ, zUuid);
      @ <tr><th>Downloads:</th><td>
      if( robot_would_be_restricted("download") ){
        @ See separate %z(href("%R/rchvdwnld/%!S",zUuid))download page</a>
      }else{
        char *zBase = archive_base_name(rid);
      @ %z(href("%s",zUrl))Tarball</a>
      @ | %z(href("%R/zip/%S/%t-%S.zip",zUuid, zPJ,zUuid))ZIP archive</a>
      @ | %z(href("%R/sqlar/%S/%t-%S.sqlar",zUuid,zPJ,zUuid))\
      @ SQL archive</a></td></tr>
      fossil_free(zUrl);
        @ %z(href("%R/tarball/%s.tar.gz",zBase))Tarball</a>
        @ | %z(href("%R/zip/%s.zip",zBase))ZIP archive</a>
        if( g.zLogin!=0 ){
          @ | %z(href("%R/sqlar/%s.sqlar",zBase))\
          @ SQL archive</a></td></tr>
        }
        fossil_free(zBase);
      blob_reset(&projName);
      }
    }

    @ <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>
    }
856
857
858
859
860
861
862

863
864
865

866
867
868
869
870
871
872
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148

1149
1150
1151
1152
1153
1154
1155
1156







+


-
+







      }else if( zLinks[0] ){
        zLinks += 3;
      }
      @ %s(zLinks)</td></tr>
    }

    if( g.perm.Hyperlink ){
      const char *zMainBranch = db_main_branch();
      @ <tr><th>Other&nbsp;Links:</th>
      @   <td>
      if( fossil_strcmp(zBrName, db_get("main-branch",0))!=0 ){
      if( fossil_strcmp(zBrName, zMainBranch)!=0 ){
        @ %z(href("%R/vdiff?branch=%!S", zUuid))branch diff</a> |
      }
      @ %z(href("%R/artifact/%!S",zUuid))manifest</a>
      @ | %z(href("%R/ci_tags/%!S",zUuid))tags</a>
      if( g.perm.Admin ){
        @   | %z(href("%R/mlink?ci=%!S",zUuid))mlink table</a>
      }
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
1173
1174
1175
1176
1177
1178
1179

1180
1181
1182
1183
1184
1185
1186
1187




1188
1189

1190
1191
1192
1193

1194
1195
1196
1197

1198
1199
1200

1201
1202
1203
1204
1205
1206
1207
1208







-
+




+


-
-
-
-


-
+



-
+



-
+


-
+







  if( !PB("nowiki") ){
    wiki_render_associated("checkin", zUuid, 0);
  }
  render_backlink_graph(zUuid,
       "<div class=\"section accordion\">References</div>\n");
  @ <div class="section accordion">Context</div><div class="accordion_panel">
  render_checkin_context(rid, 0, 0, 0);
  @ </div><div class="section accordion">Changes</div>
  @ </div><div class="section accordion" id="changes_section">Changes</div>
  @ <div class="accordion_panel">
  @ <div class="sectionmenu info-changes-menu">
  /* ^^^ .info-changes-menu is used by diff scroll sync */
  pCfg = construct_diff_flags(diffType, &DCfg);
  DCfg.diffFlags |= DIFF_NUMSTAT; /* Show stats in the 'Changes' section */
  DCfg.pRe = pRe;
  zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
  if( diffType!=0 ){
    @ %z(chref("button","%R/%s/%T?diff=0",zPageHide,zName))\
    @ Hide&nbsp;Diffs</a>
  }
  if( diffType!=1 ){
    @ %z(chref("button","%R/%s/%T?diff=1%s",zPage,zName,zW))\
    @ Unified&nbsp;Diffs</a>
    @ Unified&nbsp;Diff</a>
  }
  if( diffType!=2 ){
    @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
    @ Side-by-Side&nbsp;Diffs</a>
    @ Side-by-Side&nbsp;Diff</a>
  }
  if( diffType!=0 ){
    if( *zW ){
      @ %z(chref("button","%R/%s/%T",zPage,zName))
      @ %z(chref("button","%R/%s/%T?diff=%d",zPage,zName,diffType))
      @ Show&nbsp;Whitespace&nbsp;Changes</a>
    }else{
      @ %z(chref("button","%R/%s/%T?w",zPage,zName))
      @ %z(chref("button","%R/%s/%T?diff=%d&w",zPage,zName,diffType))
      @ Ignore&nbsp;Whitespace</a>
    }
  }
  if( zParent ){
    @ %z(chref("button","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
    @ Patch</a>
  }
953
954
955
956
957
958
959








960
961
962
963
964
965
966
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255







+
+
+
+
+
+
+
+







    const char *zNew = db_column_text(&q3,3);
    const char *zOldName = db_column_text(&q3, 4);
    append_file_change_line(zUuid, zName, zOld, zNew, zOldName,
                            pCfg,mperm);
  }
  db_finalize(&q3);
  @ </div>
  if( diffType!=0 ){
    @ <script nonce='%h(style_nonce())'>;/* info.c:%d(__LINE__) */
    @ document.getElementById('changes_section').textContent =  'Changes ' +
    @   '(%d(g.diffCnt[0]) file' + (%d(g.diffCnt[0])===1 ? '' : 's') + ': ' +
    @   '+%d(g.diffCnt[1]) ' +
    @   '−%d(g.diffCnt[2]))'
    @ </script>
  }
  append_diff_javascript(diffType);
  style_finish_page();
}

/*
** WEBPAGE: winfo
** URL:  /winfo?name=HASH
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
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
1169
1170
1171
1385
1386
1387
1388
1389
1390
1391






























































1392
1393
1394
1395
1396
1397
1398







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







  }
  if( !is_a_version(rid) ){
    webpage_error("Artifact %s is not a check-in.", P(zParam));
    return 0;
  }
  return manifest_get(rid, CFTYPE_MANIFEST, 0);
}

#if 0 /* not used */
/*
** Output a description of a check-in
*/
static void checkin_description(int rid){
  Stmt q;
  db_prepare(&q,
    "SELECT datetime(mtime), coalesce(euser,user),"
    "       coalesce(ecomment,comment), uuid,"
    "      (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref"
    "        WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
    "          AND tagxref.rid=blob.rid AND tagxref.tagtype>0)"
    "  FROM event, blob"
    " WHERE event.objid=%d AND type='ci'"
    "   AND blob.rid=%d",
    rid, rid
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zDate = db_column_text(&q, 0);
    const char *zUser = db_column_text(&q, 1);
    const char *zUuid = db_column_text(&q, 3);
    const char *zTagList = db_column_text(&q, 4);
    Blob comment;
    int wikiFlags = WIKI_INLINE|WIKI_NOBADLINKS;
    if( db_get_boolean("timeline-block-markup", 0)==0 ){
      wikiFlags |= WIKI_NOBLOCK;
    }
    hyperlink_to_version(zUuid);
    blob_zero(&comment);
    db_column_blob(&q, 2, &comment);
    wiki_convert(&comment, 0, wikiFlags);
    blob_reset(&comment);
    @ (user:
    hyperlink_to_user(zUser,zDate,",");
    if( zTagList && zTagList[0] && g.perm.Hyperlink ){
      int i;
      const char *z = zTagList;
      Blob links;
      blob_zero(&links);
      while( z && z[0] ){
        for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){}
        blob_appendf(&links,
              "%z%#h</a>%.2s",
              href("%R/timeline?r=%#t&nd&c=%t",i,z,zDate), i,z, &z[i]
        );
        if( z[i]==0 ) break;
        z += i+2;
      }
      @ tags: %s(blob_str(&links)),
      blob_reset(&links);
    }else{
      @ tags: %h(zTagList),
    }
    @ date:
    hyperlink_to_date(zDate, ")");
    tag_private_status(rid);
  }
  db_finalize(&q);
}
#endif /* not used */


/*
** WEBPAGE: vdiff
** URL: /vdiff?from=TAG&to=TAG
**
** Show the difference between two check-ins identified by the from= and
** to= query parameters.
1202
1203
1204
1205
1206
1207
1208

1209
1210
1211
1212
1213
1214
1215

1216
1217
1218
1219
1220
1221
1222
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442

1443
1444
1445
1446
1447
1448
1449
1450







+






-
+







  int graphFlags = 0;
  Blob qp;                /* non-glob= query parameters for generated links */
  Blob qpGlob;            /* glob= query parameter for generated links */
  int bInvert = PB("inv");

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( robot_restrict("diff") ) return;
  login_anonymous_available();
  fossil_nice_default();
  blob_init(&qp, 0, 0);
  blob_init(&qpGlob, 0, 0);
  diffType = preferred_diff_type();
  zRe = P("regex");
  if( zRe ) re_compile(&pRe, zRe, 0);
  if( zRe ) fossil_re_compile(&pRe, zRe, 0);
  zBranch = P("branch");
  if( zBranch && zBranch[0]==0 ) zBranch = 0;
  if( zBranch ){
    blob_appendf(&qp, "branch=%T", zBranch);
    zMergeOrigin = mprintf("merge-in:%s", zBranch);
    cgi_replace_parameter("from", zMergeOrigin);
    cgi_replace_parameter("to", zBranch);
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1494
1495
1496
1497
1498
1499
1500



1501
1502
1503
1504
1505
1506
1507







-
-
-







    blob_appendf(&qp, "&w");
  }
  cgi_check_for_malice();
  style_set_current_feature("vdiff");
  if( zBranch==0 ){
    style_submenu_element("Path", "%R/timeline?me=%T&you=%T", zFrom, zTo);
  }
  if( diffType!=0 ){
    style_submenu_element("Hide Diff", "%R/vdiff?diff=0&%b%b", &qp, &qpGlob);
  }
  if( diffType!=2 ){
    style_submenu_element("Side-by-Side Diff", "%R/vdiff?diff=2&%b%b", &qp,
                          &qpGlob);
  }
  if( diffType!=1 ) {
    style_submenu_element("Unified Diff", "%R/vdiff?diff=1&%b%b", &qp, &qpGlob);
  }
1666
1667
1668
1669
1670
1671
1672
1673

1674
1675
1676
1677
1678
1679
1680
1891
1892
1893
1894
1895
1896
1897

1898
1899
1900
1901
1902
1903
1904
1905







-
+







      blob_append(pDownloadName, zFilename, -1);
    }
    tag_private_status(rid);
  }
  db_finalize(&q);
  if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d AND tagid=%d",
                rid, TAG_CLUSTER) ){
    @ Cluster
    @ Cluster %z(href("%R/info/%S",zUuid))%S(zUuid)</a>.
    cnt++;
  }
  if( cnt==0 ){
    @ Unrecognized artifact
    if( pDownloadName && blob_size(pDownloadName)==0 ){
      blob_appendf(pDownloadName, "%S.txt", zUuid);
    }
1709
1710
1711
1712
1713
1714
1715


1716
1717




1718
1719




1720
1721
1722
1723






1724
1725
1726
1727
1728
1729
1730
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
1964
1965
1966
1967
1968







+
+


+
+
+
+
-
-
+
+
+
+



-
+
+
+
+
+
+







**    *  The "diff" query parameter
**    *  The "diff" field of the user display cookie
**    *  The "preferred-diff-type" setting
**    *  1 for mobile and 2 for desktop, based on the UserAgent
*/
int preferred_diff_type(void){
  int dflt;
  int res;
  int isBot;
  static char zDflt[2]
    /*static b/c cookie_link_parameter() does not copy it!*/;
  if( robot_would_be_restricted("diff") ){
    dflt = 0;
    isBot = 1;
  }else{
  dflt = db_get_int("preferred-diff-type",-99);
  if( dflt<=0 ) dflt = user_agent_is_likely_mobile() ? 1 : 2;
    dflt = db_get_int("preferred-diff-type",-99);
    if( dflt<=0 ) dflt = user_agent_is_likely_mobile() ? 1 : 2;
    isBot = 0;
  }
  zDflt[0] = dflt + '0';
  zDflt[1] = 0;
  cookie_link_parameter("diff","diff", zDflt);
  return atoi(PD_NoBot("diff",zDflt));
  res = atoi(PD_NoBot("diff",zDflt));
  if( isBot && res>0 && robot_restrict("diff") ){
    cgi_reply();
    fossil_exit(0);
  }
  return res;
}


/*
** WEBPAGE: fdiff
** URL: fdiff?v1=HASH&v2=HASH
**
1758
1759
1760
1761
1762
1763
1764

1765
1766
1767
1768
1769

1770
1771
1772
1773

1774
1775
1776
1777
1778
1779
1780
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021







+





+




+







  ReCompiled *pRe = 0;
  u32 objdescFlags = 0;
  int verbose = PB("verbose");
  DiffConfig DCfg;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( robot_restrict("diff") ) return;
  diff_config_init(&DCfg, 0);
  diffType = preferred_diff_type();
  if( P("from") && P("to") ){
    v1 = artifact_from_ci_and_filename("from");
    v2 = artifact_from_ci_and_filename("to");
    if( v1==0 || v2==0 ) fossil_redirect_home();
  }else{
    Stmt q;
    v1 = name_to_rid_www("v1");
    v2 = name_to_rid_www("v2");
    if( v1==0 || v2==0 ) fossil_redirect_home();

    /* If the two file versions being compared both have the same
    ** filename, then offer an "Annotate" link that constructs an
    ** annotation between those version. */
    db_prepare(&q,
      "SELECT (SELECT substr(uuid,1,20) FROM blob WHERE rid=a.mid),"
      "       (SELECT substr(uuid,1,20) FROM blob WHERE rid=b.mid),"
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806

1807
1808
1809
1810
1811
1812
1813
2037
2038
2039
2040
2041
2042
2043

2044
2045

2046
2047
2048
2049
2050
2051
2052
2053







-


-
+







      const char *zFN = db_column_text(&q, 2);
      style_submenu_element("Annotate",
        "%R/annotate?origin=%s&checkin=%s&filename=%T",
        zOrig, zCkin, zFN);
    }
    db_finalize(&q);
  }
  if( v1==0 || v2==0 ) fossil_redirect_home();
  zRe = P("regex");
  cgi_check_for_malice();
  if( zRe ) re_compile(&pRe, zRe, 0);
  if( zRe ) fossil_re_compile(&pRe, zRe, 0);
  if( verbose ) objdescFlags |= OBJDESC_DETAIL;
  if( isPatch ){
    Blob c1, c2, *pOut;
    DiffConfig DCfg;
    pOut = cgi_output_blob();
    cgi_set_content_type("text/plain");
    DCfg.diffFlags = DIFF_VERBOSE;
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
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
1994
1995
1996

1997
1998
1999
2000
2001
2002
2003
2004
2005
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
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







+
+
+
+
+
+















+




















+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+

-
-
-
-
+

-








/*
** WEBPAGE: jchunk hidden
** URL: /jchunk/HASH?from=N&to=M
**
** Return lines of text from a file as a JSON array - one entry in the
** array for each line of text.
**
** The HASH is normally a sha1 or sha3 hash that identifies an artifact
** in the BLOB table of the database.  However, if HASH starts with an "x"
** and is followed by valid hexadecimal, and if we are running in a
** "fossil ui" situation (locally and with privilege), then decode the hex
** into a filename and read the file content from that name.
**
** **Warning:**  This is an internal-use-only interface that is subject to
** change at any moment.  External application should not use this interface
** since the application will break when this interface changes, and this
** interface will undoubtedly change.
**
** This page is intended to be used in an XHR from javascript on a
** diff page, to return unseen context to fill in additional context
** when the user clicks on the appropriate button. The response is
** always in JSON form and errors are reported as documented for
** ajax_route_error().
*/
void jchunk_page(void){
  int rid = 0;
  const char *zName = PD("name", "");
  int nName = (int)(strlen(zName)&0x7fffffff);
  int iFrom = atoi(PD("from","0"));
  int iTo = atoi(PD("to","0"));
  int ln;
  int go = 1;
  const char *zSep;
  Blob content;
  Blob line;
  Blob *pOut;

  if(0){
    ajax_route_error(400, "Just testing client-side error handling.");
    return;
  }

  login_check_credentials();
  cgi_check_for_malice();
  if( !g.perm.Read ){
    ajax_route_error(403, "Access requires Read permissions.");
    return;
  }
  if( iFrom<1 || iTo<iFrom ){
    ajax_route_error(500, "Invalid line range from=%d, to=%d.",
                     iFrom, iTo);
    return;
  }
  if( zName[0]=='x'
   && ((nName-1)&1)==0
   && validate16(&zName[1],nName-1)
   && g.perm.Admin
   && cgi_is_loopback(g.zIpAddr)
   && db_open_local(0)
  ){
    /* Treat the HASH as a hex-encoded filename */
    int n = (nName-1)/2;
    char *zFN = fossil_malloc(n+1);
    decode16((const u8*)&zName[1], (u8*)zFN, nName-1);
    zFN[n] = 0;
    if( file_size(zFN, ExtFILE)<0 ){
      blob_zero(&content);
    }else{
      blob_read_from_file(&content, zFN, ExtFILE);
    }
    fossil_free(zFN);
  }else{
    /* Treat the HASH as an artifact hash matching BLOB.UUID */
#if 1
  /* Re-enable this block once this code is integrated somewhere into
     the UI. */
  rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
  if( rid==0 ){
    ajax_route_error(404, "Unknown artifact: %h", zName);
    return;
  }
    /* Re-enable this block once this code is integrated somewhere into
       the UI. */
    rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
    if( rid==0 ){
      ajax_route_error(404, "Unknown artifact: %h", zName);
      return;
    }
#else
  /* This impl is only to simplify "manual" testing via the JS
     console. */
  rid = symbolic_name_to_rid(zName, "*");
  if( rid==0 ){
    ajax_route_error(404, "Unknown artifact: %h", zName);
    return;
  }else if( rid<0 ){
    ajax_route_error(418, "Ambiguous artifact name: %h", zName);
    return;
  }
    /* This impl is only to simplify "manual" testing via the JS
       console. */
    rid = symbolic_name_to_rid(zName, "*");
    if( rid==0 ){
      ajax_route_error(404, "Unknown artifact: %h", zName);
      return;
    }else if( rid<0 ){
      ajax_route_error(418, "Ambiguous artifact name: %h", zName);
      return;
    }
#endif
  if( iFrom<1 || iTo<iFrom ){
    ajax_route_error(500, "Invalid line range from=%d, to=%d.",
                     iFrom, iTo);
    return;
    content_get(rid, &content);
  }
  content_get(rid, &content);
  g.isConst = 1;
  cgi_set_content_type("application/json");
  ln = 0;
  while( go && ln<iFrom ){
    go = blob_line(&content, &line);
    ln++;
  }
2169
2170
2171
2172
2173
2174
2175
2176

2177
2178
2179
2180

2181
2182
2183
2184
2185
2186
2187
2437
2438
2439
2440
2441
2442
2443

2444
2445
2446
2447

2448
2449
2450
2451
2452
2453
2454
2455







-
+



-
+







  blob_zero(&downloadName);
  if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
  object_description(rid, objdescFlags, 0, &downloadName);
  style_submenu_element("Download", "%R/raw/%s?at=%T",
                        zUuid, file_tail(blob_str(&downloadName)));
  @ <hr>
  content_get(rid, &content);
  if( !g.isHuman ){
  if( blob_size(&content)>100000 ){
    /* Prevent robots from running hexdump on megabyte-sized source files
    ** and there by eating up lots of CPU time and bandwidth.  There is
    ** no good reason for a robot to need a hexdump. */
    @ <p>A hex dump of this file is not available.
    @ <p>A hex dump of this file is not available because it is too large.
    @  Please download the raw binary file and generate a hex dump yourself.</p>
  }else{
    @ <blockquote><pre>
    hexdump(&content);
    @ </pre></blockquote>
  }
  style_finish_page();
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
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694







+






+








+
+







+







  fossil_print("%b\n", cgi_output_blob());
}

/*
** WEBPAGE: artifact
** WEBPAGE: file
** WEBPAGE: whatis
** WEBPAGE: docfile
**
** Typical usage:
**
**    /artifact/HASH
**    /whatis/HASH
**    /file/NAME
**    /docfile/NAME
**
** Additional query parameters:
**
**   ln              - show line numbers
**   ln=N            - highlight line number N
**   ln=M-N          - highlight lines M through N inclusive
**   ln=M-N+Y-Z      - highlight lines M through N and Y through Z (inclusive)
**   verbose         - show more detail in the description
**   brief           - show just the document, not the metadata.  The
**                     /docfile page is an alias for /file?brief
**   download        - redirect to the download (artifact page only)
**   name=NAME       - filename or hash as a query parameter
**   filename=NAME   - alternative spelling for "name="
**   fn=NAME         - alternative spelling for "name="
**   ci=VERSION      - The specific check-in to use with "name=" to
**                     identify the file.
**   txt             - Force display of unformatted source text
**   hash            - Output only the hash of the artifact
**
** The /artifact page show the complete content of a file
** identified by HASH.  The /whatis page shows only a description
** of how the artifact is used.  The /file page shows the most recent
** version of the file or directory called NAME, or a list of the
** top-level directory if NAME is omitted.
**
2430
2431
2432
2433
2434
2435
2436

2437
2438
2439
2440
2441
2442
2443
2444


2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458




2459
2460
2461
2462
2463
2464
2465
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745







+








+
+














+
+
+
+







** a default value of "tip" is used for ci= if ci= is omitted.
*/
void artifact_page(void){
  int rid = 0;
  Blob content;
  const char *zMime;
  Blob downloadName;
  Blob uuid;
  int renderAsWiki = 0;
  int renderAsHtml = 0;
  int renderAsSvg = 0;
  int objType;
  int asText;
  const char *zUuid = 0;
  u32 objdescFlags = OBJDESC_BASE;
  int descOnly = fossil_strcmp(g.zPath,"whatis")==0;
  int hashOnly = P("hash")!=0;
  int docOnly = P("brief")!=0;
  int isFile = fossil_strcmp(g.zPath,"file")==0;
  const char *zLn = P("ln");
  const char *zName = P("name");
  const char *zCI = P("ci");
  HQuery url;
  char *zCIUuid = 0;
  int isSymbolicCI = 0;  /* ci= exists and is a symbolic name, not a hash */
  int isBranchCI = 0;    /* ci= refers to a branch name */
  char *zHeader = 0;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  cgi_check_for_malice();
  style_set_current_feature("artifact");
  if( fossil_strcmp(g.zPath, "docfile")==0 ){
    isFile = 1;
    docOnly = 1;
  }

  /* Capture and normalize the name= and ci= query parameters */
  if( zName==0 ){
    zName = P("filename");
    if( zName==0 ){
      zName = P("fn");
    }
2554
2555
2556
2557
2558
2559
2560







2561
2562
2563


2564

2565
2566
2567
2568
2569
2570
2571
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852

2853
2854
2855
2856
2857
2858
2859
2860







+
+
+
+
+
+
+



+
+
-
+








  if( descOnly || P("verbose")!=0 ){
    url_add_parameter(&url, "verbose", "1");
    objdescFlags |= OBJDESC_DETAIL;
  }
  zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
  etag_check(ETAG_HASH, zUuid);

  if( descOnly && hashOnly ){
    blob_set(&uuid, zUuid);
    cgi_set_content_type("text/plain");
    cgi_set_content(&uuid);
    return;
  }

  asText = P("txt")!=0;
  if( isFile ){
    if( docOnly ){
      /* No header */
    if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){
    }else if( zCI==0 || fossil_strcmp(zCI,"tip")==0 ){
      zCI = "tip";
      @ <h2>File %z(href("%R/finfo?name=%T&m&ci=tip",zName))%h(zName)</a>
      @ from the %z(href("%R/info/tip"))latest check-in</a></h2>
    }else{
      const char *zPath;
      Blob path;
      blob_zero(&path);
2579
2580
2581
2582
2583
2584
2585
2586
2587


2588
2589
2590
2591
2592






2593
2594
2595
2596
2597
2598
2599
2868
2869
2870
2871
2872
2873
2874

2875
2876
2877





2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890







-

+
+
-
-
-
-
-
+
+
+
+
+
+







      }else if( isSymbolicCI ){
        @ part of check-in %z(href("%R/info/%!S",zCIUuid))%s(zCI)</a></h2>
      }else{
        @ part of check-in %z(href("%R/info/%!S",zCIUuid))%S(zCIUuid)</a></h2>
      }
      blob_reset(&path);
    }
    style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
    zMime = mimetype_from_name(zName);
    if( !docOnly ){
      style_submenu_element("Artifact", "%R/artifact/%S", zUuid);
    style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
                          zName, zCI);
    style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T",
                          zName, zCI);
    style_submenu_element("Doc", "%R/doc/%T/%T", zCI, zName);
      style_submenu_element("Annotate", "%R/annotate?filename=%T&checkin=%T",
                            zName, zCI);
      style_submenu_element("Blame", "%R/blame?filename=%T&checkin=%T",
                            zName, zCI);
      style_submenu_element("Doc", "%R/doc/%T/%T", zCI, zName);
    }
    blob_init(&downloadName, zName, -1);
    objType = OBJTYPE_CONTENT;
  }else{
    @ <h2>Artifact
    style_copy_button(1, "hash-ar", 0, 2, "%s", zUuid);
    if( g.perm.Setup ){
      @  (%d(rid)):</h2>
2608
2609
2610
2611
2612
2613
2614
2615

2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632

2633
2634
2635
2636
2637
2638
2639
2899
2900
2901
2902
2903
2904
2905

2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922

2923
2924
2925
2926
2927
2928
2929
2930







-
+
















-
+







  }
  if( !descOnly && P("download")!=0 ){
    cgi_redirectf("%R/raw/%s?at=%T",
          db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid),
          file_tail(blob_str(&downloadName)));
    /*NOTREACHED*/
  }
  if( g.perm.Admin ){
  if( g.perm.Admin && !docOnly ){
    const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
    if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
      style_submenu_element("Unshun", "%R/shun?accept=%s&sub=1#accshun", zUuid);
    }else{
      style_submenu_element("Shun", "%R/shun?shun=%s#addshun",zUuid);
    }
  }

  if( isFile ){
    if( isSymbolicCI ){
      zHeader = mprintf("%s at %s", file_tail(zName), zCI);
      style_set_current_page("doc/%t/%T", zCI, zName);
    }else if( zCIUuid && zCIUuid[0] ){
      zHeader = mprintf("%s at [%S]", file_tail(zName), zCIUuid);
      style_set_current_page("doc/%S/%T", zCIUuid, zName);
    }else{
      zHeader = mprintf("%s", file_tail(zName));
      zHeader = fossil_strdup(file_tail(zName));
      style_set_current_page("doc/tip/%T", zName);
    }
  }else if( descOnly ){
    zHeader = mprintf("Artifact Description [%S]", zUuid);
  }else{
    zHeader = mprintf("Artifact [%S]", zUuid);
  }
2653
2654
2655
2656
2657
2658
2659

2660
2661
2662





2663
2664
2665
2666
2667
2668
2669

2670


2671
2672
2673
2674
2675
2676
2677
2678
2679

2680


2681
2682
2683
2684
2685
2686

2687
2688
2689
2690





2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701

2702


2703
2704
2705
2706
2707
2708
2709
2944
2945
2946
2947
2948
2949
2950
2951



2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964

2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976

2977
2978
2979
2980
2981
2982
2983
2984
2985




2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002

3003
3004
3005
3006
3007
3008
3009
3010
3011







+
-
-
-
+
+
+
+
+







+
-
+
+









+
-
+
+






+
-
-
-
-
+
+
+
+
+











+
-
+
+







      const char *zUser = db_column_text(&q,0);
      const char *zDate = db_column_text(&q,1);
      const char *zIp = db_column_text(&q,2);
      @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p>
    }
    db_finalize(&q);
  }
  if( !docOnly ){
  style_submenu_element("Download", "%R/raw/%s?at=%T", zUuid, file_tail(zName));
  if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){
    style_submenu_element("Check-ins Using", "%R/timeline?uf=%s", zUuid);
    style_submenu_element("Download", "%R/raw/%s?at=%T",
                zUuid, file_tail(blob_str(&downloadName)));
    if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){
      style_submenu_element("Check-ins Using", "%R/timeline?uf=%s", zUuid);
    }
  }
  if( zMime ){
    if( fossil_strcmp(zMime, "text/html")==0 ){
      if( asText ){
        style_submenu_element("Html", "%s", url_render(&url, "txt", 0, 0, 0));
      }else{
        renderAsHtml = 1;
        if( !docOnly ){
        style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0));
          style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0));
        }
      }
    }else if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0
           || fossil_strcmp(zMime, "text/x-markdown")==0
           || fossil_strcmp(zMime, "text/x-pikchr")==0 ){
      if( asText ){
        style_submenu_element(zMime[7]=='p' ? "Pikchr" : "Wiki",
                              "%s", url_render(&url, "txt", 0, 0, 0));
      }else{
        renderAsWiki = 1;
        if( !docOnly ){
        style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0));
          style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0));
        }
      }
    }else if( fossil_strcmp(zMime, "image/svg+xml")==0 ){
      if( asText ){
        style_submenu_element("Svg", "%s", url_render(&url, "txt", 0, 0, 0));
      }else{
        renderAsSvg = 1;
        if( !docOnly ){
        style_submenu_element("Text", "%s", url_render(&url, "txt", "1", 0, 0));
      }
    }
    if( fileedit_is_editable(zName) ){
          style_submenu_element("Text", "%s", url_render(&url, "txt","1",0,0));
        }
      }
    }
    if( !docOnly && fileedit_is_editable(zName) ){
      style_submenu_element("Edit",
                            "%R/fileedit?filename=%T&checkin=%!S",
                            zName, zCI);
    }
  }
  if( (objType & (OBJTYPE_WIKI|OBJTYPE_TICKET))!=0 ){
    style_submenu_element("Parsed", "%R/info/%s", zUuid);
  }
  if( descOnly ){
    style_submenu_element("Content", "%R/artifact/%s", zUuid);
  }else{
    if( !docOnly || !isFile ){
    @ <hr>
      @ <hr>
    }
    content_get(rid, &content);
    if( renderAsWiki ){
      safe_html_context(DOCSRC_FILE);
      wiki_render_by_mimetype(&content, zMime);
      document_emit_js();
    }else if( renderAsHtml ){
      @ <iframe src="%R/raw/%s(zUuid)"
2873
2874
2875
2876
2877
2878
2879





































































































































2880
2881
2882
2883
2884
2885
2886
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  @ <div class="section">Changes</div>
  @ <p>
  ticket_output_change_artifact(pTktChng, 0, 1, 0);
  manifest_destroy(pTktChng);
  style_finish_page();
}

/*
** rid is a cluster.  Paint a page that contains detailed information
** about that cluster.
*/
static void cluster_info(int rid, const char *zName){
  Manifest *pCluster;
  int i;
  Blob where = BLOB_INITIALIZER;
  Blob unks = BLOB_INITIALIZER;
  Stmt q;
  char *zSha1Bg;
  char *zSha3Bg;
  int badRid = 0;
  int rcvid;
  int hashClr = PB("hclr");
  const char *zDate;

  pCluster = manifest_get(rid, CFTYPE_CLUSTER, 0);
  if( pCluster==0 ){
    artifact_page();
    return;
  }  
  style_header("Cluster %S", zName);
  rcvid = db_int(0, "SELECT rcvid FROM blob WHERE rid=%d", rid);
  if( rcvid==0 ){
    zDate = 0;
  }else{
    zDate = db_text(0, "SELECT datetime(mtime) FROM rcvfrom WHERE rcvid=%d",
                    rcvid);
  }
  @ <p>Artifact %z(href("%R/artifact/%h",zName))%S(zName)</a> is a cluster
  @ with %d(pCluster->nCChild) entries
  if( g.perm.Admin ){
    @ received <a href="%R/rcvfrom?rcvid=%d(rcvid)">%h(zDate)</a>:
  }else{
    @ received %h(zDate):
  }
  blob_appendf(&where,"IN(0");
  for(i=0; i<pCluster->nCChild; i++){
    int rid = fast_uuid_to_rid(pCluster->azCChild[i]);
    if( rid ){
      blob_appendf(&where,",%d", rid);
    }else{
      if( blob_size(&unks)>0 ) blob_append_char(&unks, ',');
      badRid++;
      blob_append_sql(&unks,"(%d,%Q)",-badRid,pCluster->azCChild[i]);
    }
  }
  blob_append_char(&where,')');
  describe_artifacts(blob_str(&where));
  blob_reset(&where);
  if( badRid>0 ){
    db_multi_exec(
      "WITH unks(rx,hx) AS (VALUES %s)\n"
      "INSERT INTO description(rid,uuid,type,summary) "
      "  SELECT rx, hx, 'phantom', '' FROM unks;",
      blob_sql_text(&unks)
    );
  }
  blob_reset(&unks);
  db_prepare(&q,
    "SELECT rid, uuid, summary, isPrivate, type='phantom', rcvid, ref"
    "  FROM description ORDER BY uuid"
  );
  if( skin_detail_boolean("white-foreground") ){
    zSha1Bg = "#714417";
    zSha3Bg = "#177117";
  }else{
    zSha1Bg = "#ebffb0";
    zSha3Bg = "#b0ffb0";
  }
  @ <table cellpadding="2" cellspacing="0" border="1">
  if( g.perm.Admin ){
    @ <tr><th>RID<th>Hash<th>Rcvid<th>Description<th>Ref<th>Remarks
  }else{
    @ <tr><th>RID<th>Hash<th>Description<th>Ref<th>Remarks
  }
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q,0);
    const char *zUuid = db_column_text(&q, 1);
    const char *zDesc = db_column_text(&q, 2);
    int isPriv = db_column_int(&q,3);
    int isPhantom = db_column_int(&q,4);
    const char *zRef = db_column_text(&q,6);
    if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){
      /* Don't show private artifacts to users without Private (x) permission */
      continue;
    }
    if( rid<=0 ){
      @ <tr><td>&nbsp;</td>
    }else if( hashClr ){
      const char *zClr = db_column_bytes(&q,1)>40 ? zSha3Bg : zSha1Bg;
      @ <tr style='background-color:%s(zClr);'><td align="right">%d(rid)</td>
    }else{
      @ <tr><td align="right">%d(rid)</td>
    }
    if( rid<=0 ){
      @ <td>&nbsp;%S(zUuid)&nbsp;</td>
    }else{
      @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
    }
    if( g.perm.Admin ){
      int rcvid = db_column_int(&q,5);
      if( rcvid<=0 ){
        @ <td>&nbsp;
      }else{
        @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%d(rcvid)</a>
      }
    }
    @ <td align="left">%h(zDesc)</td>
    if( zRef && zRef[0] ){
      @ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a>
    }else{
      @ <td>&nbsp;
    }
    if( isPriv || isPhantom ){
      if( isPriv==0 ){
        @ <td>phantom</td>
      }else if( isPhantom==0 ){
        @ <td>private</td>
      }else{
        @ <td>private,phantom</td>
      }
    }else{
      @ <td>&nbsp;
    }
    @ </tr>
  }
  @ </table>
  db_finalize(&q);
  style_finish_page();
}


/*
** WEBPAGE: info
** URL: info/NAME
**
** The NAME argument is any valid artifact name: an artifact hash,
** a timestamp, a tag name, etc.
2961
2962
2963
2964
2965
2966
2967




2968
2969
2970
2971
2972
2973
2974
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413







+
+
+
+







    ci_page();
  }else
  if( db_exists("SELECT 1 FROM plink WHERE pid=%d", rid) ){
    ci_page();
  }else
  if( db_exists("SELECT 1 FROM attachment WHERE attachid=%d", rid) ){
    ainfo_page();
  }else
  if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d AND tagid=%d",
                rid, TAG_CLUSTER) ){
    cluster_info(rid, zName);
  }else
  {
    artifact_page();
  }
}

/*
3228
3229
3230
3231
3232
3233
3234

3235
3236
3237
3238
3239
3240
3241
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681







+







                              rid, TAG_BGCOLOR)==2;
  fNewPropagateColor = P("clr")!=0 ? P("pclr")!=0 : fPropagateColor;
  zNewColorFlag = P("newclr") ? " checked" : "";
  zNewTagFlag = P("newtag") ? " checked" : "";
  zNewTag = PDT("tagname","");
  zNewBrFlag = P("newbr") ? " checked" : "";
  zNewBranch = PDT("brname","");
  zBranchName = branch_of_rid(rid);
  zCloseFlag = P("close") ? " checked" : "";
  zHideFlag = P("hide") ? " checked" : "";
  if( P("apply") && cgi_csrf_safe(2) ){
    Blob ctrl;
    char *zNow;

    blob_zero(&ctrl);
3275
3276
3277
3278
3279
3280
3281







3282
3283
3284
3285
3286

3287


3288

3289
3290
3291
3292
3293
3294
3295
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732

3733
3734
3735
3736

3737
3738
3739
3740
3741
3742
3743
3744







+
+
+
+
+
+
+




-
+

+
+
-
+







  blob_zero(&comment);
  blob_append(&comment, zNewComment, -1);
  zUuid[10] = 0;
  style_header("Edit Check-in [%s]", zUuid);
  if( P("preview") ){
    Blob suffix;
    int nTag = 0;
    const char *zDplyBr;   /* Branch name used to determine BG color */
    const char *zMainBranch = db_main_branch();
    if( zNewBrFlag[0] && zNewBranch[0] ){
      zDplyBr = zNewBranch;
    }else{
      zDplyBr = zBranchName;
    }
    @ <b>Preview:</b>
    @ <blockquote>
    @ <table border=0>
    if( zNewColorFlag[0] && zNewColor && zNewColor[0] ){
      @ <tr><td style="background-color: %h(zNewColor);">
      @ <tr><td style="background-color:%h(reasonable_bg_color(zNewColor,0));">
    }else if( zColor[0] ){
      @ <tr><td style="background-color:%h(reasonable_bg_color(zColor,0));">
    }else if( zDplyBr && fossil_strcmp(zDplyBr, zMainBranch)!=0 ){
      @ <tr><td style="background-color: %h(zColor);">
      @ <tr><td style="background-color:%h(hash_color(zDplyBr));">
    }else{
      @ <tr><td>
    }
    @ %!W(blob_str(&comment))
    blob_zero(&suffix);
    blob_appendf(&suffix, "(user: %h", zNewUser);
    db_prepare(&q, "SELECT substr(tagname,5) FROM tagxref, tag"
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3815
3816
3817
3818
3819
3820
3821



3822
3823
3824
3825
3826
3827
3828







-
-
-







  @ </td></tr>

  @ <tr><th align="right" valign="top">Tags:</th>
  @ <td valign="top">
  @ <label><input type="checkbox" id="newtag" name="newtag"%s(zNewTagFlag)>
  @ Add the following new tag name to this check-in:</label>
  @ <input size="15" name="tagname" id="tagname" value="%h(zNewTag)">
  zBranchName = db_text(0, "SELECT value FROM tagxref, tag"
     " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid"
     " AND tagxref.tagid=%d", rid, TAG_BRANCH);
  db_prepare(&q,
     "SELECT tag.tagid, tagname, tagxref.value FROM tagxref, tag"
     " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid"
     " ORDER BY CASE WHEN tagname GLOB 'sym-*' THEN substr(tagname,5)"
     "               ELSE tagname END /*sort*/",
     rid
  );
3409
3410
3411
3412
3413
3414
3415
3416

3417
3418
3419
3420
3421
3422
3423
3855
3856
3857
3858
3859
3860
3861

3862
3863
3864
3865
3866
3867
3868
3869







-
+







      @ Cancel tag <b>%h(&zTagName[4])</b></label>
    }
  }
  db_finalize(&q);
  @ </td></tr>

  if( !zBranchName ){
    zBranchName = db_get("main-branch", 0);
    zBranchName = fossil_strdup(db_main_branch());
  }
  if( !zNewBranch || !zNewBranch[0]){
    zNewBranch = zBranchName;
  }
  @ <tr><th align="right" valign="top">Branching:</th>
  @ <td valign="top">
  @ <label><input id="newbr" type="checkbox" name="newbr" \
3509
3510
3511
3512
3513
3514
3515
3516

3517
3518
3519
3520
3521
3522



3523
3524
3525
3526
3527
3528
3529
3530













3531
3532
3533
3534
3535
3536
3537
3955
3956
3957
3958
3959
3960
3961

3962






3963
3964
3965








3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985







-
+
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+







** COMMAND: amend
**
** Usage: %fossil amend HASH OPTION ?OPTION ...?
**
** Amend the tags on check-in HASH to change how it displays in the timeline.
**
** Options:
**    --author USER           Make USER the author for check-in
**    --author USER              Make USER the author for check-in
**    -m|--comment COMMENT    Make COMMENT the check-in comment
**    -M|--message-file FILE  Read the amended comment from FILE
**    -e|--edit-comment       Launch editor to revise comment
**    --date DATETIME         Make DATETIME the check-in time
**    --bgcolor COLOR         Apply COLOR to this check-in
**    --branchcolor COLOR     Apply and propagate COLOR to the branch
**    --bgcolor COLOR            Apply COLOR to this check-in
**    --branch NAME              Rename branch of check-in to NAME
**    --branchcolor COLOR        Apply and propagate COLOR to the branch
**    --tag TAG               Add new TAG to this check-in
**    --cancel TAG            Cancel TAG from this check-in
**    --branch NAME           Rename branch of check-in to NAME
**    --hide                  Hide branch starting from this check-in
**    --close                 Mark this "leaf" as closed
**    -n|--dry-run            Print control artifact, but make no changes
**    --date-override DATETIME  Set the change time on the control artifact
**    --user-override USER      Set the user name on the control artifact
**    --cancel TAG               Cancel TAG from this check-in
**    --close                    Mark this "leaf" as closed
**    --date DATETIME            Make DATETIME the check-in time
**    --date-override DATETIME   Set the change time on the control artifact
**    -e|--edit-comment          Launch editor to revise comment
**    --editor NAME              Text editor to use for check-in comment
**    --hide                     Hide branch starting from this check-in
**    -m|--comment COMMENT       Make COMMENT the check-in comment
**    -M|--message-file FILE     Read the amended comment from FILE
**    -n|--dry-run               Print control artifact, but make no changes
**    --no-verify-comment        Do not validate the check-in comment
**    --tag TAG                  Add new TAG to this check-in
**    --user-override USER       Set the user name on the control artifact
**
** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in
** year-month-day form, it may be truncated, the "T" may be replaced by
** a space, and it may also name a timezone offset from UTC as "-HH:MM"
** (westward) or "+HH:MM" (eastward). Either no timezone suffix or "Z"
** means UTC.
*/
3554
3555
3556
3557
3558
3559
3560

3561
3562
3563
3564
3565
3566
3567
3568
3569


3570
3571
3572
3573
3574
3575
3576
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027







+









+
+







  int fHide;                    /* True if branch should be hidden */
  int fPropagateColor;          /* True if color propagates before amend */
  int fNewPropagateColor = 0;   /* True if color propagates after amend */
  int fHasHidden = 0;           /* True if hidden tag already set */
  int fHasClosed = 0;           /* True if closed tag already set */
  int fEditComment;             /* True if editor to be used for comment */
  int fDryRun;                  /* Print control artifact, make no changes */
  int noVerifyCom = 0;          /* Allow suspicious check-in comments */
  const char *zChngTime;        /* The change time on the control artifact */
  const char *zUserOvrd;        /* The user name on the control artifact */
  const char *zUuid;
  Blob ctrl;
  Blob comment;
  char *zNow;
  int nTags, nCancels;
  int i;
  Stmt q;
  int ckComFlgs;                /* Flags passed to verify_comment() */


  fEditComment = find_option("edit-comment","e",0)!=0;
  zNewComment = find_option("comment","m",1);
  zComFile = find_option("message-file","M",1);
  zNewBranch = find_option("branch",0,1);
  zNewColor = find_option("bgcolor",0,1);
  zNewBrColor = find_option("branchcolor",0,1);
3584
3585
3586
3587
3588
3589
3590

3591
3592

3593
3594
3595
3596
3597
3598
3599
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052







+


+







  pzCancelTags = find_repeatable_option("cancel",0,&nCancels);
  fClose = find_option("close",0,0)!=0;
  fHide = find_option("hide",0,0)!=0;
  fDryRun = find_option("dry-run","n",0)!=0;
  zChngTime = find_option("date-override",0,1);
  if( zChngTime==0 ) zChngTime = find_option("chngtime",0,1);
  zUserOvrd = find_option("user-override",0,1);
  noVerifyCom = find_option("no-verify-comment",0,0)!=0;
  db_find_and_open_repository(0,0);
  user_select();
  (void)fossil_text_editor();
  verify_all_options();
  if( g.argc<3 || g.argc>=4 ) usage(AMEND_USAGE_STMT);
  rid = name_to_typed_rid(g.argv[2], "ci");
  if( rid==0 && !is_a_version(rid) ) fossil_fatal("no such check-in");
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  if( zUuid==0 ) fossil_fatal("Unable to find artifact hash");
  zComment = db_text(0, "SELECT coalesce(ecomment,comment)"
3642
3643
3644
3645
3646
3647
3648
















3649
3650


3651
3652

3653
3654
3655
3656
3657
3658
3659








































3660
3661
3662
3663
3664
3665
3666
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117


4118
4119


4120







4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







      ),
      fNewPropagateColor
    );
  }
  if( (zNewColor!=0 && zNewColor[0]==0) && (zColor && zColor[0] ) ){
    cancel_color();
  }
  if( fEditComment || zNewComment || zComFile ){
    blob_init(&comment, 0, 0);

    /* Figure out how much comment verification is requested */
    if( noVerifyCom ){
      ckComFlgs = 0;
    }else{
      const char *zVerComs = db_get("verify-comments","on");
      if( is_false(zVerComs) ){
        ckComFlgs = 0;
      }else if( strcmp(zVerComs,"preview")==0 ){
        ckComFlgs = COMCK_PREVIEW | COMCK_MARKUP;
      }else{
        ckComFlgs = COMCK_MARKUP;
      }
    }
  if( fEditComment ){
    prepare_amend_comment(&comment, zComment, zUuid);
    if( fEditComment ){
      prepare_amend_comment(&comment, zComment, zUuid);
    zNewComment = blob_str(&comment);
  }else if( zComFile ){
    }else if( zComFile ){
    blob_zero(&comment);
    blob_read_from_file(&comment, zComFile, ExtFILE);
    blob_to_utf8_no_bom(&comment, 1);
    zNewComment = blob_str(&comment);
  }
  if( zNewComment && zNewComment[0]
      && comment_compare(zComment,zNewComment)==0 ) add_comment(zNewComment);
      blob_read_from_file(&comment, zComFile, ExtFILE);
      blob_to_utf8_no_bom(&comment, 1);
    }else if( zNewComment ){
      blob_init(&comment, zNewComment, -1);
    }
    if( blob_size(&comment)>0
     && comment_compare(zComment, blob_str(&comment))==0
    ){
      int rc;
      while( (rc = verify_comment(&comment, ckComFlgs))!=0 ){
        char cReply;
        Blob ans;
        if( !fEditComment ){
          fossil_fatal("Amend aborted; "
                       "use --no-verify-comment to override");
        }
        if( rc==COMCK_PREVIEW ){
          prompt_user("Continue, abort, or edit (C/a/e)? ", &ans);
        }else{
          prompt_user("Edit, abort, or continue (E/a/c)? ", &ans);
        }
        cReply = blob_str(&ans)[0];
        cReply = fossil_tolower(cReply);
        blob_reset(&ans);
        if( cReply=='a' ){
          fossil_fatal("Amend aborted.");
        }
        if( cReply=='e' || (cReply!='c' && rc!=COMCK_PREVIEW) ){
          char *zPrior = blob_materialize(&comment);
          blob_init(&comment, 0, 0);
          prepare_amend_comment(&comment, zPrior, zUuid);
          fossil_free(zPrior);
          continue;
        }else{
          break;
        }
      }
    }
    add_comment(blob_str(&comment));
  }
  if( zNewDate && zNewDate[0] && fossil_strcmp(zDate,zNewDate)!=0 ){
    if( is_datetime(zNewDate) ){
      add_date(zNewDate);
    }else{
      fossil_fatal("Unsupported date format, use YYYY-MM-DD HH:MM:SS");
    }
  }
3771
3772
3773
3774
3775
3776
3777
3778

3779
3780
3781
3782
3783
3784
3785
4272
4273
4274
4275
4276
4277
4278

4279
4280
4281
4282
4283
4284
4285
4286







-
+







    descr->nCommitsSince = -1;
    descr->zCommitHash = mprintf("");
    descr->isDirty = -1;
    return (rid-1);
  }

  zUuid = rid_to_uuid(rid);
  descr->zCommitHash = mprintf("%s", zUuid);
  descr->zCommitHash = fossil_strdup(zUuid);
  descr->isDirty = unsaved_changes(0);

  db_multi_exec(
    "DROP TABLE IF EXISTS temp.singletonTag;"
    "CREATE TEMP TABLE singletonTag("
    "  rid INT,"
    "  tagname TEXT,"
3820
3821
3822
3823
3824
3825
3826
3827

3828
3829
3830
3831
3832
3833
3834
4321
4322
4323
4324
4325
4326
4327

4328
4329
4330
4331
4332
4333
4334
4335







-
+







    " WHERE tagname IS NOT NULL"
    " ORDER BY n LIMIT 1;",
    rid, rid
  );

  if( db_step(&q)==SQLITE_ROW ){
    const char *lastTag = db_column_text(&q, 0);
    descr->zRelTagname = mprintf("%s", lastTag);
    descr->zRelTagname = fossil_strdup(lastTag);
    descr->nCommitsSince = db_column_int(&q, 1);
    nRet = 0;
  }else{
    /* no ancestor commit with a fitting singleton tag found */
    descr->zRelTagname = mprintf("");
    descr->nCommitsSince = -1;
    nRet = -3;
Changes to src/interwiki.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
185
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







-
+



-
+







-
+







** COMMAND: interwiki*
**
** Usage: %fossil interwiki COMMAND ...
**
** Manage the "intermap" that defines the mapping from interwiki tags
** to complete URLs for interwiki links.
**
** >  fossil interwiki delete TAG ...
** > fossil interwiki delete TAG ...
**
**        Delete one or more interwiki maps.
**
** >  fossil interwiki edit TAG --base URL --hash PATH --wiki PATH
** > fossil interwiki edit TAG --base URL --hash PATH --wiki PATH
**
**        Create an interwiki referenced call TAG.  The base URL is
**        the --base option, which is required.  The --hash and --wiki
**        paths are optional.  The TAG must be lower-case alphanumeric
**        and must be unique.  A new entry is created if it does not
**        already exit.
**
** >  fossil interwiki list
** > fossil interwiki list
**
**        Show all interwiki mappings.
*/
void interwiki_cmd(void){
  const char *zCmd;
  int nCmd;
  db_find_and_open_repository(0, 0);
273
274
275
276
277
278
279

280
281
282

283
284
285
286
287
288
289
273
274
275
276
277
278
279
280
281
282

283
284
285
286
287
288
289
290







+


-
+







  int n = 0;
  Stmt q;
  db_prepare(&q,
    "SELECT substr(name,11), value->>'base'"
    "  FROM config WHERE name glob 'interwiki:*' AND json_valid(value)"
    " ORDER BY name;"
  );
  blob_append(out, "<blockquote>", -1);
  while( db_step(&q)==SQLITE_ROW ){
    if( n==0 ){
      blob_appendf(out, "<blockquote><table>\n");
      blob_appendf(out, "<table>\n");
    }
    blob_appendf(out,"<tr><td>%h</td><td>&nbsp;&rarr;&nbsp;</td>",
       db_column_text(&q,0));
    blob_appendf(out,"<td>%h</td></tr>\n",
       db_column_text(&q,1));
    n++;
  }
Changes to src/json.c.
76
77
78
79
80
81
82
83

84
85
86
87
88
89
90
76
77
78
79
80
81
82

83
84
85
86
87
88
89
90







-
+







    /* When running in server/cgi "directory" mode, zPathInfo is
    ** prefixed with the repository's name, so in order to determine
    ** whether or not we're really running in json mode we have to try
    ** a bit harder. Problem reported here:
    ** https://fossil-scm.org/forum/forumpost/e4953666d6
    */
    ReCompiled * pReg = 0;
    const char * zErr = re_compile(&pReg, "^/[^/]+/json(/.*)?", 0);
    const char * zErr = fossil_re_compile(&pReg, "^/[^/]+/json(/.*)?", 0);
    assert(zErr==0 && "Regex compilation failed?");
    if(zErr==0 &&
         re_match(pReg, (const unsigned char *)zPathInfo, -1)){
      rc = 2;
    }
    re_free(pReg);
  }
238
239
240
241
242
243
244
245

246
247
248
249
250
251
252
238
239
240
241
242
243
244

245
246
247
248
249
250
251
252







-
+







**
** - Allocation error.
** - g.json.gc.a is NULL
** - key is NULL or empty.
**
** Returns 0 on success.
**
** Ownership of v is transfered to (or shared with) g.json.gc, and v
** Ownership of v is transferred to (or shared with) g.json.gc, and v
** will be valid until that object is cleaned up or some internal code
** incorrectly removes it from the gc (which we never do). If this
** function fails, it is fatal to the app (as it indicates an
** allocation error (more likely than not) or a serious internal error
** such as numeric overflow).
*/
void json_gc_add( char const * key, cson_value * v ){
269
270
271
272
273
274
275
276

277
278
279
280
281
282
283
269
270
271
272
273
274
275

276
277
278
279
280
281
282
283







-
+







  }
}


/*
** Returns the value of json_rc_cstr(code) as a new JSON
** string, which is owned by the caller and must eventually
** be cson_value_free()d or transfered to a JSON container.
** be cson_value_free()d or transferred to a JSON container.
*/
cson_value * json_rc_string( int code ){
  return cson_value_new_string( json_rc_cstr(code), 11 );
}

cson_value * json_new_string( char const * str ){
  return str
862
863
864
865
866
867
868
869

870
871
872
873
874
875
876
862
863
864
865
866
867
868

869
870
871
872
873
874
875
876







-
+







}

/*
** Splits zStr (which must not be NULL) into tokens separated by the
** given separator character. If doDeHttp is true then each element
** will be passed through dehttpize(), otherwise they are used
** as-is. Note that tokenization happens before dehttpize(),
** which is significant if the ENcoded tokens might contain the
** which is significant if the encoded tokens might contain the
** separator character.
**
** Each new element is appended to the given target array object,
** which must not be NULL and ownership of it is not changed by this
** call.
**
** On success, returns the number of tokens _encountered_. On error a
1435
1436
1437
1438
1439
1440
1441
1442

1443
1444
1445
1446
1447
1448
1449
1435
1436
1437
1438
1439
1440
1441

1442
1443
1444
1445
1446
1447
1448
1449







-
+







** on error.
**
** If payload is not NULL and resultCode is 0 then it is set as the
** "payload" property of the returned object.  If resultCode is 0 then
** it defaults to g.json.resultCode. If resultCode is (or defaults to)
** non-zero and payload is not NULL then this function calls
** cson_value_free(payload) and does not insert the payload into the
** response. In either case, ownership of payload is transfered to (or
** response. In either case, ownership of payload is transferred to (or
** shared with, if the caller holds a reference) this function.
**
** pMsg is an optional message string property (resultText) of the
** response. If resultCode is non-0 and pMsg is NULL then
** json_err_cstr() is used to get the error string. The caller may
** provide his own or may use an empty string to suppress the
** resultText property.
1641
1642
1643
1644
1645
1646
1647
1648

1649
1650
1651
1652
1653
1654
1655
1641
1642
1643
1644
1645
1646
1647

1648
1649
1650
1651
1652
1653
1654
1655







-
+







** code must be in the inclusive range 1000..9999.
*/
int json_set_err( int code, char const * fmt, ... ){
  assert( (code>=1000) && (code<=9999) );
  fossil_free(g.zErrMsg);
  g.json.resultCode = code;
  if(!fmt || !*fmt){
    g.zErrMsg = mprintf("%s", json_err_cstr(code));
    g.zErrMsg = fossil_strdup(json_err_cstr(code));
  }else{
    va_list vargs;
    char * msg;
    va_start(vargs,fmt);
    msg = vmprintf(fmt, vargs);
    va_end(vargs);
    g.zErrMsg = msg;
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1959
1960
1961
1962
1963
1964
1965

1966
1967
1968
1969
1970
1971
1972







-







  cson_object_set( obj, "permissionFlags", sub );
  obj = cson_value_get_object(sub);

#define ADD(X,K) cson_object_set(obj, K, cson_value_new_bool(g.perm.X))
  ADD(Setup,"setup");
  ADD(Admin,"admin");
  ADD(Password,"password");
  ADD(Query,"query"); /* don't think this one is actually used */
  ADD(Write,"checkin");
  ADD(Read,"checkout");
  ADD(Hyperlink,"history");
  ADD(Clone,"clone");
  ADD(RdWiki,"readWiki");
  ADD(NewWiki,"createWiki");
  ADD(ApndWiki,"appendWiki");
Changes to src/json_branch.c.
311
312
313
314
315
316
317

318
319
320
321
322
323
324
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325







+







*/
static cson_value * json_branch_create(void){
  cson_value * payV = NULL;
  cson_object * pay = NULL;
  int rc = 0;
  BranchCreateOptions opt;
  char * zUuid = NULL;
  const char *zMainBranch = db_main_branch();
  int rid = 0;
  if( !g.perm.Write ){
    json_set_err(FSL_JSON_E_DENIED,
                 "Requires 'i' permissions.");
    return NULL;
  }
  memset(&opt,0,sizeof(BranchCreateOptions));
338
339
340
341
342
343
344
345

346
347
348
349
350
351
352
339
340
341
342
343
344
345

346
347
348
349
350
351
352
353







-
+








  opt.zColor = json_find_option_cstr("bgColor","bgcolor",NULL);
  opt.zBasis = json_find_option_cstr("basis",NULL,NULL);
  if(!opt.zBasis && !g.isHTTP){
    opt.zBasis = json_command_arg(g.json.dispatchDepth+2);
  }
  if(!opt.zBasis){
    opt.zBasis = "trunk";
    opt.zBasis = fossil_strdup(zMainBranch);
  }
  opt.isPrivate = json_find_option_bool("private",NULL,NULL,-1);
  if(-1==opt.isPrivate){
    if(!g.isHTTP){
      opt.isPrivate = (NULL != find_option("private","",0));
    }else{
      opt.isPrivate = 0;
Changes to src/json_config.c.
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
84
85
86
87
88
89
90

91
92
93
94
95
96
97







-







{ "default-skin",           CONFIGSET_SKIN },
{ "logo-mimetype",          CONFIGSET_SKIN },
{ "logo-image",             CONFIGSET_SKIN },
{ "background-mimetype",    CONFIGSET_SKIN },
{ "background-image",       CONFIGSET_SKIN },
{ "icon-mimetype",          CONFIGSET_SKIN },
{ "icon-image",             CONFIGSET_SKIN },
{ "timeline-block-markup",  CONFIGSET_SKIN },
{ "timeline-date-format",   CONFIGSET_SKIN },
{ "timeline-default-style", CONFIGSET_SKIN },
{ "timeline-dwelltime",     CONFIGSET_SKIN },
{ "timeline-closetime",     CONFIGSET_SKIN },
{ "timeline-hard-newlines", CONFIGSET_SKIN },
{ "timeline-max-comment",   CONFIGSET_SKIN },
{ "timeline-plaintext",     CONFIGSET_SKIN },
Changes to src/json_login.c.
104
105
106
107
108
109
110
111

112
113
114
115
116
117
118
104
105
106
107
108
109
110

111
112
113
114
115
116
117
118







-
+







      if( !jseed ){
        jseed = json_getenv("cs") /* name used by HTML interface */;
      }
    }
    if(jseed){
      if( cson_value_is_number(jseed) ){
        sqlite3_snprintf((int)SeedBufLen, seedBuffer, "%"CSON_INT_T_PFMT,
			 cson_value_get_integer(jseed));
                         cson_value_get_integer(jseed));
        anonSeed = seedBuffer;
      }else if( cson_value_is_string(jseed) ){
        anonSeed = cson_string_cstr(cson_value_get_string(jseed));
      }
    }
    if(!anonSeed){
      g.json.resultCode = preciseErrors
Changes to src/json_report.c.
153
154
155
156
157
158
159
160

161
162
163
164
165
166
167
153
154
155
156
157
158
159

160
161
162
163
164
165
166
167







-
+







**
** Options/arguments:
**
** report=int (CLI: -report # or -r #) is the report number to run.
**
** limit=int (CLI: -limit # or -n #) -n is for compat. with other commands.
**
** format=a|o Specifies result format: a=each row is an arry, o=each
** format=a|o Specifies result format: a=each row is an array, o=each
** row is an object.  Default=o.
*/
static cson_value * json_report_run(void){
  int nReport;
  Stmt q = empty_Stmt;
  cson_object * pay = NULL;
  cson_array * tktList = NULL;
200
201
202
203
204
205
206
207

208
209
210
211
212
213
214
200
201
202
203
204
205
206

207
208
209
210
211
212
213
214







-
+







  }

  limit = json_find_option_int("limit",NULL,"n",-1);


  /* Copy over report's SQL...*/
  blob_append(&sql, db_column_text(&q,0), -1);
  zTitle = mprintf("%s", db_column_text(&q,1));
  zTitle = fossil_strdup(db_column_text(&q,1));
  db_finalize(&q);
  db_prepare(&q, "%s", blob_sql_text(&sql));

  /** Build the response... */
  pay = cson_new_object();

  cson_object_set(pay, "report", json_new_int(nReport));
Changes to src/json_status.c.
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
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







-
+
+












-
+














-
+
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+








  /* Now get the list of non-pristine files... */
  aFiles = cson_new_array();
  cson_object_set( oPay, "files", cson_array_value( aFiles ) );

  db_prepare(&q,
    "SELECT pathname, deleted, chnged, rid, "
    "     coalesce(origname!=pathname,0), origname"
    " coalesce(origname!=pathname,0) AS renamed,"
    " origname"
    "  FROM vfile "
    " WHERE is_selected(id)"
    "   AND (chnged OR deleted OR rid=0 OR pathname!=origname) ORDER BY 1"
  );
  while( db_step(&q)==SQLITE_ROW ){
    cson_array *aStatuses = NULL;
    const char *zPathname = db_column_text(&q,0);
    int isDeleted = db_column_int(&q, 1);
    int isChnged = db_column_int(&q,2);
    int isNew = db_column_int(&q,3)==0;
    int isRenamed = db_column_int(&q,4);
    cson_object * oFile;
    char const * zStatus = "???";
    char const * zStatus = "unmodified";
    char * zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
    if( isDeleted ){
      zStatus = "deleted";
    }else if( isNew ){
      zStatus = "new" /* maintenance reminder: MUST come
                         BEFORE the isChnged checks. */;
    }else if( !file_isfile_or_link(zFullName) ){
      if( file_access(zFullName, F_OK)==0 ){
        zStatus = "notAFile";
        ++nErr;
      }else{
        zStatus = "missing";
        ++nErr;
      }
    }else if( 2==isChnged ){
    }else if( isChnged ){
      zStatus = "updatedByMerge";
    }else if( 3==isChnged ){
      zStatus = "addedByMerge";
    }else if( 4==isChnged ){
      switch( isChnged ){
        /* These numbers from checkin.c: status_report() */
        case 1:
      zStatus = "updatedByIntegrate";
    }else if( 5==isChnged ){
      zStatus = "addedByIntegrate";
    }else if( 1==isChnged ){
      if( file_contains_merge_marker(zFullName) ){
        zStatus = "conflict";
      }else{
        zStatus = "edited";
          if( file_contains_merge_marker(zFullName) ){
            zStatus = "conflict";
          }else{
            zStatus = "edited";
          }
          break;
        case 2: zStatus = "updatedByMerge"; break;
        case 3: zStatus = "addedByMerge"; break;
        case 4: zStatus = "updatedByIntegrate"; break;
        case 5: zStatus = "addedByIntegrate"; break;
        case 6: zStatus = "+exec"; break;
        case 7: zStatus = "+symlink"; break;
        case 8: zStatus = "-exec"; break;
        case 9: zStatus = "unlink"; break;
      }
    }
    oFile = cson_new_object();
    cson_array_append( aFiles, cson_object_value(oFile) );
    if( isRenamed ){
      if( *zStatus!='?' ){
        aStatuses = cson_new_array();
Changes to src/leaf.c.
224
225
226
227
228
229
230

231

232
233
234

235
236
237
238
239
240
241
224
225
226
227
228
229
230
231
232
233
234
235

236
237
238
239
240
241
242
243







+

+


-
+







** leaves on that branch.
*/
int leaf_ambiguity_warning(int rid, int currentCkout){
  char *zBr;
  Stmt q;
  int n = 0;
  Blob msg;
  const char *zMainBranch;
  if( leaf_ambiguity(rid)==0 ) return 0;
  zMainBranch = db_main_branch();
  zBr = db_text(0, "SELECT value FROM tagxref WHERE tagid=%d AND rid=%d",
                TAG_BRANCH, rid);
  if( zBr==0 ) zBr = fossil_strdup("trunk");
  if( zBr==0 ) zBr = fossil_strdup(zMainBranch);
  blob_init(&msg, 0, 0);
  blob_appendf(&msg, "WARNING: multiple open leaf check-ins on %s:", zBr);
  db_prepare(&q,
    "SELECT"
    "  (SELECT uuid FROM blob WHERE rid=leaf.rid),"
    "  (SELECT datetime(mtime,toLocal()) FROM event WHERE objid=leaf.rid),"
    "  leaf.rid"
Changes to src/loadctrl.c.
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

















-
-
-
-
-
-
-
-
+




** %fossil test-loadavg
**
** Print the load average on the host machine.
*/
void loadavg_test_cmd(void){
  fossil_print("load-average: %f\n", load_average());
}

/*
** WEBPAGE:  test-overload
**
** Generate the response that would normally be shown only when
** service is denied due to an overload condition.  This is for
** testing of the overload warning page.
*/
void overload_page(void){
  double mxLoad = atof(db_get("max-loadavg", "0.0"));
  style_set_current_feature("test");
  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)<br>
  @ URL: %h(g.zBaseURL)%h(P("PATH_INFO"))<br>
  @ Timestamp: %h(db_text("","SELECT datetime()"))Z</p>
  style_finish_page();
}

/*
** Abort the current page request if the load average of the host
** computer is too high. Admin and Setup users are exempt from this
** restriction.
*/
void load_control(void){
  double mxLoad = atof(db_get("max-loadavg", "0.0"));
#if 1
  /* Disable this block only to test load restrictions */
  if( mxLoad<=0.0 || mxLoad>=load_average() ) return;

  login_check_credentials();
  if(g.perm.Admin || g.perm.Setup){
    return;
  }
#endif

  style_set_current_feature("test");
  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_finish_page();
  overload_page();
  cgi_set_status(503,"Server Overload");
  cgi_reply();
  exit(0);
}
Changes to src/login.c.
158
159
160
161
162
163
164

165
166
167
168
169
170
171
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172







+







  int uid;                /* The user ID of anonymous */
  int n = 0;              /* Counter of captcha-secrets */

  if( zUsername==0 ) return 0;
  else if( zPassword==0 ) return 0;
  else if( zCS==0 ) return 0;
  else if( fossil_strcmp(zUsername,"anonymous")!=0 ) return 0;
  else if( anon_cookie_lifespan()==0 ) return 0;
  while( 1/*exit-by-break*/ ){
    zPw = captcha_decode((unsigned int)atoi(zCS), n);
    if( zPw==0 ) return 0;
    if( fossil_stricmp(zPw, zPassword)==0 ) break;
    n++;
  }
  uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'"
196
197
198
199
200
201
202
203

204
205
206
207
208
209
210
197
198
199
200
201
202
203

204
205
206
207
208
209
210
211







-
+







*/
static void record_login_attempt(
  const char *zUsername,     /* Name of user logging in */
  const char *zIpAddr,       /* IP address from which they logged in */
  int bSuccess               /* True if the attempt was a success */
){
  db_unprotect(PROTECT_READONLY);
  if( db_get_boolean("access-log", 0) ){
  if( db_get_boolean("access-log", 1) ){
    create_accesslog_table();
    db_multi_exec(
      "INSERT INTO accesslog(uname,ipaddr,success,mtime)"
      "VALUES(%Q,%Q,%d,julianday('now'));",
      zUsername, zIpAddr, bSuccess
    );
  }
290
291
292
293
294
295
296
297

298
299
300
301
302
303
304
291
292
293
294
295
296
297

298
299
300
301
302
303
304
305







-
+







** function "could" figure out the uid by itself but it currently
** doesn't because the code which calls this already has the uid.
**
** This function also updates the user.cookie, user.ipaddr,
** and user.cexpire fields for the given user.
**
** If zDest is not NULL then the generated cookie is copied to
** *zDdest and ownership is transfered to the caller (who should
** *zDdest and ownership is transferred to the caller (who should
** eventually pass it to free()).
**
** If bSessionCookie is true, the cookie will be a session cookie,
** else a persistent cookie. If it's a session cookie, the
** [user].[cexpire] and [user].[cookie] entries will be modified as if
** it were a persistent cookie because doing so is necessary for
** fossil's own "is this cookie still valid?" checks to work.
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





-
+
+





+
+


-
+

+


-
+




+
-
+







  fossil_free(zHash);
  if( zDest ){
    *zDest = zCookie;
  }else{
    free(zCookie);
  }
}

/*
** SETTING: anon-cookie-lifespan      width=10 default=480
** The number of minutes for which an anonymous login cookie is
** valid.  Anonymous logins are prohibited if this value is zero.
*/


/*
** The default lifetime of an anoymous cookie, in minutes.
*/
#define ANONYMOUS_COOKIE_LIFESPAN (8*60)

/*
** Return the lifetime of an anonymous cookie, in minutes.
*/
int anon_cookie_lifespan(void){
  static int lifespan = -1;
  if( lifespan<0 ){
    lifespan = db_get_int("anon-cookie-lifespan", ANONYMOUS_COOKIE_LIFESPAN);
    if( lifespan<0 ) lifespan = 0;
  }
  return lifespan;
}

/* Sets a cookie for an anonymous user login, which looks like this:
**
**    HASH/TIME/anonymous
**
** Where HASH is the sha1sum of TIME/SECRET, in which SECRET is captcha-secret.
** Where HASH is the sha1sum of TIME/USERAGENT/SECRET, in which SECRET
** is captcha-secret and USERAGENT is the HTTP_USER_AGENT value.
**
** If zCookieDest is not NULL then the generated cookie is assigned to
** *zCookieDest and the caller must eventually free() it.
**
** If bSessionCookie is true, the cookie will be a session cookie.
**
** Search for tag-20250817a to find the code that recognizes this cookie.
*/
void login_set_anon_cookie(char **zCookieDest, int bSessionCookie){
  char *zNow;                  /* Current time (julian day number) */
  char *zNow;                  /* Current time (Julian day number) */
  char *zCookie;               /* The login cookie */
  const char *zUserAgent;      /* The user agent */
  const char *zCookieName;     /* Name of the login cookie */
  Blob b;                      /* Blob used during cookie construction */
  int expires = bSessionCookie ? 0 : 6*3600;
  int expires = bSessionCookie ? 0 : anon_cookie_lifespan();
  zCookieName = login_cookie_name();
  zNow = db_text("0", "SELECT julianday('now')");
  assert( zCookieName && zNow );
  blob_init(&b, zNow, -1);
  zUserAgent = PD("HTTP_USER_AGENT","nil");
  blob_appendf(&b, "/%z", captcha_secret(0));
  blob_appendf(&b, "/%s/%z", zUserAgent, captcha_secret(0));
  sha1sum_blob(&b, &b);
  zCookie = mprintf("%s/%s/anonymous", blob_buffer(&b), zNow);
  blob_reset(&b);
  cgi_set_cookie(zCookieName, zCookie, login_cookie_path(), expires);
  if( zCookieDest ){
    *zCookieDest = zCookie;
  }else{
579
580
581
582
583
584
585















586




587
588
589
590
591
592

593
594
595
596
597
598
599
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+





-
+








  if( P("pwreset")!=0 && login_self_password_reset_available() ){
    /* If the "Reset Password" button in the form was pressed, render
    ** the Request Password Reset page in place of this one. */
    login_reqpwreset_page();
    return;
  }

  /* If the "anon" query parameter is 1 or 2, that means rework the web-page
  ** to make it a more user-friendly captcha.  Extraneous text and boxes
  ** are omitted.  The user has just the captcha image and an entry box
  ** and a "Verify" button.  Underneath is the same login page for user
  ** "anonymous", just displayed in an easier to digest format for one-time
  ** visitors.
  **
  ** anon=1 is advisory and only has effect if there is not some other login
  ** cookie.  anon=2 means always show the captcha. 
  */
  anonFlag = anon_cookie_lifespan()>0 ? atoi(PD("anon","0")) : 0;
  if( anonFlag==2 ){
    g.zLogin = 0;
  }else{
  login_check_credentials();
    login_check_credentials();
    if( g.zLogin!=0 ) anonFlag = 0;
  }

  fossil_redirect_to_https_if_needed(1);
  sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
                  constant_time_cmp_function, 0, 0);
  zUsername = P("u");
  zPasswd = P("p");
  anonFlag = g.zLogin==0 && PB("anon");

  /* Handle log-out requests */
  if( P("out") && cgi_csrf_safe(2) ){
    login_clear_login_data();
    login_redirect_to_g();
    return;
  }

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
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
843
844
845
846
847
848
849

850
851
852
853
854







855
856
857
858
859
860
861
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
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
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
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
920
921
922
923
924
925
926
927
928
929
930







+











-
-
+
+


















+







-
+







-
+










+
-
-
-
-
-
+
+
+
+
+
+



-
-
+
+
+
+
+
+
+




+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





-
+












+
-
-
+
+
+




-
+









-
+











+
-
-
-
-
-
+
+
+
+
+
+
+







      */
      login_set_user_cookie(zUsername, uid, NULL, rememberMe?0:1);
      login_redirect_to_g();
    }
  }
  style_set_current_feature("login");
  style_header("Login/Logout");
  if( anonFlag==2 ) g.zLogin = 0;
  style_adunit_config(ADUNIT_OFF);
  @ %s(zErrMsg)
  if( zGoto && !noAnon ){
    char *zAbbrev = fossil_strdup(zGoto);
    int i;
    for(i=0; zAbbrev[i] && zAbbrev[i]!='?'; i++){}
    zAbbrev[i] = 0;
    if( g.zLogin ){
      @ <p>Use a different login with greater privilege than <b>%h(g.zLogin)</b>
      @ to access <b>%h(zAbbrev)</b>.
    }else if( anonFlag ){
      @ <p>Login as <b>anonymous</b> or any named user
      @ to access page <b>%h(zAbbrev)</b>.
      @ <p><b>Verify that you are human by typing in the 8-character text
      @ password shown below.</b></p>
    }else{
      @ <p>Login as a named user to access page <b>%h(zAbbrev)</b>.
    }
    fossil_free(zAbbrev);
  }
  if( g.sslNotAvailable==0
   && strncmp(g.zBaseURL,"https:",6)!=0
   && db_get_boolean("https-login",0)
  ){
    form_begin(0, "https:%s/login", g.zBaseURL+5);
  }else{
    form_begin(0, "%R/login");
  }
  if( zGoto ){
    @ <input type="hidden" name="g" value="%h(zGoto)">
  }
  if( anonFlag ){
    @ <input type="hidden" name="anon" value="1">
    @ <input type="hidden" name="u" value="anonymous">
  }
  if( g.zLogin ){
    @ <p>Currently logged in as <b>%h(g.zLogin)</b>.
    @ <input type="submit" name="out" value="Logout" autofocus></p>
    @ </form>
  }else{
    unsigned int uSeed = captcha_seed();
    if( g.zLogin==0 && (anonFlag || zGoto==0) ){
    if( g.zLogin==0 && (anonFlag || zGoto==0) && anon_cookie_lifespan()>0 ){
      zAnonPw = db_text(0, "SELECT pw FROM user"
                           " WHERE login='anonymous'"
                           "   AND cap!=''");
    }else{
      zAnonPw = 0;
    }
    @ <table class="login_out">
    if( P("HTTPS")==0 ){
    if( P("HTTPS")==0 && !anonFlag ){
      @ <tr><td class="form_label">Warning:</td>
      @ <td><span class='securityWarning'>
      @ Login information, including the password,
      @ will be sent in the clear over an unencrypted connection.
      if( !g.sslNotAvailable ){
        @ Consider logging in at
        @ <a href='%s(g.zHttpsURL)'>%h(g.zHttpsURL)</a> instead.
      }
      @ </span></td></tr>
    }
    if( !anonFlag ){
    @ <tr>
    @   <td class="form_label" id="userlabel1">User ID:</td>
    @   <td><input type="text" id="u" aria-labelledby="userlabel1" name="u" \
    @ size="30" value="%s(anonFlag?"anonymous":"")" autofocus></td>
    @ </tr>
      @ <tr>
      @   <td class="form_label" id="userlabel1">User ID:</td>
      @   <td><input type="text" id="u" aria-labelledby="userlabel1" name="u" \
      @ size="30" value="" autofocus></td>
      @ </tr>
    }
    @ <tr>
    @  <td class="form_label" id="pswdlabel">Password:</td>
    @  <td><input aria-labelledby="pswdlabel" type="password" id="p" \
    @ name="p" value="" size="30">\
    if( zAnonPw && !noAnon ){
    @ name="p" value="" size="30"%s(anonFlag ? " autofocus" : "")>
    if( anonFlag ){
      @ </td></tr>
      @ <tr>
      @  <td></td><td>\
      captcha_speakit_button(uSeed, "Read the password out loud");
    }else if( zAnonPw && !noAnon ){
      captcha_speakit_button(uSeed, "Speak password for \"anonymous\"");
    }
    @ </td>
    @ </tr>
    if( !anonFlag ){
    @ <tr>
    @   <td></td>
    @   <td><input type="checkbox" name="remember" value="1" \
    @ id="remember-me" %s(rememberMe ? "checked=\"checked\"" : "")>
    @   <label for="remember-me">Remember me?</label></td>
    @ </tr>
    @ <tr>
    @   <td></td>
    @   <td><input type="submit" name="in" value="Login">
    @ </tr>
    if( !noAnon && login_self_register_available(0) ){
      @ <tr>
      @   <td></td>
      @   <td><input type="checkbox" name="remember" value="1" \
      @ id="remember-me" %s(rememberMe ? "checked=\"checked\"" : "")>
      @   <label for="remember-me">Remember me?</label></td>
      @ </tr>
      @ <tr>
      @   <td></td>
      @   <td><input type="submit" name="in" value="Login">
      @ </tr>
    }else{
      @ <tr>
      @   <td></td>
      @   <td><input type="submit" name="in" value="Verify that I am human">
      @ </tr>
    }
    if( !anonFlag && !noAnon && login_self_register_available(0) ){
      @ <tr>
      @   <td></td>
      @   <td><input type="submit" name="self" value="Create A New Account">
      @ </tr>
    }
    if( login_self_password_reset_available() ){
    if( !anonFlag && login_self_password_reset_available() ){
      @ <tr>
      @   <td></td>
      @   <td><input type="submit" name="pwreset" value="Reset My Password">
      @ </tr>
    }
    @ </table>
    if( zAnonPw && !noAnon ){
      const char *zDecoded = captcha_decode(uSeed, 0);
      int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
      char *zCaptcha = captcha_render(zDecoded);

      @ <p><input type="hidden" name="cs" value="%u(uSeed)">
      if( !anonFlag ){
      @ Visitors may enter <b>anonymous</b> as the user-ID with
      @ the 8-character hexadecimal password shown below:</p>
        @ Visitors may enter <b>anonymous</b> as the user-ID with
        @ the 8-character hexadecimal password shown below:</p>
      }
      @ <div class="captcha"><table class="captcha"><tr><td>\
      @ <pre class="captcha">
      @ %h(zCaptcha)
      @ </pre></td></tr></table>
      if( bAutoCaptcha ) {
      if( bAutoCaptcha && !anonFlag ) {
         @ <input type="button" value="Fill out captcha" id='autofillButton' \
         @ data-af='%s(zDecoded)'>
         builtin_request_js("login.js");
      }
      @ </div>
      free(zCaptcha);
    }
    @ </form>
  }
  if( login_is_individual() ){
  if( login_is_individual() && !anonFlag ){
    if( g.perm.EmailAlert && alert_enabled() ){
      @ <hr>
      @ <p>Configure <a href="%R/alerts">Email Alerts</a>
      @ for user <b>%h(g.zLogin)</b></p>
    }
    if( db_table_exists("repository","forumpost") ){
      @ <hr><p>
      @ <a href="%R/timeline?ss=v&y=f&vfx&u=%t(g.zLogin)">Forum
      @ post timeline</a> for user <b>%h(g.zLogin)</b></p>
    }
  }
  if( !anonFlag ){
  @ <hr><p>
  @ Select your preferred <a href="%R/skins">site skin</a>.
  @ </p>
  @ <hr><p>
  @ Manage your <a href="%R/cookies">cookies</a>.</p>
    @ <hr><p>
    @ Select your preferred <a href="%R/skins">site skin</a>.
    @ </p>
    @ <hr><p>
    @ Manage your <a href="%R/cookies">cookies</a> or your
    @ <a href="%R/tokens">access tokens</a>.</p>
  }
  if( login_is_individual() ){
    if( g.perm.Password ){
      char *zRPW = fossil_random_password(12);
      @ <hr>
      @ <p>Change Password for user <b>%h(g.zLogin)</b>:</p>
      form_begin(0, "%R/login");
      @ <table>
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
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
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1329
1330
1331
1332
1333
1334
1335




























































1336
1337
1338
1339
1340
1341
1342







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







      fossil_exit(0);
    }
  }
  fossil_free(zDecode);
  return uid;
}

/*
** SETTING: robot-restrict                width=40 block-text
** The VALUE of this setting is a list of GLOB patterns that match
** pages for which complex HTTP requests from robots should be disallowed.
** The recommended value for this setting is:
** 
**      timeline,vdiff,fdiff,annotate,blame
** 
*/

/*
** Check to see if the current HTTP request is a complex request that
** is coming from a robot and if access should restricted for such robots.
** For the purposes of this module, a "complex request" is an HTTP
** request with one or more query parameters other than "name".
**
** If this routine determines that robots should be restricted, then
** this routine publishes a redirect to the honeypot and exits without
** returning to the caller.
**
** This routine believes that this is a complex request is coming from
** a robot if all of the following are true:
**
**    *   The user is "nobody".
**    *   Either the REFERER field of the HTTP header is missing or empty,
**        or the USERAGENT field of the HTTP header suggests that
**        the request as coming from a robot.
**    *   There are one or more query parameters other than "name".
**
** Robot restrictions are governed by settings.
**
**    robot-restrict    The value is a list of GLOB patterns for pages
**                      that should restrict robot access.  No restrictions
**                      are applied if this setting is undefined or is
**                      an empty string.
*/
void login_restrict_robot_access(void){
  const char *zReferer;
  const char *zGlob;
  int isMatch = 1;
  if( g.zLogin!=0 ) return;
  zGlob = db_get("robot-restrict",0);
  if( zGlob==0 || zGlob[0]==0 ) return;
  if( g.isHuman ){
    zReferer = P("HTTP_REFERER");
    if( zReferer && zReferer[0]!=0 ) return;
  }
  if( cgi_qp_count()<1 ) return;
  isMatch = glob_multi_match(zGlob, g.zPath);
  if( !isMatch ) return;

  /* If we reach this point, it means we have a situation where we
  ** want to restrict the activity of a robot.
  */
  g.isHuman = 0;
  (void)exclude_spiders(0);
  cgi_reply();
  fossil_exit(0);
}

/*
** When this routine is called, we know that the request does not
** have a login on the present repository.  This routine checks to
** see if their login cookie might be for another member of the
** login-group.
**
** If this repository is not a part of any login group, then this
1358
1359
1360
1361
1362
1363
1364
1365

1366

1367
1368
1369
1370
1371
1372
1373
1367
1368
1369
1370
1371
1372
1373

1374
1375
1376
1377
1378
1379
1380
1381
1382
1383







-
+

+







** is valid.  If the login cookie checks out, it then sets global
** variables appropriately.
**
**    g.userUid      Database USER.UID value.  Might be -1 for "nobody"
**    g.zLogin       Database USER.LOGIN value.  NULL for user "nobody"
**    g.perm         Permissions granted to this user
**    g.anon         Permissions that would be available to anonymous
**    g.isHuman      True if the user is human, not a spider or robot
**    g.isRobot      True if the client is known to be a spider or robot
**    g.perm         Populated based on user account's capabilities
**    g.eAuthMethod  The mechanism used for authentication
**
*/
void login_check_credentials(void){
  int uid = 0;                  /* User id */
  const char *zCookie;          /* Text of the login cookie */
  const char *zIpAddr;          /* Raw IP address of the requestor */
  const char *zCap = 0;         /* Capability string */
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
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
1423
1424







-
+












-
+
+







  ** This feature allows the "fossil ui" command to give the user
  ** full access rights without having to log in.
  */
  zIpAddr = PD("REMOTE_ADDR","nil");
  if( ( cgi_is_loopback(zIpAddr)
       || (g.fSshClient & CGI_SSH_CLIENT)!=0 )
   && g.useLocalauth
   && db_get_int("localauth",0)==0
   && db_get_boolean("localauth",0)==0
   && P("HTTPS")==0
  ){
    char *zSeed;
    if( g.localOpen ) zLogin = db_lget("default-user",0);
    if( zLogin!=0 ){
      uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin);
    }else{
      uid = db_int(0, "SELECT uid FROM user WHERE cap LIKE '%%s%%'");
    }
    g.zLogin = db_text("?", "SELECT login FROM user WHERE uid=%d", uid);
    zCap = "sxy";
    g.noPswd = 1;
    g.isHuman = 1;
    g.isRobot = 0;
    g.eAuthMethod = AUTH_LOCAL;
    zSeed = db_text("??", "SELECT uid||quote(login)||quote(pw)||quote(cookie)"
                          "  FROM user WHERE uid=%d", uid);
    login_create_csrf_secret(zSeed);
    fossil_free(zSeed);
  }

  /* Check the login cookie to see if it matches a known valid user.
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437








1438
1439

1440
1441
1442
1443
1444
1445
1446
1447
1448

1449
1450
1451
1452
1453
1454
1455
1456


1457
1458
1459
1460
1461
1462
1463
1438
1439
1440
1441
1442
1443
1444




1445
1446
1447
1448
1449
1450
1451
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







-
-
-
-
+
+
+
+
+
+
+
+


+








-
+






-
-
+
+







          zUser = &zHash[i];
          break;
        }
      }
    }
    if( zUser==0 ){
      /* Invalid cookie */
    }else if( fossil_strcmp(zUser, "anonymous")==0 ){
      /* Cookies of the form "HASH/TIME/anonymous".  The TIME must not be
      ** too old and the sha1 hash of TIME/SECRET must match HASH.
      ** SECRET is the "captcha-secret" value in the repository.
    }else if( fossil_strcmp(zUser, "anonymous")==0
           && anon_cookie_lifespan()>0 ){
      /* Cookies of the form "HASH/TIME/anonymous".  The TIME must
      ** not be more than ANONYMOUS_COOKIE_LIFESPAN seconds ago and
      ** the sha1 hash of TIME/USERAGENT/SECRET must match HASH. USERAGENT
      ** is the HTTP_USER_AGENT of the client and SECRET is the
      ** "captcha-secret" value in the repository.  See tag-20250817a
      ** for the code the creates this cookie.
      */
      double rTime = atof(zArg);
      const char *zUserAgent = PD("HTTP_USER_AGENT","nil");
      Blob b;
      char *zSecret;
      int n = 0;

      do{
        blob_zero(&b);
        zSecret = captcha_secret(n++);
        if( zSecret==0 ) break;
        blob_appendf(&b, "%s/%s", zArg, zSecret);
        blob_appendf(&b, "%s/%s/%s", zArg, zUserAgent, zSecret);
        sha1sum_blob(&b, &b);
        if( fossil_strcmp(zHash, blob_str(&b))==0 ){
          uid = db_int(0,
              "SELECT uid FROM user WHERE login='anonymous'"
              " AND octet_length(cap)>0"
              " AND octet_length(pw)>0"
              " AND %.17g+0.25>julianday('now')",
              rTime
              " AND %.17g>julianday('now')",
              rTime+anon_cookie_lifespan()/1440.0
          );
        }
      }while( uid==0 );
      blob_reset(&b);
    }else{
      /* Cookies of the form "HASH/CODE/USER".  Search first in the
      ** local user table, then the user table for project CODE if we
1474
1475
1476
1477
1478
1479
1480

1481
1482
1483
1484
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
1516
1517
1518
1519

1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537





1538
1539
1540
1541
1542
1543
1544
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
1517
1518
1519
1520
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







+












+









+


















+
















-
-
+
+
+
+
+







          ** USER, but at least give them "anonymous" login. */
          uid = db_int(0, "SELECT uid FROM user WHERE login='anonymous'"
                          " AND octet_length(cap)>0"
                          " AND octet_length(pw)>0");
        }
      }
    }
    if( uid ) g.eAuthMethod = AUTH_COOKIE;
    login_create_csrf_secret(zHash);
  }

  /* If no user found and the REMOTE_USER environment variable is set,
  ** then accept the value of REMOTE_USER as the user.
  */
  if( uid==0 ){
    const char *zRemoteUser = P("REMOTE_USER");
    if( zRemoteUser && db_get_boolean("remote_user_ok",0) ){
      uid = db_int(0, "SELECT uid FROM user WHERE login=%Q"
                      " AND octet_length(cap)>0 AND octet_length(pw)>0",
                      zRemoteUser);
      if( uid ) g.eAuthMethod = AUTH_ENV;
    }
  }

  /* If the request didn't provide a login cookie or the login cookie didn't
  ** match a known valid user, check the HTTP "Authorization" header and
  ** see if those credentials are valid for a known user.
  */
  if( uid==0 && db_get_boolean("http_authentication_ok",0) ){
    uid = login_basic_authentication(zIpAddr);
    if( uid ) g.eAuthMethod = AUTH_HTTP;
  }

  /* Check for magic query parameters "resid" (for the username) and
  ** "token" for the password.  Both values (if they exist) will be
  ** obfuscated.
  */
  if( uid==0 ){
    char *zUsr, *zPW;
    if( (zUsr = unobscure(P("resid")))!=0
     && (zPW = unobscure(P("token")))!=0
    ){
      char *zSha1Pw = sha1_shared_secret(zPW, zUsr, 0);
      uid = db_int(0, "SELECT uid FROM user"
                      " WHERE login=%Q"
                      " AND (constant_time_cmp(pw,%Q)=0"
                      "      OR constant_time_cmp(pw,%Q)=0)",
                      zUsr, zSha1Pw, zPW);
      fossil_free(zSha1Pw);
      if( uid ) g.eAuthMethod = AUTH_PW;
    }
  }

  /* If no user found yet, try to log in as "nobody" */
  if( uid==0 ){
    uid = db_int(0, "SELECT uid FROM user WHERE login='nobody'");
    if( uid==0 ){
      /* If there is no user "nobody", then make one up - with no privileges */
      uid = -1;
      zCap = "";
    }
    login_create_csrf_secret("none");
  }

  login_set_uid(uid, zCap);

  /* Maybe restrict access to robots */
  login_restrict_robot_access();
  /* Maybe restrict access by robots */
  if( g.zLogin==0 && robot_restrict(g.zPath) ){
    cgi_reply();
    fossil_exit(0);
  }
}

/*
** Set the current logged in user to be uid.  zCap is precomputed
** (override) capabilities.  If zCap==0, then look up the capabilities
** in the USER table.
*/
1569
1570
1571
1572
1573
1574
1575
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
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







-
+

-
+

-
+













-
+







  ** "nobody" user is a special case in that g.zLogin==0.
  */
  g.userUid = uid;
  if( fossil_strcmp(g.zLogin,"nobody")==0 ){
    g.zLogin = 0;
  }
  if( PB("isrobot") ){
    g.isHuman = 0;
    g.isRobot = 1;
  }else if( g.zLogin==0 ){
    g.isHuman = isHuman(P("HTTP_USER_AGENT"));
    g.isRobot = !isHuman(P("HTTP_USER_AGENT"));
  }else{
    g.isHuman = 1;
    g.isRobot = 0;
  }

  /* Set the capabilities */
  login_replace_capabilities(zCap, 0);

  /* The auto-hyperlink setting allows hyperlinks to be displayed for users
  ** who do not have the "h" permission as long as their UserAgent string
  ** makes it appear that they are human.  Check to see if auto-hyperlink is
  ** enabled for this repository and make appropriate adjustments to the
  ** permission flags if it is.  This should be done before the permissions
  ** are (potentially) copied to the anonymous permission set; otherwise,
  ** those will be out-of-sync.
  */
  if( zCap[0] && !g.perm.Hyperlink && g.isHuman ){
  if( zCap[0] && !g.perm.Hyperlink && !g.isRobot ){
    int autoLink = db_get_int("auto-hyperlink",1);
    if( autoLink==1 ){
      g.jsHref = 1;
      g.perm.Hyperlink = 1;
    }else if( autoLink==2 ){
      g.perm.Hyperlink = 1;
    }
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
1939

1940
1941
1942
1943
1944
1945
1946
1920
1921
1922
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
1963
1964
1965
1966
1967
1968
1969







-
+








-
+




-
+


















-
+

-
+







      blob_appendf(&redir, "%s/login?g=%T", g.zHttpsURL, zPathInfo);
    }else{
      blob_appendf(&redir, "%R/login?g=%T", zPathInfo);
    }
    if( zQS && zQS[0] ){
      blob_appendf(&redir, "%%3f%T", zQS);
    }
    if( anonOk ) blob_append(&redir, "&anon", 5);
    if( anonOk ) blob_append(&redir, "&anon=1", 7);
    cgi_redirect(blob_str(&redir));
    /* NOTREACHED */
    assert(0);
  }
}

/*
** Call this routine if the user lacks g.perm.Hyperlink permission.  If
** the anonymous user has Hyperlink permission, then paint a mesage
** the anonymous user has Hyperlink permission, then paint a message
** to inform the user that much more information is available by
** logging in as anonymous.
*/
void login_anonymous_available(void){
  if( !g.perm.Hyperlink && g.anon.Hyperlink ){
  if( !g.perm.Hyperlink && g.anon.Hyperlink && anon_cookie_lifespan()>0 ){
    const char *zUrl = PD("PATH_INFO", "");
    @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br>
    @ Use <a href="%R/login?anon=1&amp;g=%T(zUrl)">anonymous login</a>
    @ to enable hyperlinks.</p>
  }
}

/*
** While rendering a form, call this routine to add the Anti-CSRF token
** as a hidden element of the form.
*/
void login_insert_csrf_secret(void){
  @ <input type="hidden" name="csrf" value="%s(g.zCsrfToken)">
}

/*
** Check to see if the candidate username zUserID is already used.
** Return 1 if it is already in use.  Return 0 if the name is
** available for a self-registeration.
** available for a self-registration.
*/
static int login_self_choosen_userid_already_exists(const char *zUserID){
static int login_self_chosen_userid_already_exists(const char *zUserID){
  int rc = db_exists(
    "SELECT 1 FROM user WHERE login=%Q "
    "UNION ALL "
    "SELECT 1 FROM event WHERE user=%Q OR euser=%Q",
    zUserID, zUserID, zUserID
  );
  return rc;
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
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







-
+










-
+







    zErr = "Password must be at least 6 characters long";
  }else if( fossil_strcmp(zPasswd,zConfirm)!=0 ){
    iErrLine = 5;
    zErr = "Passwords do not match";
  }else if( (uid = email_address_in_use(zEAddr))!=0 ){
    iErrLine = 3;
    zErr = "This email address is already associated with a user";
  }else if( login_self_choosen_userid_already_exists(zUserID) ){
  }else if( login_self_chosen_userid_already_exists(zUserID) ){
    iErrLine = 1;
    zErr = "This User ID is already taken. Choose something different.";
  }else{
    /* If all of the tests above have passed, that means that the submitted
    ** form contains valid data and we can proceed to create the new login */
    Blob sql;
    int uid;
    char *zPass = sha1_shared_secret(zPasswd, zUserID, 0);
    const char *zStartPerms = zPerms;
    if( db_get_boolean("selfreg-verify",0) ){
      /* If email verification is required for self-registration, initalize
      /* If email verification is required for self-registration, initialize
      ** the new user capabilities to just "7" (Sign up for email).  The
      ** full "default-perms" permissions will be added when they click
      ** the verification link on the email they are sent. */
      zStartPerms = "7";
    }
    blob_init(&sql, 0, 0);
    blob_append_sql(&sql,
2187
2188
2189
2190
2191
2192
2193
2194

2195
2196
2197
2198
2199
2200
2201
2210
2211
2212
2213
2214
2215
2216

2217
2218
2219
2220
2221
2222
2223
2224







-
+







        /* ssub */      ssub,
        /* smip */      g.zIpAddr
      );
      if( db_exists("SELECT 1 FROM subscriber WHERE semail=%Q"
                    "  AND sverified", zEAddr) ){
        /* This the case where the user was formerly a verified subscriber
        ** and here they have also registered as a user as well.  It is
        ** not necessary to repeat the verfication step */
        ** not necessary to repeat the verification step */
        login_redirect_to_g();
      }
      /* A verification email */
      pSender = alert_sender_new(0,0);
      blob_init(&hdr,0,0);
      blob_init(&body,0,0);
      blob_appendf(&hdr, "To: <%s>\n", zEAddr);
2358
2359
2360
2361
2362
2363
2364
2365

2366
2367
2368
2369
2370
2371
2372
2381
2382
2383
2384
2385
2386
2387

2388
2389
2390
2391
2392
2393
2394
2395







-
+







    @ If you need a password reset, you will have to negotiate that directly
    @ with the project administrator.
    style_finish_page();
    return;
  }
  zEAddr = PDT("ea","");

  /* Verify user imputs */
  /* Verify user inputs */
  if( !cgi_csrf_safe(1) || P("reqpwreset")==0 ){
    /* This is the initial display of the form.  No processing or error
    ** checking is to be done. Fall through into the form display
    **
    ** cgi_csrf_safe():  Nothing interesting happens on this page without
    ** a valid captcha solution, so we only need to check referrer and that
    ** the request is a POST.
Changes to src/lookslike.c.
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
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







-
+

-
-
-
+
+
+
+

-
+

+



+




-
-
+
+



-
+



-
+




-
+

-
+



-
+



-
+



+
+
+
+
+
+
+
+
+
-
+












-
+
















-
+







-
+





-
+


-
+





-
+


-
+


  if( strchr("(_", z[i+n])!=0 ) return 0;
  return 1;
}

/*
** Returns true if the given text contains certain keywords or
** punctuation which indicate that it might be an SQL injection attempt
** or some other kind of mischief.
** or Cross-site scripting attempt or some other kind of mischief.
**
** This is not a defense against vulnerabilities in the Fossil code.
** Rather, this is part of an effort to do early detection of malicious
** spiders to avoid them using up too many CPU cycles.
** This is not a primary defense against vulnerabilities in the Fossil 
** code.  Rather, this is part of an effort to do early detection of malicious
** spiders to avoid them using up too many CPU cycles.  Or, this routine
** can also be thought of as a secondary layer of defense against attacks.
*/
int looks_like_sql_injection(const char *zTxt){
int looks_like_attack(const char *zTxt){
  unsigned int i;
  int rc = 0;
  if( zTxt==0 ) return 0;
  for(i=0; zTxt[i]; i++){
    switch( zTxt[i] ){
      case '<':
      case ';':
      case '\'':
        return 1;
      case '/':             /* 0123456789 123456789 */
        if( strncmp(zTxt+i+1, "/wp-content/plugins/", 20)==0 ) return 1;
        if( strncmp(zTxt+i+1, "/wp-admin/admin-ajax", 20)==0 ) return 1;
        if( strncmp(zTxt+i+1, "/wp-content/plugins/", 20)==0 ) rc = 1;
        if( strncmp(zTxt+i+1, "/wp-admin/admin-ajax", 20)==0 ) rc = 1;
        break;
      case 'a':
      case 'A':
        if( isWholeWord(zTxt, i, "and", 3) ) return 1;
        if( isWholeWord(zTxt, i, "and", 3) ) rc = 1;
        break;
      case 'n':
      case 'N':
        if( isWholeWord(zTxt, i, "null", 4) ) return 1;
        if( isWholeWord(zTxt, i, "null", 4) ) rc = 1;
        break;
      case 'o':
      case 'O':
        if( isWholeWord(zTxt, i, "order", 5) && fossil_isspace(zTxt[i+5]) ){
          return 1;
          rc = 1;
        }
        if( isWholeWord(zTxt, i, "or", 2) ) return 1;
        if( isWholeWord(zTxt, i, "or", 2) ) rc = 1;
        break;
      case 's':
      case 'S':
        if( isWholeWord(zTxt, i, "select", 6) ) return 1;
        if( isWholeWord(zTxt, i, "select", 6) ) rc = 1;
        break;
      case 'w':
      case 'W':
        if( isWholeWord(zTxt, i, "waitfor", 7) ) return 1;
        if( isWholeWord(zTxt, i, "waitfor", 7) ) rc = 1;
        break;
    }
  }
  if( rc ){
    /* The test/markdown-test3.md document which is part of the Fossil source
    ** tree intentionally tries to fake an attack.  Do not report such
    ** errors. */
    const char *zPathInfo = P("PATH_INFO");
    if( sqlite3_strglob("/doc/*/test/markdown-test3.md", zPathInfo)==0 ){
      rc = 0;
    }
  }
  return 0;
  return rc;
}

/*
** This is a utility routine associated with the test-looks-like-sql-injection
** command.
**
** Read input from zInFile and print only those lines that look like they
** might be SQL injection.
**
** Or if bInvert is true, then show the opposite - those lines that do NOT
** look like SQL injection.
*/
static void show_sql_injection_lines(
static void show_attack_lines(
  const char *zInFile,       /* Name of input file */
  int bInvert,               /* Invert the sense of the output (-v) */
  int bDeHttpize             /* De-httpize the inputs.  (-d) */
){
  FILE *in;
  char zLine[10000];
  if( zInFile==0 || strcmp(zInFile,"-")==0 ){
    in = stdin;
  }else{
    in = fopen(zInFile, "rb");
    if( in==0 ){
      fossil_fatal("cannot open \"%s\" for reading\n", zInFile);
    }
  }
  while( fgets(zLine, sizeof(zLine), in) ){
    dehttpize(zLine);
    if( (looks_like_sql_injection(zLine)!=0) ^ bInvert ){
    if( (looks_like_attack(zLine)!=0) ^ bInvert ){
      fossil_print("%s", zLine);
    }
  }
  if( in!=stdin ) fclose(in);
}

/*
** COMMAND: test-looks-like-sql-injection
** COMMAND: test-looks-like-attack
**
** Read lines of input from files named as arguments (or from standard
** input if no arguments are provided) and print those that look like they
** might be part of an SQL injection attack.
**
** Used to test the looks_lide_sql_injection() utility subroutine, possibly
** Used to test the looks_lile_attack() utility subroutine, possibly
** by piping in actual server log data.
*/
void test_looks_like_sql_injection(void){
void test_looks_like_attack(void){
  int i;
  int bInvert = find_option("invert","v",0)!=0;
  int bDeHttpize = find_option("dehttpize","d",0)!=0;
  verify_all_options();
  if( g.argc==2 ){
    show_sql_injection_lines(0, bInvert, bDeHttpize);
    show_attack_lines(0, bInvert, bDeHttpize);
  }
  for(i=2; i<g.argc; i++){
    show_sql_injection_lines(g.argv[i], bInvert, bDeHttpize);
    show_attack_lines(g.argv[i], bInvert, bDeHttpize);
  }
}
Changes to src/main.c.
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
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







-
+



















-







#  include "json_detail.h"
#endif
#ifdef HAVE_BACKTRACE
# include <execinfo.h>
#endif

/*
** Default length of a timeout for serving an HTTP request.  Changable
** Default length of a timeout for serving an HTTP request.  Changeable
** using the "--timeout N" command-line option or via "timeout: N" in the
** CGI script.
*/
#ifndef FOSSIL_DEFAULT_TIMEOUT
# define FOSSIL_DEFAULT_TIMEOUT 600  /* 10 minutes */
#endif

/*
** Maximum number of auxiliary parameters on reports
*/
#define MX_AUX  5

/*
** Holds flags for fossil user permissions.
*/
struct FossilUserPerms {
  char Setup;            /* s: use Setup screens on web interface */
  char Admin;            /* a: administrative permission */
  char Password;         /* p: change password */
  char Query;            /* q: create new reports */
  char Write;            /* i: xfer inbound. check-in */
  char Read;             /* o: xfer outbound. check-out */
  char Hyperlink;        /* h: enable the display of hyperlinks */
  char Clone;            /* g: clone */
  char RdWiki;           /* j: view wiki via web */
  char NewWiki;          /* f: create new wiki via web */
  char ApndWiki;         /* m: append to wiki via web */
127
128
129
130
131
132
133



134
135
136
137
138
139
140
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142







+
+
+







** "th_tcl.c".
*/
struct TclContext {
  int argc;              /* Number of original (expanded) arguments. */
  char **argv;           /* Full copy of the original (expanded) arguments. */
  void *hLibrary;        /* The Tcl library module handle. */
  void *xFindExecutable; /* See tcl_FindExecutableProc in th_tcl.c. */
#if TCL_MAJOR_VERSION>=9
  void *xZipfsAppHook;   /* See TclZipfsAppHookProc in th_tcl.c. */
#endif
  void *xCreateInterp;   /* See tcl_CreateInterpProc in th_tcl.c. */
  void *xDeleteInterp;   /* See tcl_DeleteInterpProc in th_tcl.c. */
  void *xFinalize;       /* See tcl_FinalizeProc in th_tcl.c. */
  Tcl_Interp *interp;    /* The on-demand created Tcl interpreter. */
  int useObjProc;        /* Non-zero if an objProc can be called directly. */
  int useTip285;         /* Non-zero if TIP #285 is available. */
  char *setup;           /* The optional Tcl setup script. */
149
150
151
152
153
154
155

156
157
158
159
160
161
162
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165







+







  int argc; char **argv;  /* Command-line arguments to the program */
  char **argvOrig;        /* Original g.argv prior to removing options */
  char *nameOfExe;        /* Full path of executable. */
  const char *zErrlog;    /* Log errors to this file, if not NULL */
  const char *zPhase;     /* Phase of operation, for use by the error log
                          ** and for deriving $canonical_page TH1 variable */
  int isConst;            /* True if the output is unchanging & cacheable */
  int iResultCode;        /* Process reply code for commands */
  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 */
  char *zAuxSchema;       /* Main repository aux-schema */
  int dbIgnoreErrors;     /* Ignore database errors if true */
  char *zConfigDbName;    /* Path of the config database. NULL if not open */
  sqlite3_int64 now;      /* Seconds since 1970 */
184
185
186
187
188
189
190
191

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

194
195
196
197
198
199
200
201







-
+







  int fSystemTrace;       /* Trace calls to fossil_system(), --systemtrace */
  int fSshTrace;          /* Trace the SSH setup traffic */
  int fSshClient;         /* HTTP client flags for SSH client */
  int fNoHttpCompress;    /* Do not compress HTTP traffic (for debugging) */
  char *zSshCmd;          /* SSH command string */
  const char *zHttpCmd;   /* External program to do HTTP requests */
  int fNoSync;            /* Do not do an autosync ever.  --nosync */
  int fIPv4;              /* Use only IPv4, not IPv6. --ipv4 */
  int eIPvers;            /* 0: any   1: ipv4-only  2: ipv6-only */
  char *zPath;            /* Name of webpage being served (may be NULL) */
  char *zExtra;           /* Extra path information past the webpage name */
  char *zBaseURL;         /* Full text of the URL being served */
  char *zHttpsURL;        /* zBaseURL translated to https: */
  char *zTop;             /* Parent directory of zPath */
  int nExtraURL;          /* Extra bytes added to SCRIPT_NAME */
  const char *zExtRoot;   /* Document root for the /ext sub-website */
232
233
234
235
236
237
238







239


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

249
250
251
252
253
254
255
256
257







+
+
+
+
+
+
+
-
+
+







#if USE_SEE
  const char *zPidKey;    /* Saved value of the --usepidkey option.  Only
                           * applicable when using SEE on Windows or Linux. */
#endif
  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 eAuthMethod;        /* How the user authenticated to us */
# define AUTH_NONE   0    /* Not authenticated */
# define AUTH_COOKIE 1    /* Authentication by cookie */
# define AUTH_LOCAL  2    /* Uses loopback */
# define AUTH_PW     3    /* Authentication by password */
# define AUTH_ENV    4    /* Authenticated by REMOTE_USER environment var */
# define AUTH_HTTP   5    /* HTTP Basic Authentication */
  int isHuman;            /* True if access by a human, not a spider or bot */
  int isRobot;            /* True if the client is definitely a robot.  False
                          ** negatives are common for this flag */
  int comFmtFlags;        /* Zero or more "COMMENT_PRINT_*" bit flags, should be
                          ** accessed through get_comment_format(). */
  const char *zSockName;  /* Name of the unix-domain socket file */
  const char *zSockMode;  /* File permissions for unix-domain socket */
  const char *zSockOwner; /* Owner, or owner:group for unix-domain socket */

  /* Information used to populate the RCVFROM table */
287
288
289
290
291
292
293

















294
295
296
297
298
299
300
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  const char **azAuxOpt[MX_AUX]; /* Options of each option() value */
  int anAuxCols[MX_AUX];         /* Number of columns for option() values */
  int allowSymlinks;             /* Cached "allow-symlinks" option */
  int mainTimerId;               /* Set to fossil_timer_start() */
  int nPendingRequest;           /* # of HTTP requests in "fossil server" */
  int nRequest;                  /* Total # of HTTP request */
  int bAvoidDeltaManifests;      /* Avoid using delta manifests if true */

  /* State for communicating specific details between the inbound HTTP
  ** header parser (cgi.c), xfer.c, and http.c. */
  struct {
    char *zLoginCard;       /* Inbound "x-f-l-c" Cookie header. */
    int fLoginCardMode;     /* If non-0, emit login cards in outbound
                            ** requests as a HTTP cookie instead of as
                            ** part of the payload. Gets activated
                            ** on-demand based on xfer traffic
                            ** contents. Values, for
                            ** diagnostic/debugging purposes: 0x01=CLI
                            ** --flag, 0x02=cgi_setup_query_string(),
                            ** 0x04=page_xfer(),
                            ** 0x08=client_sync(). */
    int remoteVersion;      /* Remote fossil version. Used for negotiating
                            ** how to handle the login card. */
  } syncInfo;
#ifdef FOSSIL_ENABLE_JSON
  struct FossilJsonBits {
    int isJsonMode;            /* True if running in JSON mode, else
                                  false. This changes how errors are
                                  reported. In JSON mode we try to
                                  always output JSON-form error
                                  responses and always (in CGI mode)
637
638
639
640
641
642
643
644
645
646
647





648
649
650
651
652
653
654
665
666
667
668
669
670
671




672
673
674
675
676
677
678
679
680
681
682
683







-
-
-
-
+
+
+
+
+







    }
  }
  fossil_warning("%s", blob_str(&msg));
  blob_reset(&msg);
}

/*
** This function attempts to find command line options known to contain
** bitwise flags and initializes the associated global variables.  After
** this function executes, all global variables (i.e. in the "g" struct)
** containing option-settable bitwise flag fields must be initialized.
** Initialize the g.comFmtFlags global variable.
**
** Global command-line options --comfmtflags or --comment-format can be
** used for this.  However, those command-line options are undocumented
** and deprecated.   They are here for backwards compatibility only.
*/
static void fossil_init_flags_from_options(void){
  const char *zValue = find_option("comfmtflags", 0, 1);
  if( zValue==0 ){
    zValue = find_option("comment-format", 0, 1);
  }
  if( zValue ){
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
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







-
+
















-
-
+
+

-
+







  g.zPhase = "init";
#if !defined(_WIN32_WCE)
  if( fossil_getenv("FOSSIL_BREAK") ){
    if( fossil_isatty(0) && fossil_isatty(2) ){
      fprintf(stderr,
          "attach debugger to process %d and press any key to continue.\n",
          GETPID());
      fgetc(stdin);
      (void)fgetc(stdin);
    }else{
#if defined(_WIN32) || defined(WIN32)
      DebugBreak();
#elif defined(SIGTRAP)
      raise(SIGTRAP);
#endif
    }
  }
#endif

  fossil_printf_selfcheck();
  fossil_limit_memory(1);

  /* When updating the minimum SQLite version, change the number here,
  ** and also MINIMUM_SQLITE_VERSION value set in ../auto.def.  Take
  ** care that both places agree! */
  if( sqlite3_libversion_number()<3046000
   || strncmp(sqlite3_sourceid(),"2024-08-16",10)<0
  if( sqlite3_libversion_number()<3049000
   || strncmp(sqlite3_sourceid(),"2025-02-06",10)<0
  ){
    fossil_panic("Unsuitable SQLite version %s, must be at least 3.43.0",
    fossil_panic("Unsuitable SQLite version %s, must be at least 3.49.0",
                 sqlite3_libversion());
  }

  sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
  sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0);
  memset(&g, 0, sizeof(g));
  g.now = time(0);
756
757
758
759
760
761
762







763
764
765
766
767
768
769
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805







+
+
+
+
+
+
+







#ifdef FOSSIL_ENABLE_TCL
  memset(&g.tcl, 0, sizeof(TclContext));
  g.tcl.argc = g.argc;
  g.tcl.argv = copy_args(g.argc, g.argv); /* save full arguments */
#endif
  g.mainTimerId = fossil_timer_start();
  capture_case_sensitive_option();
  g.syncInfo.fLoginCardMode =
    /* The undocumented/unsupported --login-card-header provides a way
    ** to force use of the feature added by the xfer-login-card branch
    ** in 2025-07, intended for assisting in debugging any related
    ** issues. It can be removed once we reach the level of "implicit
    ** trust" in that feature. */
    find_option("login-card-header",0,0) ? 0x01 : 0;
  g.zVfsName = find_option("vfs",0,1);
  if( g.zVfsName==0 ){
    g.zVfsName = fossil_getenv("FOSSIL_VFS");
  }
  if( g.zVfsName ){
    sqlite3_vfs *pVfs = sqlite3_vfs_find(g.zVfsName);
    if( pVfs ){
793
794
795
796
797
798
799
800

801
802
803
804
805
806
807
829
830
831
832
833
834
835

836
837
838
839
840
841
842
843







-
+







      "another flag and is treated as such. --args FILENAME may be used\n"
      "in conjunction with any other flags.\n");
    fossil_exit(1);
  }else{
    const char *zChdir = find_option("chdir",0,1);
    g.isHTTP = 0;
    g.rcvid = 0;
    g.fQuiet = find_option("quiet", 0, 0)!=0;
    g.fQuiet = find_option("quiet", "q", 0)!=0;
    g.fSqlTrace = find_option("sqltrace", 0, 0)!=0;
    g.fSqlStats = find_option("sqlstats", 0, 0)!=0;
    g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
    g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
    g.fCgiTrace = find_option("cgitrace", 0, 0)!=0;
    g.fSshClient = 0;
    g.zSshCmd = 0;
824
825
826
827
828
829
830
831

832
833
834
835
836
837
838
860
861
862
863
864
865
866

867
868
869
870
871
872
873
874







-
+







    if( find_option("localtime",0,0) ) g.fTimeFormat = 2;
    if( zChdir && file_chdir(zChdir, 0) ){
      fossil_fatal("unable to change directories to %s", zChdir);
    }
#if USE_SEE
    db_maybe_handle_saved_encryption_key_for_process(SEE_KEY_READ);
#endif
    if( find_option("help",0,0)!=0 ){
    if( find_option("help","?",0)!=0 ){
      /* If --help is found anywhere on the command line, translate the command
       * to "fossil help cmdname" where "cmdname" is the first argument that
       * does not begin with a "-" character.  If all arguments start with "-",
       * translate to "fossil help argv[1] argv[2]...". */
      int i, nNewArgc;
      char **zNewArgv = fossil_malloc( sizeof(char*)*(g.argc+3) );
      zNewArgv[0] = g.argv[0];
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
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







-
+















-
-
-
+
-
-
+







#ifdef FOSSIL_ENABLE_TH1_HOOKS
    }
    if( !g.isHTTP && !g.fNoThHook && (rc==TH_OK || rc==TH_CONTINUE) ){
      Th_CommandNotify(pCmd->zName, pCmd->eCmdFlags);
    }
  }
#endif
  fossil_exit(0);
  fossil_exit(g.iResultCode);
  /*NOT_REACHED*/
  return 0;
}

/*
** Print a usage comment and quit
*/
void usage(const char *zFormat){
  fossil_fatal("Usage: %s %s %s", g.argv[0], g.argv[1], zFormat);
}

/*
** Remove n elements from g.argv beginning with the i-th element.
*/
static void remove_from_argv(int i, int n){
  int j;
  for(j=i+n; j<g.argc; i++, j++){
    g.argv[i] = g.argv[j];
  memmove(&g.argv[i], &g.argv[i+n], sizeof(g.argv[i])*(g.argc-i-n));
  }
  g.argc = i;
  g.argc -= n;
}


/*
** Look for a command-line option.  If present, remove it from the
** argument list and return a pointer to either the flag's name (if
** hasArg==0), sans leading - or --, or its value (if hasArg==1).
1062
1063
1064
1065
1066
1067
1068









1069
1070
1071
1072
1073
1074
1075
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117







+
+
+
+
+
+
+
+
+







      zReturn = g.argv[i+hasArg];
      remove_from_argv(i, 1+hasArg);
      break;
    }
  }
  return zReturn;
}

/*
** Restore an option previously removed by find_option().
*/
void restore_option(const char *zName, const char *zValue, int hasOpt){
  if( zValue==0 && hasOpt ) return;
  g.argv[g.argc++] = (char*)zName;
  if( hasOpt ) g.argv[g.argc++] = (char*)zValue;
}

/* Return true if zOption exists in the command-line arguments,
** but do not remove it from the list or otherwise process it.
*/
int has_option(const char *zOption){
  int i;
  int n = (int)strlen(zOption);
1131
1132
1133
1134
1135
1136
1137
1138

1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154

1155
1156
1157
1158
1159
1160
1161
1173
1174
1175
1176
1177
1178
1179

1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195

1196
1197
1198
1199
1200
1201
1202
1203







-
+















-
+







** the global state and return the new pointer, freeing any previous value.
** If absent and there is no cached value, return NULL.
*/
const char *find_repository_option(){
  const char *zRepository = find_option("repository", "R", 1);
  if( zRepository ){
    if( g.zRepositoryOption ) fossil_free(g.zRepositoryOption);
    g.zRepositoryOption = mprintf("%s", zRepository);
    g.zRepositoryOption = fossil_strdup(zRepository);
  }
  return g.zRepositoryOption;
}

/*
** Verify that there are no unprocessed command-line options.  If
** Any remaining command-line argument begins with "-" print
** an error message and quit.
**
** Exception: if "--" is encountered, it is consumed from the argument
** list and this function immediately returns. The effect is to treat
** all arguments after "--" as non-flags (conventionally used to
** enable passing-in of filenames which start with a dash).
**
** This function must normally only be called one time per app
** invokation. The exception is commands which process their
** invocation. The exception is commands which process their
** arguments, call this to confirm that there are no extraneous flags,
** then modify the arguments list for forwarding to another
** (sub)command (which itself will call this to confirm its own
** arguments).
*/
void verify_all_options(void){
  int i;
1328
1329
1330
1331
1332
1333
1334

1335
1336
1337
1338
1339
1340
1341
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384







+







  while( find_option("verbose","v",0)!=0 ) verboseFlag++;
  while( find_option("vv",0,0)!=0 )        verboseFlag += 2;

  /* We should be done with options.. */
  verify_all_options();
  fossil_version_blob(&versionInfo, verboseFlag);
  fossil_print("%s", blob_str(&versionInfo));
  blob_reset(&versionInfo);
}


/*
** WEBPAGE: version
**
** Show the version information for Fossil.
1358
1359
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
1398
1399
1401
1402
1403
1404
1405
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







-
+

















-
+








-
+







  @ %h(blob_str(&versionInfo))
  @ </pre>
  style_finish_page();
}


/*
** Set the g.zBaseURL value to the full URL for the toplevel of
** Set the g.zBaseURL value to the full URL for the top level of
** the fossil tree.  Set g.zTop to g.zBaseURL without the
** leading "http://" and the host and port.
**
** The g.zBaseURL is normally set based on HTTP_HOST and SCRIPT_NAME
** environment variables.  However, if zAltBase is not NULL then it
** is the argument to the --baseurl option command-line option and
** g.zBaseURL and g.zTop is set from that instead.
*/
void set_base_url(const char *zAltBase){
  int i;
  const char *zHost;
  const char *zMode;
  const char *zCur;

  if( g.zBaseURL!=0 ) return;
  if( zAltBase ){
    int i, n, c;
    g.zTop = g.zBaseURL = mprintf("%s", zAltBase);
    g.zTop = g.zBaseURL = fossil_strdup(zAltBase);
    i = (int)strlen(g.zBaseURL);
    while( i>3 && g.zBaseURL[i-1]=='/' ){ i--; }
    g.zBaseURL[i] = 0;
    if( strncmp(g.zTop, "http://", 7)==0 ){
      /* it is HTTP, replace prefix with HTTPS. */
      g.zHttpsURL = mprintf("https://%s", &g.zTop[7]);
    }else if( strncmp(g.zTop, "https://", 8)==0 ){
      /* it is already HTTPS, use it. */
      g.zHttpsURL = mprintf("%s", g.zTop);
      g.zHttpsURL = fossil_strdup(g.zTop);
    }else{
      fossil_fatal("argument to --baseurl should be 'http://host/path'"
                   " or 'https://host/path'");
    }
    for(i=n=0; (c = g.zTop[i])!=0; i++){
      if( c=='/' ){
        n++;
1481
1482
1483
1484
1485
1486
1487
1488

1489
1490
1491
1492
1493
1494
1495
1524
1525
1526
1527
1528
1529
1530

1531
1532
1533
1534
1535
1536
1537
1538







-
+







*/
NORETURN void fossil_redirect_home(void){
  /* In order for ?skin=... to work when visiting the site from
  ** a typical external link, we have to process it here, as
  ** that parameter gets lost during the redirect. We "could"
  ** pass the whole query string along instead, but that seems
  ** unnecessary. */
  if(cgi_setup_query_string()>1){
  if(cgi_setup_query_string() & 0x02){
    cookie_render();
  }
  cgi_redirectf("%R%s", db_get("index-page", "/index"));
}

/*
** If running as root, chroot to the directory containing the
1632
1633
1634
1635
1636
1637
1638
1639

1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658

1659
1660
1661
1662
1663
1664
1665
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







-
+


















-
+







  exit(1);
}

/*
** Return true if it is appropriate to redirect requests to HTTPS.
**
** Redirect to https is appropriate if all of the above are true:
**    (1) The redirect-to-https flag has a valud of iLevel or greater.
**    (1) The redirect-to-https flag has a value of iLevel or greater.
**    (2) The current connection is http, not https or ssh
**    (3) The sslNotAvailable flag is clear
*/
int fossil_wants_https(int iLevel){
  if( g.sslNotAvailable ) return 0;
  if( db_get_int("redirect-to-https",0)<iLevel ) return 0;
  if( P("HTTPS")!=0 ) return 0;
  return 1;
}

/*
** Redirect to the equivalent HTTPS request if the current connection is
** insecure and if the redirect-to-https flag greater than or equal to
** iLevel.  iLevel is 1 for /login pages and 2 for every other page.
*/
int fossil_redirect_to_https_if_needed(int iLevel){
  if( fossil_wants_https(iLevel) ){
    const char *zQS = P("QUERY_STRING");
    char *zURL;
    char *zURL = 0;
    if( zQS==0 || zQS[0]==0 ){
      zURL = mprintf("%s%T", g.zHttpsURL, P("PATH_INFO"));
    }else if( zQS[0]!=0 ){
      zURL = mprintf("%s%T?%s", g.zHttpsURL, P("PATH_INFO"), zQS);
    }
    cgi_redirect_with_status(zURL, 301, "Moved Permanently");
    return 1;
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
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







-
+



-
+






-
+







        @ <!-- Looking for repository named "%h(zRepo)" -->
        fprintf(stderr, "# looking for repository named \"%s\"\n", zRepo);
      }


      /* Restrictions on the URI for security:
      **
      **    1.  Reject characters that are not ASCII alphanumerics, 
      **    1.  Reject characters that are not ASCII alphanumerics,
      **        "-", "_", ".", "/", or unicode (above ASCII).
      **        In other words:  No ASCII punctuation or control characters
      **        other than "-", "_", "." and "/".
      **    2.  Exception to rule 1: Allow /X:/ where X is any ASCII 
      **    2.  Exception to rule 1: Allow /X:/ where X is any ASCII
      **        alphabetic character at the beginning of the name on windows.
      **    3.  "-" may not occur immediately after "/"
      **    4.  "." may not be adjacent to another "." or to "/"
      **
      ** Any character does not satisfy these constraints a Not Found
      ** error is returned.
      */  
      */
      szFile = 0;
      for(j=nBase+1, k=0; zRepo[j] && k<i-1; j++, k++){
        char c = zRepo[j];
        if( c>='a' && c<='z' ) continue;
        if( c>='A' && c<='Z' ) continue;
        if( c>='0' && c<='9' ) continue;
        if( (c&0x80)==0x80 ) continue;
1845
1846
1847
1848
1849
1850
1851





1852
1853
1854
1855
1856
1857
1858
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906







+
+
+
+
+







      ** then szFile will become zero (for an empty file) or positive.
      ** Special case:  Assume any file with a basename of ".fossil" does
      ** not exist.
      */
      zCleanRepo = file_cleanup_fullpath(zRepo);
      if( szFile==0 && sqlite3_strglob("*/.fossil",zRepo)!=0 ){
        szFile = file_size(zCleanRepo, ExtFILE);
        if( szFile>0 && !file_isfile(zCleanRepo, ExtFILE) ){
          /* Only let szFile be non-negative if zCleanRepo really is a file
          ** and not a directory or some other filesystem object. */
          szFile = -1;
        }
        if( g.fHttpTrace ){
          sqlite3_snprintf(sizeof(zBuf), zBuf, "%lld", szFile);
          @ <!-- file_size(%h(zCleanRepo)) is %s(zBuf) -->
          fprintf(stderr, "# file_size(%s) = %s\n", zCleanRepo, zBuf);
        }
      }

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
2086
2087
2088
2089
2090
2091
2092



2093
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







-
-
-
+
+
+
+
+
+
+






+
+
+
-
+
+

-
+







  /* Use the first element of PATH_INFO as the page name
  ** and deliver the appropriate page back to the user.
  */
  set_base_url(0);
  if( fossil_redirect_to_https_if_needed(2) ) return;
  if( zPathInfo==0 || zPathInfo[0]==0
      || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
    /* Second special case: If the PATH_INFO is blank, issue a redirect to
    ** the home page identified by the "index-page" setting in the repository
    ** CONFIG table, to "/index" if there no "index-page" setting. */
    /* Second special case: If the PATH_INFO is blank, issue a
    ** temporary 302 redirect:
    **    (1) to "/ckout" if g.useLocalauth and g.localOpen are both set.
    **    (2) to the home page identified by the "index-page" setting
    **        in the repository CONFIG table
    **    (3) to "/index" if there no "index-page" setting in CONFIG
    */
#ifdef FOSSIL_ENABLE_JSON
    if(g.json.isJsonMode){
      json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
      fossil_exit(0);
    }
#endif
    if( g.useLocalauth && g.localOpen ){
      cgi_redirectf("%R/ckout");
    }else{
    fossil_redirect_home() /*does not return*/;
      fossil_redirect_home() /*does not return*/;
    }
  }else{
    zPath = mprintf("%s", zPathInfo);
    zPath = fossil_strdup(zPathInfo);
  }

  /* Make g.zPath point to the first element of the path.  Make
  ** g.zExtra point to everything past that point.
  */
  g.zPath = &zPath[1];
  for(i=1; zPath[i] && zPath[i]!='/'; i++){}
2145
2146
2147
2148
2149
2150
2151
2152

2153
2154
2155
2156
2157
2158
2159
2201
2202
2203
2204
2205
2206
2207

2208
2209
2210
2211
2212
2213
2214
2215







-
+







        json_bootstrap_late();
        jsonOnce = 1;
      }
    }
#endif
    if( (pCmd->eCmdFlags & CMDFLAG_RAWCONTENT)==0 ){
      cgi_decode_post_parameters();
      if( !cgi_same_origin() ){
      if( !cgi_same_origin(0) ){
        isReadonly = 1;
        db_protect(PROTECT_READONLY);
      }
    }
    if( g.fCgiTrace ){
      fossil_trace("######## Calling %s #########\n", pCmd->zName);
      cgi_print_all(1, 1, 0);
2245
2246
2247
2248
2249
2250
2251











2252
2253
2254
2255
2256
2257
2258
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







+
+
+
+
+
+
+
+
+
+
+







**      website we have an CGI at http://fossil.com/index.html (note
**      ".com" instead of ".org") that looks like this:
**
**          #!/usr/bin/fossil
**          redirect: * https://fossil-scm.org/home
**
**      Thus requests to the .com website redirect to the .org website.
**      This form uses a 301 Permanent redirect.
**
**      On a "*" redirect, the PATH_INFO and QUERY_STRING of the query
**      that provoked the redirect are appended to the target.  So, for
**      example, if the input URL for the redirect above were
**      "http://www.fossil.com/index.html/timeline?c=20250404", then
**      the redirect would be to:
**
**           https://fossil-scm.org/home/timeline?c=20250404
**                                      ^^^^^^^^^^^^^^^^^^^^
**                                      Copied from input URL
*/
static void redirect_web_page(int nRedirect, char **azRedirect){
  int i;                             /* Loop counter */
  const char *zNotFound = 0;         /* Not found URL */
  const char *zName = P("name");
  set_base_url(0);
  if( zName==0 ){
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
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







-
+
+


-
+






-
+







      }
    }
  }
  if( zNotFound ){
    Blob to;
    const char *z;
    if( strstr(zNotFound, "%s") ){
      cgi_redirectf(zNotFound /*works-like:"%s"*/, zName);
      char *zTarget = mprintf(zNotFound /*works-like:"%s"*/, zName);
      cgi_redirect_perm(zTarget);
    }
    if( strchr(zNotFound, '?') ){
      cgi_redirect(zNotFound);
      cgi_redirect_perm(zNotFound);
    }
    blob_init(&to, zNotFound, -1);
    z = P("PATH_INFO");
    if( z && z[0]=='/' ) blob_append(&to, z, -1);
    z = P("QUERY_STRING");
    if( z && z[0]!=0 ) blob_appendf(&to, "?%s", z);
    cgi_redirect(blob_str(&to));
    cgi_redirect_perm(blob_str(&to));
  }else{
    @ <html>
    @ <head><title>No Such Object</title></head>
    @ <body>
    @ <p>No such object: <b>%h(zName)</b></p>
    @ </body>
    cgi_reply();
2330
2331
2332
2333
2334
2335
2336
2337








2338
2339
2340
2341
2342
2343
2344
2398
2399
2400
2401
2402
2403
2404

2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419







-
+
+
+
+
+
+
+
+







**                             or "directory:"
**
**    notfound: URL            When in "directory:" mode, redirect to
**                             URL if no suitable repository is found.
**
**    repolist                 When in "directory:" mode, display a page
**                             showing a list of available repositories if
**                             the URL is "/".
**                             the URL is "/".  Some control over the display
**                             is accomplished using environment variables.
**                             FOSSIL_REPOLIST_TITLE is the tital of the page.
**                             FOSSIL_REPOLIST_SHOW cause the "Description"
**                             column to display if it contains "description" as
**                             as a substring, and causes the Login-Group column
**                             to display if it contains the "login-group"
**                             substring.
**
**    localauth                Grant administrator privileges to connections
**                             from 127.0.0.1 or ::1.
**
**    nossl                    Signal that no SSL connections are available.
**
**    nocompress               Do not compress HTTP replies.
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
2447
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
2480







+
+
+
















-
+







**
**    redirect: REPO URL       Extract the "name" query parameter and search
**                             REPO for a check-in or ticket that matches the
**                             value of "name", then redirect to URL.  There
**                             can be multiple "redirect:" lines that are
**                             processed in order.  If the REPO is "*", then
**                             an unconditional redirect to URL is taken.
**                             When "*" is used a 301 permanent redirect is
**                             issued and the tail and query string from the
**                             original query are appended onto URL.
**
**    jsmode: VALUE            Specifies the delivery mode for JavaScript
**                             files. See the help text for the --jsmode
**                             flag of the http command.
**
**    mainmenu: FILE           Override the mainmenu config setting with the
**                             contents of the given file.
**
** Most CGI files contain only a "repository:" line.  It is uncommon to
** use any other option.
**
** The lines are processed in the order they are read, which is most
** significant for "errorlog:", which should be set before "repository:"
** so that any warnings from the database when opening the repository
** go to that log file.
**
** See also: [[http]], [[server]], [[winsrv]]
** See also: [[http]], [[server]], [[winsrv]] [Windows only]
*/
void cmd_cgi(void){
  const char *zNotFound = 0;
  char **azRedirect = 0;             /* List of repositories to redirect to */
  int nRedirect = 0;                 /* Number of entries in azRedirect */
  Glob *pFileGlob = 0;               /* Pattern for files */
  int allowRepoList = 0;             /* Allow lists of repository files */
2438
2439
2440
2441
2442
2443
2444
2445

2446
2447
2448
2449
2450
2451
2452
2453
2454
2455

2456
2457
2458
2459
2460
2461
2462
2516
2517
2518
2519
2520
2521
2522

2523
2524
2525
2526
2527
2528
2529
2530
2531
2532

2533
2534
2535
2536
2537
2538
2539
2540







-
+









-
+







      /* directory: DIRECTORY
      **
      ** If repository: is omitted, then terms of the PATH_INFO cgi parameter
      ** are appended to DIRECTORY looking for a repository (whose name ends
      ** in ".fossil") or a file in "files:".
      */
      db_close(1);
      g.zRepositoryName = mprintf("%s", blob_str(&value));
      g.zRepositoryName = fossil_strdup(blob_str(&value));
      blob_reset(&value);
      continue;
    }
    if( blob_eq(&key, "notfound:") && blob_token(&line, &value) ){
      /* notfound: URL
      **
      ** If using directory: and no suitable repository or file is found,
      ** then redirect to URL.
      */
      zNotFound = mprintf("%s", blob_str(&value));
      zNotFound = fossil_strdup(blob_str(&value));
      blob_reset(&value);
      continue;
    }
    if( blob_eq(&key, "localauth") ){
      /* localauth
      **
      ** Grant "administrator" privileges to users connecting with HTTP
2492
2493
2494
2495
2496
2497
2498
2499
2500


2501
2502
2503
2504
2505
2506
2507
2570
2571
2572
2573
2574
2575
2576


2577
2578
2579
2580
2581
2582
2583
2584
2585







-
-
+
+







    }
    if( blob_eq(&key, "redirect:") && blob_token(&line, &value)
            && blob_token(&line, &value2) ){
      /* See the header comment on the redirect_web_page() function
      ** above for details. */
      nRedirect++;
      azRedirect = fossil_realloc(azRedirect, 2*nRedirect*sizeof(char*));
      azRedirect[nRedirect*2-2] = mprintf("%s", blob_str(&value));
      azRedirect[nRedirect*2-1] = mprintf("%s", blob_str(&value2));
      azRedirect[nRedirect*2-2] = fossil_strdup(blob_str(&value));
      azRedirect[nRedirect*2-1] = fossil_strdup(blob_str(&value2));
      blob_reset(&value);
      blob_reset(&value2);
      continue;
    }
    if( blob_eq(&key, "files:") && blob_token(&line, &value) ){
      /* files: GLOBLIST
      **
2520
2521
2522
2523
2524
2525
2526

2527
2528





2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539

2540
2541
2542
2543
2544
2545
2546
2547
2548

2549
2550
2551
2552
2553
2554
2555
2598
2599
2600
2601
2602
2603
2604
2605


2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620

2621
2622
2623
2624
2625
2626
2627
2628
2629

2630
2631
2632
2633
2634
2635
2636
2637







+
-
-
+
+
+
+
+










-
+








-
+







    if( blob_eq(&key, "setenv:") && blob_token(&line, &value) ){
      /* setenv: NAME VALUE
      ** setenv: NAME
      **
      ** Sets environment variable NAME to VALUE.  If VALUE is omitted, then
      ** the environment variable is unset.
      */
      char *zValue;
      blob_token(&line,&value2);
      fossil_setenv(blob_str(&value), blob_str(&value2));
      blob_tail(&line,&value2);
      blob_trim(&value2);
      zValue = blob_str(&value2);
      while( fossil_isspace(zValue[0]) ){ zValue++; }
      fossil_setenv(blob_str(&value), zValue);
      blob_reset(&value);
      blob_reset(&value2);
      continue;
    }
    if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
      /* errorlog: FILENAME
      **
      ** Causes messages from warnings, errors, and panics to be appended
      ** to FILENAME.
      */
      g.zErrlog = mprintf("%s", blob_str(&value));
      g.zErrlog = fossil_strdup(blob_str(&value));
      blob_reset(&value);
      continue;
    }
    if( blob_eq(&key, "extroot:") && blob_token(&line, &value) ){
      /* extroot: DIRECTORY
      **
      ** Enables the /ext webpage to use sub-cgi rooted at DIRECTORY
      */
      g.zExtRoot = mprintf("%s", blob_str(&value));
      g.zExtRoot = fossil_strdup(blob_str(&value));
      blob_reset(&value);
      continue;
    }
    if( blob_eq(&key, "timeout:") && blob_token(&line, &value) ){
      /* timeout: SECONDS
      **
      ** Set an alarm() that kills the process after SECONDS.  The
2602
2603
2604
2605
2606
2607
2608
2609

2610
2611
2612
2613
2614
2615
2616
2684
2685
2686
2687
2688
2689
2690

2691
2692
2693
2694
2695
2696
2697
2698







-
+







    if( blob_eq(&key, "mainmenu:") && blob_token(&line, &value) ){
      /* mainmenu: FILENAME
      **
      ** Use the contents of FILENAME as the value of the site's
      ** "mainmenu" setting, overriding the contents (for this
      ** request) of the db-side setting or the hard-coded default.
      */
      g.zMainMenuFile = mprintf("%s", blob_str(&value));
      g.zMainMenuFile = fossil_strdup(blob_str(&value));
      blob_reset(&value);
      continue;
    }
    if( blob_eq(&key, "cgi-debug:") && blob_token(&line, &value) ){
      /* cgi-debug: FILENAME
      **
      ** Causes output from cgi_debug() and CGIDEBUG(()) calls to go
2659
2660
2661
2662
2663
2664
2665
2666

2667
2668
2669
2670
2671
2672
2673
2741
2742
2743
2744
2745
2746
2747

2748
2749
2750
2751
2752
2753
2754
2755







-
+







static void find_server_repository(int arg, int fCreate){
  if( g.argc<=arg ){
    db_must_be_within_tree();
  }else{
    const char *zRepo = g.argv[arg];
    int isDir = file_isdir(zRepo, ExtFILE);
    if( isDir==1 ){
      g.zRepositoryName = mprintf("%s", zRepo);
      g.zRepositoryName = fossil_strdup(zRepo);
      file_simplify_name(g.zRepositoryName, -1, 0);
    }else{
      if( isDir==0 && fCreate ){
        const char *zPassword;
        db_create_repository(zRepo);
        db_open_repository(zRepo);
        db_begin_transaction();
2793
2794
2795
2796
2797
2798
2799
2800

2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812

2813
2814
2815
2816
2817
2818
2819
2875
2876
2877
2878
2879
2880
2881

2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893

2894
2895
2896
2897
2898
2899
2900
2901







-
+











-
+







** selects from among the various repositories.  If the pathname does
** not select a valid repository and the --notfound option is available,
** then the server redirects (HTTP code 302) to the URL of --notfound.
** When REPOSITORY is a directory, the pathname must contain only
** alphanumerics, "_", "/", "-" and "." and no "-" may occur after a "/"
** and every "." must be surrounded on both sides by alphanumerics or else
** a 404 error is returned.  Static content files in the directory are
** returned if they match comma-separate GLOB pattern specified by --files
** returned if they match comma-separated GLOB pattern specified by --files
** and do not match "*.fossil*" and have a well-known suffix.
**
** Options:
**   --acme              Deliver files from the ".well-known" subdirectory
**   --baseurl URL       Base URL (useful with reverse proxies)
**   --cert FILE         Use TLS (HTTPS) encryption with the certificate (the
**                       fullchain.pem) taken from FILE.
**   --chroot DIR        Use directory for chroot instead of repository path.
**   --ckout-alias N     Treat URIs of the form /doc/N/... as if they were
**                          /doc/ckout/...
**   --extroot DIR       Document root for the /ext extension mechanism
**   --files GLOB        Comma-separate glob patterns for static file to serve
**   --files GLOB        Comma-separated glob patterns for static files to serve
**   --host NAME         DNS Hostname of the server
**   --https             The HTTP request originated from https but has already
**                       been decoded by a reverse proxy.  Hence, URLs created
**                       by Fossil should use "https:" rather than "http:".
**   --in FILE           Take input from FILE instead of standard input
**   --ipaddr ADDR       Assume the request comes from the given IP address
**   --jsmode MODE       Determine how JavaScript is delivered with pages.
2847
2848
2849
2850
2851
2852
2853
2854

2855
2856
2857
2858
2859
2860
2861
2929
2930
2931
2932
2933
2934
2935

2936
2937
2938
2939
2940
2941
2942
2943







-
+







**   --scgi              Interpret input as SCGI rather than HTTP
**   --skin LABEL        Use override skin LABEL. Use an empty string ("")
**                       to force use of the current local skin config.
**   --th-trace          Trace TH1 execution (for debugging purposes)
**   --usepidkey         Use saved encryption key from parent process. This is
**                       only necessary when using SEE on Windows or Linux.
**
** See also: [[cgi]], [[server]], [[winsrv]]
** See also: [[cgi]], [[server]], [[winsrv]] [Windows only]
*/
void cmd_http(void){
  const char *zIpAddr = 0;
  const char *zNotFound;
  const char *zHost;
  const char *zAltBase;
  const char *zFileGlob;
2871
2872
2873
2874
2875
2876
2877
2878

2879
2880
2881
2882
2883
2884
2885
2953
2954
2955
2956
2957
2958
2959

2960
2961
2962
2963
2964
2965
2966
2967







-
+








  /* The winhttp module passes the --files option as --files-urlenc with
  ** the argument being URL encoded, to avoid wildcard expansion in the
  ** shell.  This option is for internal use and is undocumented.
  */
  zFileGlob = find_option("files-urlenc",0,1);
  if( zFileGlob ){
    char *z = mprintf("%s", zFileGlob);
    char *z = fossil_strdup(zFileGlob);
    dehttpize(z);
    zFileGlob = z;
  }else{
    zFileGlob = find_option("files",0,1);
  }
  skin_override();
  zNotFound = find_option("notfound", 0, 1);
2910
2911
2912
2913
2914
2915
2916













2917
2918
2919
2920
2921
2922
2923
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018







+
+
+
+
+
+
+
+
+
+
+
+
+







  }else{
    g.httpOut = stdout;
#if defined(_WIN32)
   _setmode(_fileno(stdout), _O_BINARY);
#endif
  }
  zIpAddr = find_option("ipaddr",0,1);
#if defined(_WIN32)
  /* The undocumented option "--as NAME" causes NAME to become
  ** the fake command name.  This only happens on Windows and only
  ** if preceded by --in, --out, and --ipaddr.  It is a work-around
  ** to get the original command-name down into the "http" command that
  ** is run in a subprocess to manage HTTP requests on Windows for
  ** commands like "fossil ui" and "fossil server".
  */
  if( zInFile && zOutFile && zIpAddr ){
    const char *z = find_option("as",0,1);
    if( z ) g.zCmdName = z;
  }
#endif
  useSCGI = find_option("scgi", 0, 0)!=0;
  if( useSCGI ) g.zReqType = "SCGI";
  zAltBase = find_option("baseurl", 0, 1);
  if( find_option("nodelay",0,0)!=0 ) backoffice_no_delay();
  if( zAltBase ) set_base_url(zAltBase);
  if( find_option("https",0,0)!=0 ){
    zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */
3002
3003
3004
3005
3006
3007
3008
3009

3010
3011
3012
3013
3014
3015
3016
3017
3018
3019

3020

3021
3022
3023
3024
3025
3026
3027
3028
3029
3030

3031



3032

3033
3034
3035
3036
3037
3038
3039
3097
3098
3099
3100
3101
3102
3103

3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121

3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140







-
+










+

+




-





+

+
+
+

+







** This command can used for interactive debugging of web pages.  For
** example, one can put a simple HTTP request in a file like this:
**
**     echo 'GET /timeline' >request.txt
**
** Then run (in a debugger) a command like this:
**
**     fossil test-http --debug <request.txt
**     fossil test-http <request.txt
**
** This command is also used internally by the "ssh" sync protocol.  Some
** special processing to support sync happens when this command is run
** and the SSH_CONNECTION environment variable is set.  Use the --test
** option on interactive sessions to avoid that special processing when
** using this command interactively over SSH.  A better solution would be
** to use a different command for "ssh" sync, but we cannot do that without
** breaking legacy.
**
** Options:
**   --csrf-safe N       Set cgi_csrf_safe() to return N
**   --nobody            Pretend to be user "nobody"
**   --ssh-sim           Pretend to be over an SSH connection
**   --test              Do not do special "sync" processing when operating
**                       over an SSH link
**   --th-trace          Trace TH1 execution (for debugging purposes)
**   --usercap   CAP     User capability string (Default: "sxy")
**
*/
void cmd_test_http(void){
  const char *zIpAddr;    /* IP address of remote client */
  const char *zUserCap;
  int bTest = 0;
  const char *zCsrfSafe = find_option("csrf-safe",0,1);

  if( find_option("ssh-sim",0,0)!=0 ){
    putenv("SSH_CONNECTION=127.0.0.1 12345 127.0.0.2 23456");
  }
  Th_InitTraceLog();
  if( zCsrfSafe ) g.okCsrf = atoi(zCsrfSafe);
  zUserCap = find_option("usercap",0,1);
  if( !find_option("nobody",0,0) ){
    if( zUserCap==0 ){
      g.useLocalauth = 1;
      zUserCap = "sxy";
    }
    login_set_capabilities(zUserCap, 0);
3165
3166
3167
3168
3169
3170
3171



3172
3173
3174
3175

3176
3177
3178
3179
3180
3181
3182
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287







+
+
+




+







**   --cert FILE         Use TLS (HTTPS) encryption with the certificate (the
**                       fullchain.pem) taken from FILE.
**   --chroot DIR        Use directory for chroot instead of repository path
**   --ckout-alias NAME  Treat URIs of the form /doc/NAME/... as if they were
**                       /doc/ckout/...
**   --create            Create a new REPOSITORY if it does not already exist
**   --errorlog FILE     Append HTTP error messages to FILE
**   --extpage FILE      Shortcut for "--extroot DIR --page ext/TAIL" where
**                       DIR is the directory holding FILE and TAIL is the
**                       filename at the end of FILE.  Only works for "ui".
**   --extroot DIR       Document root for the /ext extension mechanism
**   --files GLOBLIST    Comma-separated list of glob patterns for static files
**   --fossilcmd PATH    The pathname of the "fossil" executable on the remote
**                       system when REPOSITORY is remote.
**   --from PATH         Use PATH as the diff baseline for the /ckout page
**   --localauth         Enable automatic login for requests from localhost
**   --localhost         Listen on 127.0.0.1 only (always true for "ui")
**   --https             Indicates that the input is coming through a reverse
**                       proxy that has already translated HTTPS into HTTP.
**   --jsmode MODE       Determine how JavaScript is delivered with pages.
**                       Mode can be one of:
**                          inline       All JavaScript is inserted inline at
3215
3216
3217
3218
3219
3220
3221
3222

3223
3224
3225
3226
3227
3228
3229
3320
3321
3322
3323
3324
3325
3326

3327
3328
3329
3330
3331
3332
3333
3334







-
+







**   --socket-owner USR  Try to set the owner of the unix socket to USR.
**                       USR can be of the form USER:GROUP to set both
**                       user and group.
**   --th-trace          Trace TH1 execution (for debugging purposes)
**   --usepidkey         Use saved encryption key from parent process.  This is
**                       only necessary when using SEE on Windows or Linux.
**
** See also: [[cgi]], [[http]], [[winsrv]]
** See also: [[cgi]], [[http]], [[winsrv]] [Windows only]
*/
void cmd_webserver(void){
  int iPort, mxPort;        /* Range of TCP ports allowed */
  const char *zPort;        /* Value of the --port option */
  const char *zBrowser;     /* Name of web browser program */
  char *zBrowserCmd = 0;    /* Command to launch the web browser */
  int isUiCmd;              /* True if command is "ui", not "server' */
3241
3242
3243
3244
3245
3246
3247


3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267

3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283












3284
3285



3286



3287
3288
3289
3290
3291
3292
3293
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373

3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402


3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416







+
+



















-
+
















+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+

+
+
+







  int fCreate = 0;           /* The --create flag */
  int fNoBrowser = 0;        /* Do not auto-launch web-browser */
  const char *zInitPage = 0; /* Start on this page.  --page option */
  int findServerArg = 2;     /* argv index for find_server_repository() */
  char *zRemote = 0;         /* Remote host on which to run "fossil ui" */
  const char *zJsMode;       /* The --jsmode parameter */
  const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */
  const char *zFrom;         /* Value for --from */
  const char *zExtPage = 0;  /* Argument to --extpage */


#if USE_SEE
  db_setup_for_saved_encryption_key();
#endif

#if defined(_WIN32)
  const char *zStopperFile;    /* Name of file used to terminate server */
  zStopperFile = find_option("stopper", 0, 1);
#endif

  if( g.zErrlog==0 ){
    g.zErrlog = "-";
  }
  g.zExtRoot = find_option("extroot",0,1);
  zJsMode = find_option("jsmode",0,1);
  builtin_set_js_delivery_mode(zJsMode,0);
  zFileGlob = find_option("files-urlenc",0,1);
  if( zFileGlob ){
    char *z = mprintf("%s", zFileGlob);
    char *z = fossil_strdup(zFileGlob);
    dehttpize(z);
    zFileGlob = z;
  }else{
    zFileGlob = find_option("files",0,1);
  }
  skin_override();
#if !defined(_WIN32)
  zChRoot = find_option("chroot",0,1);
  noJail = find_option("nojail",0,0)!=0;
  zTimeout = find_option("max-latency",0,1);
#endif
  g.useLocalauth = find_option("localauth", 0, 0)!=0;
  Th_InitTraceLog();
  zPort = find_option("port", "P", 1);
  isUiCmd = g.argv[1][0]=='u';
  if( isUiCmd ){
    zFrom = find_option("from", 0, 1);
    if( zFrom && zFrom==file_tail(zFrom) ){
      fossil_fatal("the argument to --from must be a pathname for"
                   " the \"ui\" command");
    }
    zExtPage = find_option("extpage",0,1);
    if( zExtPage ){
      char *zFullPath = file_canonical_name_dup(zExtPage);
      g.zExtRoot = file_dirname(zFullPath);
      zInitPage = mprintf("ext/%s",file_tail(zFullPath));
      fossil_free(zFullPath);
    }else{
    zInitPage = find_option("page", "p", 1);
    if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
      zInitPage = find_option("page", "p", 1);
      if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
    }
    zFossilCmd = find_option("fossilcmd", 0, 1);
    if( zFrom && zInitPage==0 ){
      zInitPage = mprintf("ckout?exbase=%H", zFrom);
    }
  }
  zNotFound = find_option("notfound", 0, 1);
  allowRepoList = find_option("repolist",0,0)!=0;
  if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1;
  zAltBase = find_option("baseurl", 0, 1);
  fCreate = find_option("create",0,0)!=0;
  g.zReqType = "HTTP";
3354
3355
3356
3357
3358
3359
3360
3361

3362
3363
3364
3365
3366
3367
3368
3477
3478
3479
3480
3481
3482
3483

3484
3485
3486
3487
3488
3489
3490
3491







-
+







    ** chdir to that check-out so that the current version
    ** gets highlighted in the timeline by default. */
    const char * zDir = g.argv[2];
    if(dir_has_ckout_db(zDir)){
      if(0!=file_chdir(zDir, 0)){
        fossil_fatal("Cannot chdir to %s", zDir);
      }
      findServerArg = 99;
      findServerArg = g.argc;
      fCreate = 0;
      g.argv[2] = 0;
      --g.argc;
    }
  }
  if( isUiCmd && 3==g.argc
   && (zRemote = (char*)file_skip_userhost(g.argv[2]))!=0
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392

3393
3394
3395
3396
3397
3398
3399
3400
3505
3506
3507
3508
3509
3510
3511




3512

3513
3514
3515
3516
3517
3518
3519







-
-
-
-
+
-







    g.useLocalauth = 1;
    allowRepoList = 1;
  }
  if( !zRemote ){
    find_server_repository(findServerArg, fCreate);
  }
  if( zInitPage==0 ){
    if( isUiCmd && g.localOpen ){
      zInitPage = "timeline?c=current";
    }else{
      zInitPage = "";
    zInitPage = "";
    }
  }
  if( zPort ){
    if( strchr(zPort,':') ){
      int i;
      for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){}
      if( i>0 ){
        if( zPort[0]=='[' && zPort[i-1]==']' ){
3429
3430
3431
3432
3433
3434
3435
3436

3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455


3456
3457
3458






3459


3460
3461
3462
3463
3464
3465
3466
3467

3468
3469
3470
3471
3472
3473
3474
3548
3549
3550
3551
3552
3553
3554

3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573

3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584

3585
3586
3587
3588
3589
3590
3591
3592
3593

3594
3595
3596
3597
3598
3599
3600
3601







-
+


















-
+
+



+
+
+
+
+
+
-
+
+







-
+







  if( zRemote ){
    /* If a USER@HOST:REPO argument is supplied, then use SSH to run
    ** "fossil ui --nobrowser" on the remote system and to set up a
    ** tunnel from the local machine to the remote. */
    FILE *sshIn;
    Blob ssh;
    int bRunning = 0;    /* True when fossil starts up on the remote */
    int isRetry;         /* True if on the second attempt */        
    int isRetry;         /* True if on the second attempt */
    char zLine[1000];

    blob_init(&ssh, 0, 0);
    for(isRetry=0; isRetry<2 && !bRunning; isRetry++){
      blob_reset(&ssh);
      transport_ssh_command(&ssh);
      blob_appendf(&ssh,
         " -t -L 127.0.0.1:%d:127.0.0.1:%d %!$",
         iPort, iPort, zRemote
      );
      if( zFossilCmd==0 ){
        if( ssh_needs_path_argument(zRemote,-1) ^ isRetry ){
          ssh_add_path_argument(&ssh);
        }
        blob_append_escaped_arg(&ssh, "fossil", 1);
      }else{
        blob_appendf(&ssh, " %$", zFossilCmd);
      }
      blob_appendf(&ssh, " ui --nobrowser --localauth --port %d", iPort);
      blob_appendf(&ssh, " ui --nobrowser --localauth --port 127.0.0.1:%d",
                   iPort);
      if( zNotFound ) blob_appendf(&ssh, " --notfound %!$", zNotFound);
      if( zFileGlob ) blob_appendf(&ssh, " --files-urlenc %T", zFileGlob);
      if( g.zCkoutAlias ) blob_appendf(&ssh," --ckout-alias %!$",g.zCkoutAlias);
      if( zExtPage ){
        if( !file_is_absolute_path(zExtPage) ){
          zExtPage = mprintf("%s/%s", g.argv[2], zExtPage);
        }
        blob_appendf(&ssh, " --extpage %$", zExtPage);
      }else if( g.zExtRoot ){
      if( g.zExtRoot ) blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
        blob_appendf(&ssh, " --extroot %$", g.zExtRoot);
      }
      if( skin_in_use() ) blob_appendf(&ssh, " --skin %s", skin_in_use());
      if( zJsMode ) blob_appendf(&ssh, " --jsmode %s", zJsMode);
      if( fCreate ) blob_appendf(&ssh, " --create");
      blob_appendf(&ssh, " %$", g.argv[2]);
      if( isRetry ){
        fossil_print("First attempt to run \"fossil\" on %s failed\n"
                     "Retry: ", zRemote);
      } 
      }
      fossil_print("%s\n", blob_str(&ssh));
      sshIn = popen(blob_str(&ssh), "r");
      if( sshIn==0 ){
        fossil_fatal("unable to %s", blob_str(&ssh));
      }
      while( fgets(zLine, sizeof(zLine), sshIn) ){
        fputs(zLine, stdout);
3570
3571
3572
3573
3574
3575
3576
3577

3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3697
3698
3699
3700
3701
3702
3703

3704
3705
3706
3707

3708
3709
3710
3711
3712
3713
3714







-
+



-







  if( g.httpUseSSL && g.httpSSLConn ){
    ssl_close_server(g.httpSSLConn);
    g.httpSSLConn = 0;
  }
#endif /* FOSSIL_ENABLE_SSL */

#else /* WIN32 */
  find_server_repository(2, 0);
  /* Win32 implementation */
  if( fossil_strcmp(g.zRepositoryName,"/")==0 ){
    allowRepoList = 1;
  }
  /* Win32 implementation */
  if( allowRepoList ){
    flags |= HTTP_SERVER_REPOLIST;
  }
  if( win32_http_service(iPort, zAltBase, zNotFound, zFileGlob, flags) ){
    win32_http_server(iPort, mxPort, zBrowserCmd, zStopperFile,
                      zAltBase, zNotFound, zFileGlob, zIpAddr, flags);
  }
3629
3630
3631
3632
3633
3634
3635



3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650




3651
3652
3653
3654

3655
3656
3657
3658
3659
3660
3661
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776



3777
3778
3779
3780




3781
3782
3783
3784
3785
3786
3787
3788







+
+
+












-
-
-
+
+
+
+
-
-
-
-
+







**     case=1           Issue a fossil_warning() while generating the page.
**     case=2           Extra db_begin_transaction()
**     case=3           Extra db_end_transaction()
**     case=4           Error during SQL processing
**     case=5           Call the segfault handler
**     case=6           Call webpage_assert()
**     case=7           Call webpage_error()
**     case=8           Simulate a timeout
**     case=9           Simulate a TH1 XSS vulnerability
**     case=10          Simulate a TH1 SQL-injection vulnerability
*/
void test_warning_page(void){
  int iCase = atoi(PD("case","0"));
  int i;
  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
  }
  style_set_current_feature("test");
  style_header("Warning Test Page");
  style_submenu_element("Error Log","%R/errorlog");
  if( iCase<1 || iCase>4 ){
    @ <p>Generate a message to the <a href="%R/errorlog">error log</a>
    @ by clicking on one of the following cases:
  @ <p>This page will generate various kinds of errors to test Fossil's
  @ reaction.  Depending on settings, a message might be written
  @ into the <a href="%R/errorlog">error log</a>.  Click on
  @ one of the following hyperlinks to generate a simulated error:
  }else{
    @ <p>This is the test page for case=%d(iCase).  All possible cases:
  }
  for(i=1; i<=8; i++){
  for(i=1; i<=10; i++){
    @ <a href='./test-warning?case=%d(i)'>[%d(i)]</a>
  }
  @ </p>
  @ <p><ol>
  @ <li value='1'> Call fossil_warning()
  if( iCase==1 ){
    fossil_warning("Test warning message from /test-warning");
3680
3681
3682
3683
3684
3685
3686
3687

3688
3689
3690
3691
3692

3693
3694
3695
3696



















3697
3698
3699
3700
3701
3807
3808
3809
3810
3811
3812
3813

3814
3815
3816
3817
3818

3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847







-
+




-
+




+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





  if( iCase==5 ){
    sigsegv_handler(0);
  }
  @ <li value='6'> call webpage_assert(0)
  if( iCase==6 ){
    webpage_assert( 5==7 );
  }
  @ <li value='7'> call webpage_error()"
  @ <li value='7'> call webpage_error()
  if( iCase==7 ){
    cgi_reset_content();
    webpage_error("Case 7 from /test-warning");
  }
  @ <li value='8'> simulated timeout"
  @ <li value='8'> simulated timeout
  if( iCase==8 ){
    fossil_set_timeout(1);
    cgi_reset_content();
    sqlite3_sleep(1100);
  }
  @ <li value='9'> simulated TH1 XSS vulnerability
  @ <li value='10'> simulated TH1 SQL-injection vulnerability
  if( iCase==9 || iCase==10 ){
    const char *zR;
    int n, rc;
    static const char *zTH1[] = {
       /* case 9 */  "html [taint {<b>XSS</b>}]",
       /* case 10 */ "query [taint {SELECT 'SQL-injection' AS msg}] {\n"
                     "  html \"<b>[htmlize $msg]</b>\"\n"
                     "}"
    };
    rc = Th_Eval(g.interp, 0, zTH1[iCase==10], -1);
    zR = Th_GetResult(g.interp, &n);
    if( rc==TH_OK ){
      @ <pre class="th1result">%h(zR)</pre>
    }else{
      @ <pre class="th1error">%h(zR)</pre>
    }
  }
  @ </ol>
  @ <p>End of test</p>
  style_finish_page();
}
Changes to src/main.mk.
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
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







+



















+







  $(SRCDIR)/loadctrl.c \
  $(SRCDIR)/login.c \
  $(SRCDIR)/lookslike.c \
  $(SRCDIR)/main.c \
  $(SRCDIR)/manifest.c \
  $(SRCDIR)/markdown.c \
  $(SRCDIR)/markdown_html.c \
  $(SRCDIR)/match.c \
  $(SRCDIR)/md5.c \
  $(SRCDIR)/merge.c \
  $(SRCDIR)/merge3.c \
  $(SRCDIR)/moderate.c \
  $(SRCDIR)/name.c \
  $(SRCDIR)/patch.c \
  $(SRCDIR)/path.c \
  $(SRCDIR)/piechart.c \
  $(SRCDIR)/pikchrshow.c \
  $(SRCDIR)/pivot.c \
  $(SRCDIR)/popen.c \
  $(SRCDIR)/pqueue.c \
  $(SRCDIR)/printf.c \
  $(SRCDIR)/publish.c \
  $(SRCDIR)/purge.c \
  $(SRCDIR)/rebuild.c \
  $(SRCDIR)/regexp.c \
  $(SRCDIR)/repolist.c \
  $(SRCDIR)/report.c \
  $(SRCDIR)/robot.c \
  $(SRCDIR)/rss.c \
  $(SRCDIR)/schema.c \
  $(SRCDIR)/search.c \
  $(SRCDIR)/security_audit.c \
  $(SRCDIR)/setup.c \
  $(SRCDIR)/setupuser.c \
  $(SRCDIR)/sha1.c \
158
159
160
161
162
163
164

165
166
167
168
169
170
171
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174







+







  $(SRCDIR)/vfile.c \
  $(SRCDIR)/wiki.c \
  $(SRCDIR)/wikiformat.c \
  $(SRCDIR)/winfile.c \
  $(SRCDIR)/winhttp.c \
  $(SRCDIR)/xfer.c \
  $(SRCDIR)/xfersetup.c \
  $(SRCDIR)/xsystem.c \
  $(SRCDIR)/zip.c

EXTRA_FILES = \
  $(SRCDIR)/../extsrc/pikchr-worker.js \
  $(SRCDIR)/../extsrc/pikchr.js \
  $(SRCDIR)/../extsrc/pikchr.wasm \
  $(SRCDIR)/../skins/ardoise/css.txt \
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
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







+













+







  $(SRCDIR)/fossil.numbered-lines.js \
  $(SRCDIR)/fossil.page.brlist.js \
  $(SRCDIR)/fossil.page.chat.js \
  $(SRCDIR)/fossil.page.fileedit.js \
  $(SRCDIR)/fossil.page.forumpost.js \
  $(SRCDIR)/fossil.page.pikchrshow.js \
  $(SRCDIR)/fossil.page.pikchrshowasm.js \
  $(SRCDIR)/fossil.page.ticket.js \
  $(SRCDIR)/fossil.page.whistory.js \
  $(SRCDIR)/fossil.page.wikiedit.js \
  $(SRCDIR)/fossil.pikchr.js \
  $(SRCDIR)/fossil.popupwidget.js \
  $(SRCDIR)/fossil.storage.js \
  $(SRCDIR)/fossil.tabs.js \
  $(SRCDIR)/fossil.wikiedit-wysiwyg.js \
  $(SRCDIR)/graph.js \
  $(SRCDIR)/hbmenu.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/login.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \
  $(SRCDIR)/merge.tcl \
  $(SRCDIR)/scroll.js \
  $(SRCDIR)/skin.js \
  $(SRCDIR)/sorttable.js \
  $(SRCDIR)/sounds/0.wav \
  $(SRCDIR)/sounds/1.wav \
  $(SRCDIR)/sounds/2.wav \
  $(SRCDIR)/sounds/3.wav \
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
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







+



















+







  $(OBJDIR)/loadctrl_.c \
  $(OBJDIR)/login_.c \
  $(OBJDIR)/lookslike_.c \
  $(OBJDIR)/main_.c \
  $(OBJDIR)/manifest_.c \
  $(OBJDIR)/markdown_.c \
  $(OBJDIR)/markdown_html_.c \
  $(OBJDIR)/match_.c \
  $(OBJDIR)/md5_.c \
  $(OBJDIR)/merge_.c \
  $(OBJDIR)/merge3_.c \
  $(OBJDIR)/moderate_.c \
  $(OBJDIR)/name_.c \
  $(OBJDIR)/patch_.c \
  $(OBJDIR)/path_.c \
  $(OBJDIR)/piechart_.c \
  $(OBJDIR)/pikchrshow_.c \
  $(OBJDIR)/pivot_.c \
  $(OBJDIR)/popen_.c \
  $(OBJDIR)/pqueue_.c \
  $(OBJDIR)/printf_.c \
  $(OBJDIR)/publish_.c \
  $(OBJDIR)/purge_.c \
  $(OBJDIR)/rebuild_.c \
  $(OBJDIR)/regexp_.c \
  $(OBJDIR)/repolist_.c \
  $(OBJDIR)/report_.c \
  $(OBJDIR)/robot_.c \
  $(OBJDIR)/rss_.c \
  $(OBJDIR)/schema_.c \
  $(OBJDIR)/search_.c \
  $(OBJDIR)/security_audit_.c \
  $(OBJDIR)/setup_.c \
  $(OBJDIR)/setupuser_.c \
  $(OBJDIR)/sha1_.c \
422
423
424
425
426
427
428

429
430
431
432
433
434
435
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443







+







  $(OBJDIR)/vfile_.c \
  $(OBJDIR)/wiki_.c \
  $(OBJDIR)/wikiformat_.c \
  $(OBJDIR)/winfile_.c \
  $(OBJDIR)/winhttp_.c \
  $(OBJDIR)/xfer_.c \
  $(OBJDIR)/xfersetup_.c \
  $(OBJDIR)/xsystem_.c \
  $(OBJDIR)/zip_.c

OBJ = \
 $(OBJDIR)/add.o \
 $(OBJDIR)/ajax.o \
 $(OBJDIR)/alerts.o \
 $(OBJDIR)/allrepo.o \
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
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







+



















+







 $(OBJDIR)/loadctrl.o \
 $(OBJDIR)/login.o \
 $(OBJDIR)/lookslike.o \
 $(OBJDIR)/main.o \
 $(OBJDIR)/manifest.o \
 $(OBJDIR)/markdown.o \
 $(OBJDIR)/markdown_html.o \
 $(OBJDIR)/match.o \
 $(OBJDIR)/md5.o \
 $(OBJDIR)/merge.o \
 $(OBJDIR)/merge3.o \
 $(OBJDIR)/moderate.o \
 $(OBJDIR)/name.o \
 $(OBJDIR)/patch.o \
 $(OBJDIR)/path.o \
 $(OBJDIR)/piechart.o \
 $(OBJDIR)/pikchrshow.o \
 $(OBJDIR)/pivot.o \
 $(OBJDIR)/popen.o \
 $(OBJDIR)/pqueue.o \
 $(OBJDIR)/printf.o \
 $(OBJDIR)/publish.o \
 $(OBJDIR)/purge.o \
 $(OBJDIR)/rebuild.o \
 $(OBJDIR)/regexp.o \
 $(OBJDIR)/repolist.o \
 $(OBJDIR)/report.o \
 $(OBJDIR)/robot.o \
 $(OBJDIR)/rss.o \
 $(OBJDIR)/schema.o \
 $(OBJDIR)/search.o \
 $(OBJDIR)/security_audit.o \
 $(OBJDIR)/setup.o \
 $(OBJDIR)/setupuser.o \
 $(OBJDIR)/sha1.o \
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
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







+

-
+








-
-
-

+



+



+



+



+



+















-
+







 $(OBJDIR)/vfile.o \
 $(OBJDIR)/wiki.o \
 $(OBJDIR)/wikiformat.o \
 $(OBJDIR)/winfile.o \
 $(OBJDIR)/winhttp.o \
 $(OBJDIR)/xfer.o \
 $(OBJDIR)/xfersetup.o \
 $(OBJDIR)/xsystem.o \
 $(OBJDIR)/zip.o
all:	$(OBJDIR) $(APPNAME)
all:	$(APPNAME)

install:	all
	mkdir -p $(INSTALLDIR)
	cp $(APPNAME) $(INSTALLDIR)

codecheck:	$(TRANS_SRC) $(OBJDIR)/codecheck1
	$(OBJDIR)/codecheck1 $(TRANS_SRC)

$(OBJDIR):
	-mkdir $(OBJDIR)

$(OBJDIR)/translate:	$(SRCDIR_tools)/translate.c
	-mkdir -p $(OBJDIR)
	$(XBCC) -o $(OBJDIR)/translate $(SRCDIR_tools)/translate.c

$(OBJDIR)/makeheaders:	$(SRCDIR_tools)/makeheaders.c
	-mkdir -p $(OBJDIR)
	$(XBCC) -o $(OBJDIR)/makeheaders $(SRCDIR_tools)/makeheaders.c

$(OBJDIR)/mkindex:	$(SRCDIR_tools)/mkindex.c
	-mkdir -p $(OBJDIR)
	$(XBCC) -o $(OBJDIR)/mkindex $(SRCDIR_tools)/mkindex.c

$(OBJDIR)/mkbuiltin:	$(SRCDIR_tools)/mkbuiltin.c
	-mkdir -p $(OBJDIR)
	$(XBCC) -o $(OBJDIR)/mkbuiltin $(SRCDIR_tools)/mkbuiltin.c

$(OBJDIR)/mkversion:	$(SRCDIR_tools)/mkversion.c
	-mkdir -p $(OBJDIR)
	$(XBCC) -o $(OBJDIR)/mkversion $(SRCDIR_tools)/mkversion.c

$(OBJDIR)/codecheck1:	$(SRCDIR_tools)/codecheck1.c
	-mkdir -p $(OBJDIR)
	$(XBCC) -o $(OBJDIR)/codecheck1 $(SRCDIR_tools)/codecheck1.c

# Run the test suite.
# Other flags that can be included in TESTFLAGS are:
#
#  -halt     Stop testing after the first failed test
#  -keep     Keep the temporary workspace for debugging
#  -prot     Write a detailed log of the tests to the file ./prot
#  -verbose  Include even more details in the output
#  -quiet    Hide most output from the terminal
#  -strict   Treat known bugs as failures
#
# TESTFLAGS can also include names of specific test files to limit
# the run to just those test cases.
#
test:	$(OBJDIR) $(APPNAME)
test:	$(APPNAME)
	$(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(TESTFLAGS)

$(OBJDIR)/VERSION.h:	$(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion $(OBJDIR)/phony.h
	$(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid \
		$(SRCDIR)/../manifest \
		$(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h

641
642
643
644
645
646
647

648
649
650
651




652
653
654
655
656
657
658
655
656
657
658
659
660
661
662
663
664


665
666
667
668
669
670
671
672
673
674
675







+


-
-
+
+
+
+







                 -DSQLITE_OMIT_DEPRECATED \
                 -DSQLITE_OMIT_PROGRESS_CALLBACK \
                 -DSQLITE_OMIT_SHARED_CACHE \
                 -DSQLITE_OMIT_LOAD_EXTENSION \
                 -DSQLITE_MAX_EXPR_DEPTH=0 \
                 -DSQLITE_ENABLE_LOCKING_STYLE=0 \
                 -DSQLITE_DEFAULT_FILE_FORMAT=4 \
                 -DSQLITE_ENABLE_DBSTAT_VTAB \
                 -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
                 -DSQLITE_ENABLE_FTS4 \
                 -DSQLITE_ENABLE_DBSTAT_VTAB \
                 -DSQLITE_ENABLE_FTS5 \
                 -DSQLITE_ENABLE_FTS5 \
                 -DSQLITE_ENABLE_MATH_FUNCTIONS \
                 -DSQLITE_ENABLE_PERCENTILE \
                 -DSQLITE_ENABLE_SETLK_TIMEOUT \
                 -DSQLITE_ENABLE_STMTVTAB \
                 -DSQLITE_HAVE_ZLIB \
                 -DSQLITE_ENABLE_DBPAGE_VTAB \
                 -DSQLITE_TRUSTED_SCHEMA=0 \
                 -DHAVE_USLEEP

# Setup the options used to compile the included SQLite shell.
666
667
668
669
670
671
672

673
674
675
676




677
678
679
680
681
682
683
683
684
685
686
687
688
689
690
691
692


693
694
695
696
697
698
699
700
701
702
703







+


-
-
+
+
+
+







                -DSQLITE_OMIT_DEPRECATED \
                -DSQLITE_OMIT_PROGRESS_CALLBACK \
                -DSQLITE_OMIT_SHARED_CACHE \
                -DSQLITE_OMIT_LOAD_EXTENSION \
                -DSQLITE_MAX_EXPR_DEPTH=0 \
                -DSQLITE_ENABLE_LOCKING_STYLE=0 \
                -DSQLITE_DEFAULT_FILE_FORMAT=4 \
                -DSQLITE_ENABLE_DBSTAT_VTAB \
                -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
                -DSQLITE_ENABLE_FTS4 \
                -DSQLITE_ENABLE_DBSTAT_VTAB \
                -DSQLITE_ENABLE_FTS5 \
                -DSQLITE_ENABLE_FTS5 \
                -DSQLITE_ENABLE_MATH_FUNCTIONS \
                -DSQLITE_ENABLE_PERCENTILE \
                -DSQLITE_ENABLE_SETLK_TIMEOUT \
                -DSQLITE_ENABLE_STMTVTAB \
                -DSQLITE_HAVE_ZLIB \
                -DSQLITE_ENABLE_DBPAGE_VTAB \
                -DSQLITE_TRUSTED_SCHEMA=0 \
                -DHAVE_USLEEP \
                -Dmain=sqlite3_shell \
                -DSQLITE_SHELL_IS_UTF8=1 \
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
877
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







+



















+







	$(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)/match_.c:$(OBJDIR)/match.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)/patch_.c:$(OBJDIR)/patch.h \
	$(OBJDIR)/path_.c:$(OBJDIR)/path.h \
	$(OBJDIR)/piechart_.c:$(OBJDIR)/piechart.h \
	$(OBJDIR)/pikchrshow_.c:$(OBJDIR)/pikchrshow.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)/publish_.c:$(OBJDIR)/publish.h \
	$(OBJDIR)/purge_.c:$(OBJDIR)/purge.h \
	$(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h \
	$(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \
	$(OBJDIR)/repolist_.c:$(OBJDIR)/repolist.h \
	$(OBJDIR)/report_.c:$(OBJDIR)/report.h \
	$(OBJDIR)/robot_.c:$(OBJDIR)/robot.h \
	$(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \
	$(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \
	$(OBJDIR)/search_.c:$(OBJDIR)/search.h \
	$(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \
	$(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \
	$(OBJDIR)/setupuser_.c:$(OBJDIR)/setupuser.h \
	$(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h \
906
907
908
909
910
911
912

913
914
915
916
917
918
919
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942







+







	$(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)/xfer_.c:$(OBJDIR)/xfer.h \
	$(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.h \
	$(OBJDIR)/xsystem_.c:$(OBJDIR)/xsystem.h \
	$(OBJDIR)/zip_.c:$(OBJDIR)/zip.h \
	$(SRCDIR_extsrc)/pikchr.c:$(OBJDIR)/pikchr.h \
	$(SRCDIR_extsrc)/sqlite3.h \
	$(SRCDIR)/th.h \
	$(OBJDIR)/VERSION.h 
	touch $(OBJDIR)/headers
$(OBJDIR)/headers: Makefile
1594
1595
1596
1597
1598
1599
1600








1601
1602
1603
1604
1605
1606
1607
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638







+
+
+
+
+
+
+
+







$(OBJDIR)/markdown_html_.c:	$(SRCDIR)/markdown_html.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/markdown_html.c >$@

$(OBJDIR)/markdown_html.o:	$(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c

$(OBJDIR)/markdown_html.h:	$(OBJDIR)/headers

$(OBJDIR)/match_.c:	$(SRCDIR)/match.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/match.c >$@

$(OBJDIR)/match.o:	$(OBJDIR)/match_.c $(OBJDIR)/match.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/match.o -c $(OBJDIR)/match_.c

$(OBJDIR)/match.h:	$(OBJDIR)/headers

$(OBJDIR)/md5_.c:	$(SRCDIR)/md5.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/md5.c >$@

$(OBJDIR)/md5.o:	$(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/md5.o -c $(OBJDIR)/md5_.c

1746
1747
1748
1749
1750
1751
1752








1753
1754
1755
1756
1757
1758
1759
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798







+
+
+
+
+
+
+
+







$(OBJDIR)/report_.c:	$(SRCDIR)/report.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/report.c >$@

$(OBJDIR)/report.o:	$(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/report.o -c $(OBJDIR)/report_.c

$(OBJDIR)/report.h:	$(OBJDIR)/headers

$(OBJDIR)/robot_.c:	$(SRCDIR)/robot.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/robot.c >$@

$(OBJDIR)/robot.o:	$(OBJDIR)/robot_.c $(OBJDIR)/robot.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/robot.o -c $(OBJDIR)/robot_.c

$(OBJDIR)/robot.h:	$(OBJDIR)/headers

$(OBJDIR)/rss_.c:	$(SRCDIR)/rss.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/rss.c >$@

$(OBJDIR)/rss.o:	$(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/rss.o -c $(OBJDIR)/rss_.c

2082
2083
2084
2085
2086
2087
2088








2089
2090
2091
2092
2093
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
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
2189
2190
2191







+
+
+
+
+
+
+
+



















+
+



+
+



+
+



-
+


-
+


-
+

-
-
+
+




+







$(OBJDIR)/xfersetup_.c:	$(SRCDIR)/xfersetup.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/xfersetup.c >$@

$(OBJDIR)/xfersetup.o:	$(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/xfersetup.o -c $(OBJDIR)/xfersetup_.c

$(OBJDIR)/xfersetup.h:	$(OBJDIR)/headers

$(OBJDIR)/xsystem_.c:	$(SRCDIR)/xsystem.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/xsystem.c >$@

$(OBJDIR)/xsystem.o:	$(OBJDIR)/xsystem_.c $(OBJDIR)/xsystem.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/xsystem.o -c $(OBJDIR)/xsystem_.c

$(OBJDIR)/xsystem.h:	$(OBJDIR)/headers

$(OBJDIR)/zip_.c:	$(SRCDIR)/zip.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/zip.c >$@

$(OBJDIR)/zip.o:	$(OBJDIR)/zip_.c $(OBJDIR)/zip.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/zip.o -c $(OBJDIR)/zip_.c

$(OBJDIR)/zip.h:	$(OBJDIR)/headers

$(SQLITE3_OBJ):	$(SQLITE3_SRC)
	$(XTCC) $(SQLITE_OPTIONS) $(SQLITE_CFLAGS) $(SEE_FLAGS) \
		-c $(SQLITE3_SRC) -o $@
$(OBJDIR)/shell.o:	$(SQLITE3_SHELL_SRC) $(SRCDIR_extsrc)/sqlite3.h
	$(XTCC) $(SHELL_OPTIONS) $(SHELL_CFLAGS) $(SEE_FLAGS) $(LINENOISE_DEF.$(USE_LINENOISE)) -c $(SQLITE3_SHELL_SRC) -o $@

$(OBJDIR)/linenoise.o:	$(SRCDIR_extsrc)/linenoise.c $(SRCDIR_extsrc)/linenoise.h
	$(XTCC) -c $(SRCDIR_extsrc)/linenoise.c -o $@

$(OBJDIR)/th.o:	$(SRCDIR)/th.c
	-mkdir -p $(OBJDIR)

	$(XTCC) -c $(SRCDIR)/th.c -o $@

$(OBJDIR)/th_lang.o:	$(SRCDIR)/th_lang.c
	-mkdir -p $(OBJDIR)

	$(XTCC) -c $(SRCDIR)/th_lang.c -o $@

$(OBJDIR)/th_tcl.o:	$(SRCDIR)/th_tcl.c
	-mkdir -p $(OBJDIR)

	$(XTCC) -c $(SRCDIR)/th_tcl.c -o $@


$(OBJDIR)/pikchr.o:	$(SRCDIR_extsrc)/pikchr.c
$(OBJDIR)/pikchr.o:	$(SRCDIR_extsrc)/pikchr.c $(OBJDIR)/mkversion
	$(XTCC) $(PIKCHR_OPTIONS) -c $(SRCDIR_extsrc)/pikchr.c -o $@

$(OBJDIR)/cson_amalgamation.o: $(SRCDIR_extsrc)/cson_amalgamation.c
$(OBJDIR)/cson_amalgamation.o: $(SRCDIR_extsrc)/cson_amalgamation.c $(OBJDIR)/mkversion
	$(XTCC) -c $(SRCDIR_extsrc)/cson_amalgamation.c -o $@

$(SRCDIR_extsrc)/pikchr.js: $(SRCDIR_extsrc)/pikchr.c
$(SRCDIR_extsrc)/pikchr.js: $(SRCDIR_extsrc)/pikchr.c $(MAKEFILE_LIST)
	$(EMCC_WRAPPER) -o $@ $(EMCC_OPT) --no-entry \
        -sEXPORTED_RUNTIME_METHODS=cwrap,setValue,getValue,stackSave,stackRestore \
        -sEXPORTED_FUNCTIONS=_pikchr $(SRCDIR_extsrc)/pikchr.c \
        -sEXPORTED_RUNTIME_METHODS=cwrap,ccall,setValue,getValue,stackSave,stackAlloc,stackRestore \
        -sEXPORTED_FUNCTIONS=_pikchr,_pikchr_version $(SRCDIR_extsrc)/pikchr.c \
        -sENVIRONMENT=web \
        -sMODULARIZE \
        -sEXPORT_NAME=initPikchrModule \
        --minify 0
	$(TCLSH) $(TOPDIR)/tools/randomize-js-names.tcl $(SRCDIR_extsrc)
	@chmod -x $(SRCDIR_extsrc)/pikchr.wasm
wasm: $(SRCDIR_extsrc)/pikchr.js

#
# compile_commands.json support...
#
# We have to avoid applying compile_commands support to the in-tree
Changes to src/manifest.c.
316
317
318
319
320
321
322
323
324




325
326
327
328
329

330


331
332
333
334
335
336
337
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







-
-
+
+
+
+





+
-
+
+







/*
** Remove the PGP signature from the artifact, if there is one.
*/
static void remove_pgp_signature(const char **pz, int *pn){
  const char *z = *pz;
  int n = *pn;
  int i;
  if( strncmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)!=0 ) return;
  for(i=34; i<n && !after_blank_line(z+i); i++){}
  if( strncmp(z, "-----BEGIN PGP SIGNED MESSAGE-----", 34)==0 ) i = 34;
  else if( strncmp(z, "-----BEGIN SSH SIGNED MESSAGE-----", 34)==0 ) i = 34;
  else return;
  for(; i<n && !after_blank_line(z+i); i++){}
  if( i>=n ) return;
  z += i;
  n -= i;
  *pz = z;
  for(i=n-1; i>=0; i--){
    if( z[i]=='\n' &&
    if( z[i]=='\n' && strncmp(&z[i],"\n-----BEGIN PGP SIGNATURE-", 25)==0 ){
        (strncmp(&z[i],"\n-----BEGIN PGP SIGNATURE-----", 29)==0
         || strncmp(&z[i],"\n-----BEGIN SSH SIGNATURE-----", 29)==0 )){
      n = i+1;
      break;
    }
  }
  *pn = n;
  return;
}
940
941
942
943
944
945
946
947

948
949
950
951
952
953
954
955
944
945
946
947
948
949
950

951

952
953
954
955
956
957
958







-
+
-








      /*
      **    T (+|*|-)<tagname> <uuid> ?<value>?
      **
      ** 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
      ** anti-tag that undoes a prior "+" or blocks propagation of a "*".
      ** 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.
      */
2728
2729
2730
2731
2732
2733
2734
2735

2736
2737
2738
2739
2740
2741
2742
2731
2732
2733
2734
2735
2736
2737

2738
2739
2740
2741
2742
2743
2744
2745







-
+







          zUuid = fossil_strdup(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].",
           " Move to branch [/timeline?r=%t&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;
2905
2906
2907
2908
2909
2910
2911







































































































































































































































































































2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
  Blob content;
  db_find_and_open_repository(0, 0);
  if( g.argc!=3 ) usage("RECORDID");
  rid = name_to_rid(g.argv[2]);
  content_get(rid, &content);
  manifest_crosslink(rid, &content, MC_NONE);
}

/*
** For a given CATYPE_... value, returns a human-friendly name, or
** NULL if typeId is unknown or is CFTYPE_ANY. The names returned by
** this function are geared towards use with artifact_to_json(), and
** may differ from some historical uses. e.g. CFTYPE_CONTROL artifacts
** are called "tag" artifacts by this function.
*/
const char * artifact_type_to_name(int typeId){
  switch(typeId){
    case CFTYPE_MANIFEST: return "checkin";
    case CFTYPE_CLUSTER: return "cluster";
    case CFTYPE_CONTROL: return "tag";
    case CFTYPE_WIKI: return "wiki";
    case CFTYPE_TICKET: return "ticket";
    case CFTYPE_ATTACHMENT: return "attachment";
    case CFTYPE_EVENT: return "technote";
    case CFTYPE_FORUM: return "forumpost";
  }
  return NULL;
}

/*
** Creates a JSON representation of p, appending it to b.
**
** b is not cleared before rendering, so the caller needs to do that
** if it's important for their use case.
**
** Pedantic note: this routine traverses p->aFile directly, rather
** than using manifest_file_next(), so that delta manifests are
** rendered as-is instead of containing their derived F-cards. If that
** policy is ever changed, p will need to be non-const.
*/
void artifact_to_json(Manifest const *p, Blob *b){
  int i;

  blob_append_literal(b, "{");
  blob_appendf(b, "\"uuid\":\"%z\"", rid_to_uuid(p->rid));
  /*blob_appendf(b, ", \"rid\": %d", p->rid); not portable across repos*/
  blob_appendf(b, ",\"type\":%!j", artifact_type_to_name(p->type));
#define ISA(TYPE) if( p->type==TYPE )
#define CARD_LETTER(LETTER) \
  blob_append_literal(b, ",\"" #LETTER "\":")
#define CARD_STR(LETTER, VAL) \
  assert( VAL ); CARD_LETTER(LETTER); blob_appendf(b, "%!j", VAL)
#define CARD_STR2(LETTER, VAL) \
  if( VAL ) { CARD_STR(LETTER, VAL); } (void)0
#define STR_OR_NULL(VAL)                 \
  if( VAL ) blob_appendf(b, "%!j", VAL); \
  else blob_append(b, "null", 4)
#define KVP_STR(ADDCOMMA, KEY,VAL)  \
  if(ADDCOMMA) blob_append_char(b, ','); \
  blob_appendf(b, "%!j:", #KEY);   \
  STR_OR_NULL(VAL)

  ISA( CFTYPE_ATTACHMENT ){
    CARD_LETTER(A);
    blob_append_char(b, '{');
    KVP_STR(0, filename, p->zAttachName);
    KVP_STR(1, target, p->zAttachTarget);
    KVP_STR(1, source, p->zAttachSrc);
    blob_append_char(b, '}');
  }
  CARD_STR2(B, p->zBaseline);
  CARD_STR2(C, p->zComment);
  CARD_LETTER(D); blob_appendf(b, "%f", p->rDate);
  ISA( CFTYPE_EVENT ){
    blob_appendf(b, ", \"E\":{\"time\":%f,\"id\":%!j}",
                 p->rEventDate, p->zEventId);
  }
  ISA( CFTYPE_MANIFEST ){
    CARD_LETTER(F);
    blob_append_char(b, '[');
    for( i = 0; i < p->nFile; ++i ){
      ManifestFile const * const pF = &p->aFile[i];
      if( i>0 ) blob_append_char(b, ',');
      blob_append_char(b, '{');
      KVP_STR(0, name, pF->zName);
      KVP_STR(1, uuid, pF->zUuid);
      KVP_STR(1, perm, pF->zPerm);
      KVP_STR(1, rename, pF->zPrior);
      blob_append_char(b, '}');
    }
    /* Special case: model check-ins with no F-card as having an empty
    ** array, rather than no F-cards, to hypothetically simplify
    ** handling in JSON queries. */
    blob_append_char(b, ']');
  }
  CARD_STR2(G, p->zThreadRoot);
  ISA( CFTYPE_FORUM ){
    CARD_LETTER(H);
    STR_OR_NULL( (p->zThreadTitle && *p->zThreadTitle) ? p->zThreadTitle : NULL);
    CARD_STR2(I, p->zInReplyTo);
  }
  if( p->nField ){
    CARD_LETTER(J);
    blob_append_char(b, '[');
    for( i = 0; i < p->nField; ++i ){
      const char * zName = p->aField[i].zName;
      if( i>0 ) blob_append_char(b, ',');
      blob_append_char(b, '{');
      KVP_STR(0, name, '+'==*zName ? &zName[1] : zName);
      KVP_STR(1, value, p->aField[i].zValue);
      blob_appendf(b, ",\"append\":%s", '+'==*zName ? "true" : "false");
      blob_append_char(b, '}');
    }
    blob_append_char(b, ']');
  }
  CARD_STR2(K, p->zTicketUuid);
  CARD_STR2(L, p->zWikiTitle);
  ISA( CFTYPE_CLUSTER ){
    CARD_LETTER(M);
    blob_append_char(b, '[');
    for( i = 0; i < p->nCChild; ++i ){
      if( i>0 ) blob_append_char(b, ',');
      blob_appendf(b, "%!j", p->azCChild[i]);
    }
    blob_append_char(b, ']');
  }
  CARD_STR2(N, p->zMimetype);
  ISA( CFTYPE_MANIFEST || p->nParent>0 ){
    CARD_LETTER(P);
    blob_append_char(b, '[');
    for( i = 0; i < p->nParent; ++i ){
      if( i>0 ) blob_append_char(b, ',');
      blob_appendf(b, "%!j", p->azParent[i]);
    }
    /* Special case: model check-ins with no P-card as having an empty
    ** array, as per F-cards. */
    blob_append_char(b, ']');
  }
  if( p->nCherrypick ){
    CARD_LETTER(Q);
    blob_append_char(b, '[');
    for( i = 0; i < p->nCherrypick; ++i ){
      if( i>0 ) blob_append_char(b, ',');
      blob_append_char(b, '{');
      blob_appendf(b, "\"type\":\"%c\"", p->aCherrypick[i].zCPTarget[0]);
      KVP_STR(1, target, &p->aCherrypick[i].zCPTarget[1]);
      KVP_STR(1, base, p->aCherrypick[i].zCPBase);
      blob_append_char(b, '}');
    }
    blob_append_char(b, ']');
  }
  CARD_STR2(R, p->zRepoCksum);
  if( p->nTag ){
    CARD_LETTER(T);
    blob_append_char(b, '[');
    for( i = 0; i < p->nTag; ++i ){
      const char *zName = p->aTag[i].zName;
      if( i>0 ) blob_append_char(b, ',');
      blob_append_char(b, '{');
      blob_appendf(b, "\"type\":\"%c\"", *zName);
      KVP_STR(1, name, &zName[1]);
      KVP_STR(1, target, p->aTag[i].zUuid ? p->aTag[i].zUuid : "*")
        /* We could arguably resolve the "*" as null or p's uuid. */;
      KVP_STR(1, value, p->aTag[i].zValue);
      blob_append_char(b, '}');
    }
    blob_append_char(b, ']');
  }
  CARD_STR2(U, p->zUser);
  if( p->zWiki || CFTYPE_WIKI==p->type || CFTYPE_FORUM==p->type
      || CFTYPE_EVENT==p->type ){
    CARD_LETTER(W);
    STR_OR_NULL((p->zWiki && *p->zWiki) ? p->zWiki : NULL);
  }
  blob_append_literal(b, "}");
#undef CARD_FMT
#undef CARD_LETTER
#undef CARD_STR
#undef CARD_STR2
#undef ISA
#undef KVP_STR
#undef STR_OR_NULL
}

/*
** Convenience wrapper around artifact_to_json() which expects rid to
** be the blob.rid of any artifact type. If it can load a Manifest
** with that rid, it returns rid, else it returns 0.
*/
int artifact_to_json_by_rid(int rid, Blob *pOut){
  Manifest * const p = manifest_get(rid, CFTYPE_ANY, 0);
  if( p ){
    artifact_to_json(p, pOut);
    manifest_destroy(p);
  }else{
    rid = 0;
  }
  return rid;
}

/*
** Convenience wrapper around artifact_to_json() which accepts any
** artifact name which is legal for symbolic_name_to_rid(). On success
** it returns the rid of the artifact. Returns 0 if no such artifact
** exists and a negative value if the name is ambiguous.
**
** pOut is not cleared before rendering, so the caller needs to do
** that if it's important for their use case.
*/
int artifact_to_json_by_name(const char *zName, Blob *pOut){
  const int rid = symbolic_name_to_rid(zName, 0);
  return rid>0
    ? artifact_to_json_by_rid(rid, pOut)
    : rid;
}

/*
** SQLite UDF for artifact_to_json(). Its single argument should be
** either an INTEGER (blob.rid value) or a TEXT symbolic artifact
** name, as per symbolic_name_to_rid(). If an artifact is found then
** the result of the UDF is that JSON as a string, else it evaluates
** to NULL.
*/
void artifact_to_json_sql_func(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  int rid = 0;
  Blob b = empty_blob;

  if(1 != argc){
    goto error_usage;
  }
  switch( sqlite3_value_type(argv[0]) ){
    case SQLITE_INTEGER:
      rid = artifact_to_json_by_rid(sqlite3_value_int(argv[0]), &b);
      break;
    case SQLITE_TEXT:{
      const char * z = (const char *)sqlite3_value_text(argv[0]);
      if( z ){
        rid = artifact_to_json_by_name(z, &b);
      }
      break;
    }
    default:
      goto error_usage;
  }
  if( rid>0 ){
    sqlite3_result_text(context, blob_str(&b), blob_size(&b),
                        SQLITE_TRANSIENT);
    blob_reset(&b);
  }else{
    /* We should arguably error out if rid<0 (ambiguous name) */
    sqlite3_result_null(context);
  }
  return;
error_usage:
  sqlite3_result_error(context, "Expecting one argument: blob.rid or "
                       "artifact symbolic name", -1);
}



/*
** COMMAND: test-artifact-to-json
**
** Usage:  %fossil test-artifact-to-json ?-pretty|-p? symbolic-name [...names]
**
** Tests the artifact_to_json() and artifact_to_json_by_name() APIs.
*/
void test_manifest_to_json(void){
  int i;
  Blob b = empty_blob;
  Stmt q;
  const int bPretty = find_option("pretty","p",0)!=0;
  int nErr = 0;

  db_find_and_open_repository(0,0);
  db_prepare(&q, "select json_pretty(:json)");
  for( i=2; i<g.argc; ++i ){
    char const *zName = g.argv[i];
    const int rc = artifact_to_json_by_name(zName, &b);
    if( rc<=0 ){
      ++nErr;
      fossil_warning("Error reading artifact %Q", zName);
      continue;
    }else if( bPretty ){
      db_bind_blob(&q, ":json", &b);
      b.nUsed = 0;
      db_step(&q);
      db_column_blob(&q, 0, &b);
      db_reset(&q);
    }
    fossil_print("%b\n", &b);
    blob_reset(&b);
  }
  db_finalize(&q);
  if( nErr ){
    fossil_warning("Error count: %d", nErr);
  }
}
Changes to src/markdown.c.
528
529
530
531
532
533
534
535
536


537
538
539
540
541
542
543
528
529
530
531
532
533
534


535
536
537
538
539
540
541
542
543







-
-
+
+







      end = i;
    }
  }
}

/*
** data[*pI] should be a "`" character that introduces a code-span.
** The code-span boundry mark can be any number of one or more "`"
** characters.  We do not know the size of the boundry marker, only
** The code-span boundary mark can be any number of one or more "`"
** characters.  We do not know the size of the boundary marker, only
** that there is at least one "`" at data[*pI].
**
** This routine increases *pI to move it past the code-span, including
** the closing boundary mark.  Or, if the code-span is unterminated,
** this routine moves *pI past the opening boundary mark only.
*/
static void skip_codespan(const char *data, size_t size, size_t *pI){
632
633
634
635
636
637
638
639

640
641
642
643
644
645
646
632
633
634
635
636
637
638

639
640
641
642
643
644
645
646







-
+







**    punct   space     (*            no             yes
**    punct   punct     (*)          yes             yes
**    punct   alnum     (*x          yes              no
**    alnum   space     a*            no             yes
**    alnum   punct     a*(           no             yes
**    alnum   alnum     a*x          yes             yes
**
** The following routines determine whether a delimitor is left
** The following routines determine whether a delimiter is left
** or right flanking.
*/
static int left_flanking(char before, char after){
  if( fossil_isspace(after) ) return 0;
  if( fossil_isalnum(after) ) return 1;
  if( fossil_isalnum(before) ) return 0;
  return 1;
2531
2532
2533
2534
2535
2536
2537
2538

2539
2540
2541
2542
2543
2544
2545
2531
2532
2533
2534
2535
2536
2537

2538
2539
2540
2541
2542
2543
2544
2545







-
+







  size_t i, id_offset, id_end, upc_offset, upc_size;
  struct footnote fn = FOOTNOTE_INITIALIZER;

  /* failfast if data is too short */
  if( beg+5>=end ) return 0;
  i = beg;

  /* footnote definition must start at the begining of a line */
  /* footnote definition must start at the beginning of a line */
  if( data[i]!='[' ) return 0;
  i++;
  if( data[i]!='^' ) return 0;
  id_offset = ++i;

  /* id part: anything but a newline between brackets */
  while( i<end && data[i]!=']' && data[i]!='\n' && data[i]!='\r' ){ i++; }
Changes to src/markdown.md.
186
187
188
189
190
191
192
193

194
195
196
197
198
199
200
186
187
188
189
190
191
192

193
194
195
196
197
198
199
200







-
+







> Both a footnote's text and a fragment to which a footnote applies
> are subject to further interpretation as Markdown sources.

## Miscellaneous ##

> *   In-line images are made using **\!\[alt-text\]\(image-URL\)**.
> *   Use HTML for advanced formatting such as forms, noting that certain
>     tags are [disallowed in some contexts](/help?cmd=safe-html).
>     tags are [disallowed in some contexts](/help/safe-html).
> *   **\<!--** HTML-style comments **-->** are supported.
> *   Escape special characters (ex: **\[** **\(** **\|** **\***)
>     using backslash (ex: **\\\[** **\\\(** **\\\|** **\\\***).
> *   A line consisting of **---**, **\*\*\***, or **\_\_\_** is a horizontal
>     rule.  Spaces and extra **-**/**\***/**_** are allowed.
> *   Paragraphs enclosed in **\<html\>...\</html\>** is passed through unchanged.
> *   See [daringfireball.net][] for additional information.
Changes to src/markdown_html.c.
214
215
216
217
218
219
220


221
222
223
224
225
226
227





































228


229
230

231
232
233
234
235
236
237
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







+
+







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+


+







static void html_header(
  struct Blob *ob,
  struct Blob *text,
  int level,
  void *opaque
){
  struct Blob *title = ((MarkdownToHtml*)opaque)->output_title;
  char *z = 0;
  int i,j;
  /* The first header at the beginning of a text is considered as
   * a title and not output. */
  if( blob_size(ob)<=PROLOG_SIZE && title!=0 && blob_size(title)==0 ){
    blob_appendb(title, text);
    return;
  }
  INTER_BLOCK(ob);
  z = fossil_strdup(blob_buffer(text));
  if( z==0 ){
    j = 0;
  }else{
    /*
    ** The GitHub "slugify" algorithm converts the text of a markdown header
    ** into a ID for that header.  The algorithm is:
    **
    **   1.  ASCII alphanumerics -> convert to lower case
    **   2.  Spaces, hyphens, underscores -> convert to '-'
    **   3.  Non-ASCII -> preserve as-is
    **   4.  Other punctuation -> remove
    **   5.  Multiple consecutive dashes -> collapse to one
    **   6.  Leading and trailing dashes -> remove
    **   7.  Markup <...> and &...; -> remove
    **
    ** This implementation does the conversion in-place.
    */
    for(i=j=0; z[i]; i++){
      if( fossil_isalnum(z[i]) ){
        z[j++] = fossil_tolower(z[i]);
      }else if( fossil_isspace(z[i]) || z[i]=='-' || z[i]=='_' ){
        if( j>0 && z[j-1]!='-' ) z[j++] = '-';
      }else if( z[i]=='<' ){
        do{ i++; }while( z[i]!=0 && z[i]!='>' );
      }else if( z[i]=='&' ){
        do{ i++; }while( z[i]!=0 && z[i]!=';' );
      }else if( (z[i]&0x80)!=0 ){
        z[j++] = z[i];
      }
    }
    if( j>0 && z[j-1]=='-' ) j--;
    z[j] = 0;
  }
  if( j>0 ){
    blob_appendf(ob, "<h%d id=\"%s\">", level, z);
  }else{
  blob_appendf(ob, "<h%d>", level);
    blob_appendf(ob, "<h%d>", level);
  }
  blob_appendb(ob, text);
  blob_appendf(ob, "</h%d>", level);
  fossil_free(z);
}

static void html_hrule(struct Blob *ob, void *opaque){
  INTER_BLOCK(ob);
  blob_append_literal(ob, "<hr>\n");
}

276
277
278
279
280
281
282
283

284
285
286
287
288
289
290
317
318
319
320
321
322
323

324
325
326
327
328
329
330
331







-
+







static void html_table(
  struct Blob *ob,
  struct Blob *head_row,
  struct Blob *rows,
  void *opaque
){
  INTER_BLOCK(ob);
  blob_append_literal(ob, "<table>\n");
  blob_append_literal(ob, "<table class='md-table'>\n");
  if( head_row && blob_size(head_row)>0 ){
    blob_append_literal(ob, "<thead>\n");
    blob_appendb(ob, head_row);
    blob_append_literal(ob, "</thead>\n<tbody>\n");
  }
  if( rows ){
    blob_appendb(ob, rows);
694
695
696
697
698
699
700
701

702
703
704
705
706
707
708
735
736
737
738
739
740
741

742
743
744
745
746
747
748
749







-
+







   && (rPikVar = atof(zPikVar))>=0.1
   && rPikVar<10.0
  ){
    blob_appendf(&bSrc, "fontscale = %.13g\n", rPikVar);
  }
  blob_append(&bSrc, zSrc, nSrc)
    /*have to dup input to ensure a NUL-terminated source string */;
  pikchr_process(blob_str(&bSrc), pikFlags, 0, ob);
  pikchr_process(blob_str(&bSrc), pikFlags, ob);
  blob_reset(&bSrc);
}

/* Invoked for `...` blocks where there are nSep grave accents in a
** row that serve as the delimiter.  According to CommonMark:
**
**   *  https://spec.commonmark.org/0.29/#fenced-code-blocks
909
910
911
912
913
914
915

























































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
1006
1007
1008
1009
1010
1011
1012
1013







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
    blob_set( &context.reqURI, zRU );
  #endif
  html_renderer.opaque = &context;
  if( output_title ) blob_reset(output_title);
  blob_reset(output_body);
  markdown(output_body, input_markdown, &html_renderer);
}

/*
** Undo HTML escapes in Blob p.  In other words convert:
**
**     &amp;     ->     &
**     &lt;      ->     <
**     &gt;      ->     >
**     &quot;    ->     "
**     &#NNN;    ->     ascii character NNN
*/
void markdown_dehtmlize_blob(Blob *p){
  char *z;
  unsigned int j, k;

  z = p->aData;
  for(j=k=0; j<p->nUsed; j++){
    char c = z[j];
    if( c=='&' ){
      if( z[j+1]=='#' && fossil_isdigit(z[j+2]) ){
        int n = 3;
        int x = z[j+2] - '0';
        if( fossil_isdigit(z[j+3]) ){
          x = x*10 + z[j+3] - '0';
          n++;
          if( fossil_isdigit(z[j+4]) ){
            x = x*10 + z[j+4] - '0';
            n++;
          }
        }
        if( z[j+n]==';' ){
          z[k++] = (char)x;
          j += n;
        }else{
          z[k++] = c;
        }
      }else if( memcmp(&z[j],"&lt;",4)==0 ){
        z[k++] = '<';
        j += 3;
      }else if( memcmp(&z[j],"&gt;",4)==0 ){
        z[k++] = '>';
        j += 3;
      }else if( memcmp(&z[j],"&quot;",6)==0 ){
        z[k++] = '"';
        j += 5;
      }else if( memcmp(&z[j],"&amp;",5)==0 ){
        z[k++] = '&';
        j += 4;
      }else{
        z[k++] = c;
      }
    }else{
      z[k++] = c;
    }
  }
  z[k] = 0;
  p->nUsed = k;
}
Added src/match.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
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
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
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
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
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/*
** Copyright (c) 2007 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 implement string comparisons using a
** variety of algorithm.  The comparison algorithm can be any of:
**
**      MS_EXACT            The string must exactly match the pattern.
**
**      MS_BRLIST           The pattern is a space- and/or comma-separated
**                          list of strings, any one of which may match
**                          the input string.
**
**      MS_GLOB             Like BRLIST, except each component of the pattern
**                          is a GLOB expression.
**
**      MS_LIKE             Like BRLIST, except each component of the pattern
**                          is an SQL LIKE expression.
**
**      MS_REGEXP           Like BRLIST, except each component of the pattern
**                          is a regular expression.
**     
*/
#include "config.h"
#include <string.h>
#include "match.h"

#if INTERFACE
/*
** Types of comparisons that we are able to perform:
*/
typedef enum {
  MS_EXACT=1,   /* Exact string comparison */
  MS_GLOB=2,    /* Matches against a list of GLOB patterns. */
  MS_LIKE=3,    /* Matches against a list of LIKE patterns. */
  MS_REGEXP=4,  /* Matches against a list of regular expressions. */
  MS_BRLIST=5,  /* Matches any element of a list */
} MatchStyle;

/*
** The following object represents a precompiled pattern to use for
** string matching.
**
**    *  Create an instance of this object using match_create().
**    *  Do comparisons using match_text().
**    *  Destroy using match_free() when you are done.
**
*/
struct Matcher {
  MatchStyle style;   /* Which algorithm to use */
  int nPattern;       /* How many patterns are their */
  char **azPattern;   /* List of patterns */
  ReCompiled **aRe;   /* List of compiled regular expressions */
};
  
#endif /*INTERFACE*/

/*
** Translate a "match style" text name into the MS_* enum value.
** Return eDflt if no match is found.
*/
MatchStyle match_style(const char *zStyle, MatchStyle eDflt){
  if( zStyle==0 )                           return eDflt;
  if( fossil_stricmp(zStyle, "brlist")==0 ) return MS_BRLIST;
  if( fossil_stricmp(zStyle, "list")==0 )   return MS_BRLIST;
  if( fossil_stricmp(zStyle, "regexp")==0 ) return MS_REGEXP;
  if( fossil_stricmp(zStyle, "re")==0 )     return MS_REGEXP;
  if( fossil_stricmp(zStyle, "glob")==0 )   return MS_GLOB;
  if( fossil_stricmp(zStyle, "like")==0 )   return MS_LIKE;
  if( fossil_stricmp(zStyle, "exact")==0 )  return MS_EXACT;
  return eDflt;
}


/*
** Create a new Matcher object using the pattern provided.
*/
Matcher *match_create(MatchStyle style, const char *zPat){
  char cDel;         /* Delimiter character */
  int i;             /* Loop counter */
  Matcher *p;        /* The new Matcher to be constructed */
  char *zOne;        /* One element of the pattern */

  if( zPat==0 ) return 0;
  p = fossil_malloc( sizeof(*p) );
  memset(p, 0, sizeof(*p));
  p->style = style;

  if( style==MS_EXACT ){
    p->nPattern = 1;
    p->azPattern = fossil_malloc( sizeof(p->azPattern[0]) );
    p->azPattern[0] = fossil_strdup(zPat);
    return p;
  }

  while( 1 ){
    /* Skip leading delimiters. */
    for( ; fossil_isspace(*zPat) || *zPat==','; ++zPat );

    /* Next non-delimiter character determines quoting. */
    if( zPat[0]==0 ){
      /* Terminate loop at end of string. */
      break;
    }else if( zPat[0]=='\'' || zPat[0]=='"' ){
      /* If word is quoted, prepare to stop at end quote. */
      cDel = zPat[0];
      ++zPat;
    }else{
      /* If word is not quoted, prepare to stop at delimiter. */
      cDel = ',';
    }

    /* Find the next delimiter character or end of string. */
    for( i=0; zPat[i] && zPat[i]!=cDel; ++i ){
      /* If delimiter is comma, also recognize spaces as delimiters. */
      if( cDel==',' && fossil_isspace(zPat[i]) ){
        break;
      }

      /* In regexp mode, ignore delimiters following backslashes. */
      if( style==MS_REGEXP && zPat[i]=='\\' && zPat[i+1] ){
        ++i;
      }
    }

    /* zOne is a zero-terminated copy of the pattern, without delimiters */
    zOne = fossil_strndup(zPat, i);
    zPat += i;
    if( zPat[0] ) zPat++;

    /* Check for regular expression syntax errors. */
    if( style==MS_REGEXP ){
      ReCompiled *regexp;
      const char *zFail = fossil_re_compile(&regexp, zOne, 0);
      if( zFail ){
        re_free(regexp);
        continue;
      }
      p->nPattern++;
      p->aRe = fossil_realloc(p->aRe, sizeof(p->aRe)*p->nPattern);
      p->aRe[p->nPattern-1] = regexp;
      fossil_free(zOne);
    }else{
      p->nPattern++;
      p->azPattern = fossil_realloc(p->azPattern, sizeof(char*)*p->nPattern);
      p->azPattern[p->nPattern-1] = zOne;
    }
  }
  return p;
}

/*
** Return non-zero (true) if the input string matches the pattern
** described by the matcher.
**
** The return value is really the 1-based index of the particular
** pattern that matched.
*/
int match_text(Matcher *p, const char *zText){
  int i;
  if( p==0 ){
    return zText==0;
  }
  switch( p->style ){
    case MS_BRLIST:
    case MS_EXACT: {
      for(i=0; i<p->nPattern; i++){
        if( strcmp(p->azPattern[i], zText)==0 ) return i+1;
      }
      break;
    }
    case MS_GLOB: {
      for(i=0; i<p->nPattern; i++){
        if( sqlite3_strglob(p->azPattern[i], zText)==0 ) return i+1;
      }
      break;
    }
    case MS_LIKE: {
      for(i=0; i<p->nPattern; i++){
        if( sqlite3_strlike(p->azPattern[i], zText, 0)==0 ) return i+1;
      }
      break;
    }
    case MS_REGEXP: {
      int nText = (int)strlen(zText);
      for(i=0; i<p->nPattern; i++){
        if( re_match(p->aRe[i], (const u8*)zText, nText) ) return i+1;
      }
      break;
    }
  }
  return 0;
}


/*
** Destroy a previously allocated Matcher object.
*/
void match_free(Matcher *p){
  int i;
  if( p==0 ) return;
  if( p->style==MS_REGEXP ){
    for(i=0; i<p->nPattern; i++) re_free(p->aRe[i]);
    fossil_free(p->aRe);
  }else{
    for(i=0; i<p->nPattern; i++) fossil_free(p->azPattern[i]);
    fossil_free(p->azPattern);
  }
  memset(p, 0, sizeof(*p));
  fossil_free(p);
}



/*
** Quote a tag string by surrounding it with double quotes and preceding
** internal double quotes and backslashes with backslashes.
*/
static const char *tagQuote(
   int len,         /* Maximum length of zTag, or negative for unlimited */
   const char *zTag /* Tag string */
){
  Blob blob = BLOB_INITIALIZER;
  int i, j;
  blob_zero(&blob);
  blob_append(&blob, "\"", 1);
  for( i=j=0; zTag[j] && (len<0 || j<len); ++j ){
    if( zTag[j]=='\"' || zTag[j]=='\\' ){
      if( j>i ){
        blob_append(&blob, zTag+i, j-i);
      }
      blob_append(&blob, "\\", 1);
      i = j;
    }
  }
  if( j>i ){
    blob_append(&blob, zTag+i, j-i);
  }
  blob_append(&blob, "\"", 1);
  return blob_str(&blob);
}

/*
** Construct the  SQL expression that goes into the WHERE clause of a join
** that involves the TAG table and that selects a particular tag out of
** that table.
**
** This function is adapted from glob_expr() to support the MS_EXACT, MS_GLOB,
** MS_LIKE, MS_REGEXP, and MS_BRLIST match styles.
**
** For MS_EXACT, the returned expression
** checks for integer match against the tag ID which is looked up directly by
** this function.  For the other modes, the returned SQL expression performs
** string comparisons against the tag names, so it is necessary to join against
** the tag table to access the "tagname" column.
**
** Each pattern is adjusted to start with "sym-" and be anchored at end.
**
** In MS_REGEXP mode, backslash can be used to protect delimiter characters.
** The backslashes are not removed from the regular expression.
**
** In addition to assembling and returning an SQL expression, this function
** makes an English-language description of the patterns being matched, suitable
** for display in the web interface.
**
** If any errors arise during processing, *zError is set to an error message.
** Otherwise it is set to NULL.
*/
const char *match_tag_sqlexpr(
  MatchStyle matchStyle,        /* Match style code */
  const char *zTag,             /* Tag name, match pattern, or pattern list */
  const char **zDesc,           /* Output expression description string */
  const char **zError           /* Output error string */
){
  Blob expr = BLOB_INITIALIZER; /* SQL expression string assembly buffer */
  Blob desc = BLOB_INITIALIZER; /* English description of match patterns */
  Blob err = BLOB_INITIALIZER;  /* Error text assembly buffer */
  const char *zStart;           /* Text at start of expression */
  const char *zDelimiter;       /* Text between expression terms */
  const char *zEnd;             /* Text at end of expression */
  const char *zPrefix;          /* Text before each match pattern */
  const char *zSuffix;          /* Text after each match pattern */
  const char *zIntro;           /* Text introducing pattern description */
  const char *zPattern = 0;     /* Previous quoted pattern */
  const char *zFail = 0;        /* Current failure message or NULL if okay */
  const char *zOr = " or ";     /* Text before final quoted pattern */
  char cDel;                    /* Input delimiter character */
  int i;                        /* Input match pattern length counter */

  /* Optimize exact matches by looking up the ID in advance to create a simple
   * numeric comparison.  Bypass the remainder of this function. */
  if( matchStyle==MS_EXACT ){
    *zDesc = tagQuote(-1, zTag);
    return mprintf("(tagid=%d)", db_int(-1,
        "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag));
  }

  /* Decide pattern prefix and suffix strings according to match style. */
  if( matchStyle==MS_GLOB ){
    zStart = "(";
    zDelimiter = " OR ";
    zEnd = ")";
    zPrefix = "tagname GLOB 'sym-";
    zSuffix = "'";
    zIntro = "glob pattern ";
  }else if( matchStyle==MS_LIKE ){
    zStart = "(";
    zDelimiter = " OR ";
    zEnd = ")";
    zPrefix = "tagname LIKE 'sym-";
    zSuffix = "'";
    zIntro = "SQL LIKE pattern ";
  }else if( matchStyle==MS_REGEXP ){
    zStart = "(tagname REGEXP '^sym-(";
    zDelimiter = "|";
    zEnd = ")$')";
    zPrefix = "";
    zSuffix = "";
    zIntro = "regular expression ";
  }else/* if( matchStyle==MS_BRLIST )*/{
    zStart = "tagname IN ('sym-";
    zDelimiter = "','sym-";
    zEnd = "')";
    zPrefix = "";
    zSuffix = "";
    zIntro = "";
  }

  /* Convert the list of matches into an SQL expression and text description. */
  blob_zero(&expr);
  blob_zero(&desc);
  blob_zero(&err);
  while( 1 ){
    /* Skip leading delimiters. */
    for( ; fossil_isspace(*zTag) || *zTag==','; ++zTag );

    /* Next non-delimiter character determines quoting. */
    if( !*zTag ){
      /* Terminate loop at end of string. */
      break;
    }else if( *zTag=='\'' || *zTag=='"' ){
      /* If word is quoted, prepare to stop at end quote. */
      cDel = *zTag;
      ++zTag;
    }else{
      /* If word is not quoted, prepare to stop at delimiter. */
      cDel = ',';
    }

    /* Find the next delimiter character or end of string. */
    for( i=0; zTag[i] && zTag[i]!=cDel; ++i ){
      /* If delimiter is comma, also recognize spaces as delimiters. */
      if( cDel==',' && fossil_isspace(zTag[i]) ){
        break;
      }

      /* In regexp mode, ignore delimiters following backslashes. */
      if( matchStyle==MS_REGEXP && zTag[i]=='\\' && zTag[i+1] ){
        ++i;
      }
    }

    /* Check for regular expression syntax errors. */
    if( matchStyle==MS_REGEXP ){
      ReCompiled *regexp;
      char *zTagDup = fossil_strndup(zTag, i);
      zFail = fossil_re_compile(&regexp, zTagDup, 0);
      re_free(regexp);
      fossil_free(zTagDup);
    }

    /* Process success and error results. */
    if( !zFail ){
      /* Incorporate the match word into the output expression.  %q is used to
       * protect against SQL injection attacks by replacing ' with ''. */
      blob_appendf(&expr, "%s%s%#q%s", blob_size(&expr) ? zDelimiter : zStart,
          zPrefix, i, zTag, zSuffix);

      /* Build up the description string. */
      if( !blob_size(&desc) ){
        /* First tag: start with intro followed by first quoted tag. */
        blob_append(&desc, zIntro, -1);
        blob_append(&desc, tagQuote(i, zTag), -1);
      }else{
        if( zPattern ){
          /* Third and subsequent tags: append comma then previous tag. */
          blob_append(&desc, ", ", 2);
          blob_append(&desc, zPattern, -1);
          zOr = ", or ";
        }

        /* Second and subsequent tags: store quoted tag for next iteration. */
        zPattern = tagQuote(i, zTag);
      }
    }else{
      /* On error, skip the match word and build up the error message buffer. */
      if( !blob_size(&err) ){
        blob_append(&err, "Error: ", 7);
      }else{
        blob_append(&err, ", ", 2);
      }
      blob_appendf(&err, "(%s%s: %s)", zIntro, tagQuote(i, zTag), zFail);
    }

    /* Advance past all consumed input characters. */
    zTag += i;
    if( cDel!=',' && *zTag==cDel ){
      ++zTag;
    }
  }

  /* Finalize and extract the pattern description. */
  if( zPattern ){
    blob_append(&desc, zOr, -1);
    blob_append(&desc, zPattern, -1);
  }
  *zDesc = blob_str(&desc);

  /* Finalize and extract the error text. */
  *zError = blob_size(&err) ? blob_str(&err) : 0;

  /* Finalize and extract the SQL expression. */
  if( blob_size(&expr) ){
    blob_append(&expr, zEnd, -1);
    return blob_str(&expr);
  }

  /* If execution reaches this point, the pattern was empty.  Return NULL. */
  return 0;
}
Changes to src/merge.c.
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
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
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
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
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
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
480
481
482
483
484
485

486
487
488
489
490
491
492
493







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



















-
+







** This file contains code used to merge two or more branches into
** a single tree.
*/
#include "config.h"
#include "merge.h"
#include <assert.h>


/*
** Bring up a Tcl/Tk GUI to show details of the most recent merge.
*/
static void merge_info_tk(int bDark, int bAll, int nContext){
  int i;
  Blob script;
  const char *zTempFile = 0;
  int bDebug;
  char *zCmd;
  const char *zTclsh;
  zTclsh = find_option("tclsh",0,1);
  if( zTclsh==0 ){
    zTclsh = db_get("tclsh",0);
  }
  /* The undocumented --script FILENAME option causes the Tk script to
  ** be written into the FILENAME instead of being run.  This is used
  ** for testing and debugging. */
  zTempFile = find_option("script",0,1);
  bDebug = find_option("tkdebug",0,0)!=0;
  verify_all_options();

  blob_zero(&script);
  blob_appendf(&script, "set ncontext %d\n", nContext);
  blob_appendf(&script, "set fossilexe {\"%/\"}\n", g.nameOfExe);
  blob_appendf(&script, "set fossilcmd {| \"%/\" merge-info}\n",
               g.nameOfExe);
  blob_appendf(&script, "set filelist [list");
  if( g.argc==2 ){
    /* No files named on the command-line.  Use every file mentioned
    ** in the MERGESTAT table to generate the file list. */
    Stmt q;
    int cnt = 0;
    db_prepare(&q,
       "WITH priority(op,pri) AS (VALUES('CONFLICT',0),('ERROR',0),"
                                       "('MERGE',1),('ADDED',2),('UPDATE',2))"
       "SELECT coalesce(fnr,fn), op FROM mergestat JOIN priority USING(op)"
           " %s ORDER BY pri, 1",
       bAll ? "" : "WHERE op IN ('MERGE','CONFLICT')" /*safe-for-%s*/
    );
    while( db_step(&q)==SQLITE_ROW ){
      blob_appendf(&script," %s ", db_column_text(&q,1));
      blob_append_tcl_literal(&script, db_column_text(&q,0),
                              db_column_bytes(&q,0));
      cnt++;
    }
    db_finalize(&q);
    if( cnt==0 ){
      fossil_print(
        "No interesting changes in this merge. Use --all to see everything\n"
      );
      return;
    }
  }else{
    /* Use only files named on the command-line in the file list.
    ** But verify each file named is actually found in the MERGESTAT
    ** table first. */
    for(i=2; i<g.argc; i++){
      char *zFile;          /* Input filename */
      char *zTreename;      /* Name of the file in the tree */
      Blob fname;           /* Filename relative to root */
      char *zOp;            /* Operation on this file */
      zFile = mprintf("%/", g.argv[i]);
      file_tree_name(zFile, &fname, 0, 1);
      fossil_free(zFile);
      zTreename = blob_str(&fname);
      zOp = db_text(0, "SELECT op FROM mergestat WHERE fn=%Q or fnr=%Q",
                       zTreename, zTreename);
      blob_appendf(&script, " %s ", zOp);
      fossil_free(zOp);
      blob_append_tcl_literal(&script, zTreename, (int)strlen(zTreename));
      blob_reset(&fname);
    }
  }
  blob_appendf(&script, "]\n");
  blob_appendf(&script, "set darkmode %d\n", bDark!=0);
  blob_appendf(&script, "set debug %d\n", bDebug!=0);
  blob_appendf(&script, "%s", builtin_file("merge.tcl", 0));
  if( zTempFile ){
    blob_write_to_file(&script, zTempFile);
    fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile);
  }else{
#if defined(FOSSIL_ENABLE_TCL)
    Th_FossilInit(TH_INIT_DEFAULT);
    if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script),
                              blob_size(&script), 1, 1, 0)==TCL_OK ){
      blob_reset(&script);
      return;
    }
    /*
     * If evaluation of the Tcl script fails, the reason may be that Tk
     * could not be found by the loaded Tcl, or that Tcl cannot be loaded
     * dynamically (e.g. x64 Tcl with x86 Fossil).  Therefore, fallback
     * to using the external "tclsh", if available.
     */
#endif
    zTempFile = write_blob_to_temp_file(&script);
    zCmd = mprintf("%$ %$", zTclsh, zTempFile);
    if( bDebug ){
      fossil_print("%s\n", zCmd);
      fflush(stdout);
    }
    fossil_system(zCmd);
    file_delete(zTempFile);
    fossil_free(zCmd);
  }
  blob_reset(&script);
}

/*
** Generate a TCL list on standard output that can be fed into the
** merge.tcl script to show the details of the most recent merge
** command associated with file "zFName".  zFName must be the filename
** relative to the root of the check-in - in other words a "tree name".
**
** When this routine is called, we know that the mergestat table
** exists, but we do not know if zFName is mentioned in that table.
**
** The diffMode variable has these values:
**
**     0       Standard 3-way diff
**     12      2-way diff between baseline and local
**     13      2-way diff between baseline and merge-in
**     23      2-way diff between local and merge-in
*/
static void merge_info_tcl(const char *zFName, int nContext, int diffMode){
  const char *zTreename;/* Name of the file in the tree */
  Stmt q;               /* To query the MERGESTAT table */
  MergeBuilder mb;      /* The merge builder object */
  Blob pivot,v1,v2,out; /* Blobs for holding content */
  const char *zFN;      /* A filename */
  int rid;              /* RID value */
  int sz;               /* File size value */

  zTreename = zFName;
  db_prepare(&q,
       /*   0    1     2   3     4   5    6     7  */
    "SELECT fnp, ridp, fn, ridv, sz, fnm, ridm, fnr"
    "  FROM mergestat"
    " WHERE fnp=%Q OR fnr=%Q",
    zTreename, zTreename
  );
  if( db_step(&q)!=SQLITE_ROW ){
    db_finalize(&q);
    fossil_print("ERROR {don't know anything about file: %s}\n", zTreename);
    return;
  }
  mergebuilder_init_tcl(&mb);
  mb.nContext = nContext;

  blob_zero(&pivot);
  if( diffMode!=23 ){
    /* Set up the pivot or baseline */
    zFN = db_column_text(&q, 0);
    if( zFN==0 ){
      /* No pivot because the file was added */
      mb.zPivot = "(no baseline)";
    }else{
      mb.zPivot = mprintf("%s (baseline)", file_tail(zFN));
      rid = db_column_int(&q, 1);
      content_get(rid, &pivot);
    }
    mb.pPivot = &pivot;
  }

  blob_zero(&v2);
  if( diffMode!=12 ){
    /* Set up the merge-in as V2 */
    zFN = db_column_text(&q, 5);
    if( zFN==0 ){
      /* File deleted in the merged-in branch */
      mb.zV2 = "(deleted file)";
    }else{
      mb.zV2 = mprintf("%s (merge-in)", file_tail(zFN));
      rid = db_column_int(&q, 6);
      content_get(rid, &v2);
    }
    mb.pV2 = &v2;
  }

  blob_zero(&v1);
  if( diffMode!=13 ){
    /* Set up the local content as V1 */
    zFN = db_column_text(&q, 2);
    if( zFN==0 ){
      /* File added by merge */
      mb.zV1 = "(no original)";
    }else{
      mb.zV1 = mprintf("%s (local)", file_tail(zFN));
      rid = db_column_int(&q, 3);
      sz = db_column_int(&q, 4);
      if( rid==0 && sz>0 ){
        /* The origin file had been edited so we'll have to pull its
        ** original content out of the undo buffer */
        Stmt q2;
        db_prepare(&q2, 
          "SELECT content FROM undo"
          " WHERE pathname=%Q AND octet_length(content)=%d",
          zFN, sz
        );
        blob_zero(&v1);
        if( db_step(&q2)==SQLITE_ROW ){
          db_column_blob(&q2, 0, &v1);
        }else{
          mb.zV1 = "(local content missing)";
        }
        db_finalize(&q2);
      }else{
        /* The origin file was unchanged when the merge first occurred */
        content_get(rid, &v1);
      }
    }
    mb.pV1 = &v1;
  }

  blob_zero(&out);
  if( diffMode==0 ){
    /* Set up the output and do a 3-way diff */
    zFN = db_column_text(&q, 7);
    if( zFN==0 ){
      mb.zOut = "(Merge Result)";
    }else{
      mb.zOut = mprintf("%s (after merge)", file_tail(zFN));
    }
    mb.pOut = &out;
    merge_three_blobs(&mb);
  }else{
    /* Set up to do a two-way diff */
    Blob *pLeft, *pRight;
    const char *zTagLeft, *zTagRight;
    DiffConfig cfg;
    memset(&cfg, 0, sizeof(cfg));
    cfg.diffFlags = DIFF_TCL;
    cfg.nContext = mb.nContext;
    if( diffMode==12 || diffMode==13 ){
      pLeft = &pivot;
      zTagLeft = "baseline";
    }else{
      pLeft = &v1;
      zTagLeft = "local";
    }
    if( diffMode==12 ){
      pRight = &v1;
      zTagRight = "local";
    }else{
      pRight = &v2;
      zTagRight = "merge-in";
    }
    cfg.azLabel[0] = mprintf("%s (%s)", zFName, zTagLeft);
    cfg.azLabel[1] = mprintf("%s (%s)", zFName, zTagRight);
    diff_print_filenames("", "", &cfg, &out);
    text_diff(pLeft, pRight, &out, &cfg);
    fossil_free((char*)cfg.azLabel[0]);
    fossil_free((char*)cfg.azLabel[1]);
  }

  blob_write_to_file(&out, "-");
  mb.xDestroy(&mb);
  blob_reset(&pivot);
  blob_reset(&v1);
  blob_reset(&v2);
  blob_reset(&out);
  db_finalize(&q);
}

/*
** COMMAND: merge-info
**
** Usage: %fossil merge-info [OPTIONS]
**
** Display information about the most recent merge operation.
**
** Options:
**   -a|--all             Show all file changes that happened because of
**                        the merge.  Normally only MERGE, CONFLICT, and ERROR
**                        lines are shown
**   -c|--context N       Show N lines of context around each change,
**                        with negative N meaning show all content.  Only
**                        meaningful in combination with --tcl or --tk.
**   --dark               Use dark mode for the Tcl/Tk-based GUI
**   --tk                 Bring up a Tcl/Tk GUI that shows the changes
**                        associated with the most recent merge.
**
** Options used internally by --tk:
**   --diff12 FILE        Bring up a separate --tk diff for just the baseline
**                        and local variants of FILE.
**   --diff13 FILE        Like --diff12 but for baseline versus merge-in
**   --diff23 FILE        Like --diff12 but for local versus merge-in
**   --tcl FILE           Generate (to stdout) a TCL list containing
**                        information needed to display the changes to
**                        FILE caused by the most recent merge.  FILE must
**                        be a pathname relative to the root of the check-out.
**
** Debugging options available only when --tk is used:
**   --tkdebug            Show sub-commands run to implement --tk
**   --script FILE        Write script used to implement --tk into FILE
*/
void merge_info_cmd(void){
  const char *zCnt;
  const char *zTcl;
  int bTk;
  int bDark;
  int bAll;
  int nContext;
  Stmt q;
  const char *zWhere;
  int cnt = 0;
  const char *zDiff2 = 0;
  int diffMode = 0;

  db_must_be_within_tree();
  bTk = find_option("tk", 0, 0)!=0;
  zTcl = find_option("tcl", 0, 1);
  zCnt = find_option("context", "c", 1);
  bDark = find_option("dark", 0, 0)!=0;
  bAll = find_option("all", "a", 0)!=0;
  if( (zDiff2 = find_option("diff12", 0, 1))!=0 ){
    diffMode = 12;
  }else
  if( (zDiff2 = find_option("diff13", 0, 1))!=0 ){
    diffMode = 13;
  }else
  if( (zDiff2 = find_option("diff23", 0, 1))!=0 ){
    diffMode = 23;
  }

  if( zCnt ){
    nContext = atoi(zCnt);
    if( nContext<0 ) nContext = 0xfffffff;
  }else{
    nContext = 6;
  }
  if( !db_table_exists("localdb","mergestat") ){
    if( zTcl ){
      fossil_print("ERROR {no merge data available}\n");
    }else{
      fossil_print("No merge data is available\n");
    }
    return;
  }
  if( bTk ){
    merge_info_tk(bDark, bAll, nContext);
    return;
  }
  if( zTcl ){
    if( diffMode ) zTcl = zDiff2;
    merge_info_tcl(zTcl, nContext, diffMode);
    return;
  }
  if( diffMode ){
    char *zCmd;
    zCmd = mprintf("merge-info --diff%d %!$ -c %d%s",
                   diffMode, zDiff2, nContext, bDark ? " --dark" : "");
    diff_tk(zCmd, g.argc);
    fossil_free(zCmd);
    return;
  }

  verify_all_options();
  if( g.argc>2 ){
    usage("[OPTIONS]");
  }

  if( bAll ){
    zWhere = "";
  }else{
    zWhere = "WHERE op IN ('MERGE','CONFLICT','ERROR')";
  }
  db_prepare(&q,
    "WITH priority(op,pri) AS (VALUES('CONFLICT',0),('ERROR',0),"
                                    "('MERGE',1),('ADDED',2),('UPDATE',2))"

        /*  0   1                 2  */
    "SELECT op, coalesce(fnr,fn), msg"
    "  FROM mergestat JOIN priority USING(op)"
    " %s"
    " ORDER BY pri, coalesce(fnr,fn)",
    zWhere /*safe-for-%s*/
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zOp = db_column_text(&q, 0);
    const char *zName = db_column_text(&q, 1);
    const char *zErr = db_column_text(&q, 2);
    if( zErr && fossil_strcmp(zOp,"CONFLICT")!=0 ){
      fossil_print("%-9s %s  (%s)\n", zOp, zName, zErr);
    }else{
      fossil_print("%-9s %s\n", zOp, zName);
    }
    cnt++;
  }
  db_finalize(&q);
  if( !bAll && cnt==0 ){
    fossil_print(
      "No interesting changes in this merge.  Use --all to see everything.\n"
    );
  }
}

/*
** Erase all information about prior merges.  Do this, for example, after
** a commit.
*/
void merge_info_forget(void){
  db_multi_exec(
    "DROP TABLE IF EXISTS localdb.mergestat;"
    "DELETE FROM localdb.vvar WHERE name glob 'mergestat-*';"
  );
}


/*
** Initialize the MERGESTAT table.
**
** Notes about mergestat:
**
**    *  ridv is a positive integer and sz is NULL if the V file contained
**       no local edits prior to the merge.  If the V file was modified prior
**       to the merge then ridv is NULL and sz is the size of the file prior
**       to merge.
**
**    *  fnp, ridp, fn, ridv, and sz are all NULL for a file that was
**       added by merge.
*/
void merge_info_init(void){
  merge_info_forget();
  db_multi_exec(
    "CREATE TABLE localdb.mergestat(\n"
    "  op TEXT,   -- 'UPDATE', 'ADDED', 'MERGE', etc...\n"
    "  fnp TEXT,  -- Name of the pivot file (P)\n"
    "  ridp INT,  -- RID for the pivot file\n"
    "  fn TEXT,   -- Name of origin file (V)\n"
    "  ridv INT,  -- RID for origin file, or NULL if previously edited\n"
    "  sz INT,    -- Size of origin file in bytes, NULL if unedited\n"
    "  fnm TEXT,  -- Name of the file being merged in (M)\n"
    "  ridm INT,  -- RID for the merge-in file\n"
    "  fnr TEXT,  -- Name of the final output file, after all renaming\n"
    "  nc INT DEFAULT 0,    -- Number of conflicts\n"
    "  msg TEXT   -- Error message\n"
    ");"
  );
}

/*
** Print information about a particular check-in.
*/
void print_checkin_description(int rid, int indent, const char *zLabel){
  Stmt q;
  db_prepare(&q,
     "SELECT datetime(mtime,toLocal()),"
     "       coalesce(euser,user), coalesce(ecomment,comment),"
     "       (SELECT uuid FROM blob WHERE rid=%d),"
     "       (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref"
     "         WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
     "           AND tagxref.rid=%d AND tagxref.tagtype>0)"
     "  FROM event WHERE objid=%d", rid, rid, rid);
  if( db_step(&q)==SQLITE_ROW ){
    const char *zTagList = db_column_text(&q, 4);
    char *zCom;
    if( zTagList && zTagList[0] ){
      zCom = mprintf("%s (%s)", db_column_text(&q, 2), zTagList);
    }else{
      zCom = mprintf("%s", db_column_text(&q,2));
      zCom = fossil_strdup(db_column_text(&q,2));
    }
    fossil_print("%-*s [%S] by %s on %s\n%*s",
       indent-1, zLabel,
       db_column_text(&q, 3),
       db_column_text(&q, 1),
       db_column_text(&q, 0),
       indent, "");
293
294
295
296
297
298
299



300
301
302
303
304
305
306
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751







+
+
+







** "merge --cherrypick".
**
** Files which are renamed in the merged-in branch will be renamed in
** the current check-out.
**
** If the VERSION argument is omitted, then Fossil attempts to find
** a recent fork on the current branch to merge.
**
** Note that this command does not commit the merge, as that is a
** separate step.
**
** If there are multiple VERSION arguments, then each VERSION is merged
** (or cherrypicked) in the order that they appear on the command-line.
**
** Options:
**   --backout               Do a reverse cherrypick merge against VERSION.
**                           In other words, back out the changes that were
318
319
320
321
322
323
324
325

326

327
328
329
330
331
332
333
763
764
765
766
767
768
769

770
771
772
773
774
775
776
777
778
779







-
+

+







**                           changes back to the nearest common ancestor.
**   -f|--force              Force the merge even if it would be a no-op
**   --force-missing         Force the merge even if there is missing content
**   --integrate             Merged branch will be closed when committing
**   -K|--keep-merge-files   On merge conflict, retain the temporary files
**                           used for merging, named *-baseline, *-original,
**                           and *-merge.
**   -n|--dry-run            If given, display instead of run actions
**   -n|--dry-run            Do not actually change files on disk
**   --nosync                Do not auto-sync prior to merging
**   --noundo                Do not record changes in the undo log
**   -v|--verbose            Show additional details of the merge
*/
void merge_cmd(void){
  int vid;              /* Current version "V" */
  int mid;              /* Version we are merging from "M" */
  int pid = 0;          /* The pivot version - most recent common ancestor P */
  int nid = 0;          /* The name pivot version "N" */
345
346
347
348
349
350
351

352

353
354
355
356
357
358
359
791
792
793
794
795
796
797
798

799
800
801
802
803
804
805
806







+
-
+







  int keepMergeFlag;    /* True if --keep-merge-files is present */
  int nConflict = 0;    /* Number of conflicts seen */
  int nOverwrite = 0;   /* Number of unmanaged files overwritten */
  char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */
  const char *zVersion; /* The VERSION argument */
  int bMultiMerge = 0;  /* True if there are two or more VERSION arguments */
  int nMerge = 0;       /* Number of prior merges processed */
  int useUndo = 1;      /* True to record changes in the undo log */
  Stmt q;               /* SQL statment used for merge processing */
  Stmt q;               /* SQL statement used for merge processing */


  /* Notation:
  **
  **      V     The current check-out
  **      M     The version being merged in
  **      P     The "pivot" - the most recent common ancestor of V and M.
393
394
395
396
397
398
399


400
401
402
403
404
405
406
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855







+
+







  ** Hints:
  **   *  Combine --debug and --verbose for still more output.
  **   *  The --dry-run option is also useful in combination with --debug.
  */
  debugFlag = find_option("debug",0,0)!=0;
  if( debugFlag && verboseFlag ) debugFlag = 2;
  showVfileFlag = find_option("show-vfile",0,0)!=0;
  useUndo = find_option("noundo",0,0)==0;
  if( dryRunFlag ) useUndo = 0;

  verify_all_options();
  db_must_be_within_tree();
  if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    fossil_fatal("nothing is checked out");
559
560
561
562
563
564
565
566

567
568
569
570
571
572
573
1008
1009
1010
1011
1012
1013
1014

1015
1016
1017
1018
1019
1020
1021
1022







-
+







  if( verboseFlag ){
    print_checkin_description(mid, 12,
              integrateFlag ? "integrate:" : "merge-from:");
    print_checkin_description(pid, 12, "baseline:");
  }
  vfile_check_signature(vid, CKSIG_ENOTFILE);
  if( nMerge==0 ) db_begin_transaction();
  if( !dryRunFlag ) undo_begin();
  if( useUndo ) undo_begin();
  if( load_vfile_from_rid(mid) && !forceMissingFlag ){
    fossil_fatal("missing content, unable to merge");
  }
  if( load_vfile_from_rid(pid) && !forceMissingFlag ){
    fossil_fatal("missing content, unable to merge");
  }
  if( zPivot ){
593
594
595
596
597
598
599
600

601
602
603
604
605
606
607
1042
1043
1044
1045
1046
1047
1048

1049
1050
1051
1052
1053
1054
1055
1056







-
+







    fossil_print("V=%-4d %z (current version)\n", vid, z);
    fossil_print("vAncestor = '%c'\n", vAncestor);
  }
  if( showVfileFlag ) debug_show_vfile();

  /*
  ** The vfile.pathname field is used to match files against each other.  The
  ** FV table contains one row for each each unique filename in
  ** FV table contains one row for each unique filename in
  ** in the current check-out, the pivot, and the version being merged.
  */
  db_multi_exec(
    "DROP TABLE IF EXISTS fv;"
    "CREATE TEMP TABLE fv(\n"
    "  fn TEXT UNIQUE %s,\n"       /* The filename */
    "  idv INTEGER DEFAULT 0,\n"   /* VFILE entry for current version */
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

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
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

920
921
922
923
924
925
926
927










928
929
930
931
932
933
934
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255

1256
1257
1258
1259
1260

1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
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
1320
1321
1322
1323
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
1359
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
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
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440

1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466







+
+
+
+
+
-
+



+
-
+










+

-








+
+
+
+
+
+
+
+
+
+
+











+
-
+
+
+
+














+


+











+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
-
+

+




















+
+
+




+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+












-
+






+
+
+
+



+


+
+
+
+
+
+

-
+








+
+
+
+
+
+
+
+
+
+







    debug_fv_dump( debugFlag>=2 );
  }

  /************************************************************************
  ** All of the information needed to do the merge is now contained in the
  ** FV table.  Starting here, we begin to actually carry out the merge.
  **
  ** Begin by constructing the localdb.mergestat table. 
  */
  merge_info_init();

  /*
  ** First, find files that have changed from P->M but not P->V.
  ** Find files that have changed from P->M but not P->V.
  ** Copy the M content over into V.
  */
  db_prepare(&q,
    /*      0    1     2   3        4    5     6     7   */
    "SELECT idv, ridm, fn, islinkm FROM fv"
    "SELECT idv, ridm, fn, islinkm, fnp, ridp, ridv, fnm FROM fv"
    " WHERE idp>0 AND idv>0 AND idm>0"
    "   AND ridm!=ridp AND ridv=ridp AND NOT chnged"
  );
  while( db_step(&q)==SQLITE_ROW ){
    int idv = db_column_int(&q, 0);
    int ridm = db_column_int(&q, 1);
    const char *zName = db_column_text(&q, 2);
    int islinkm = db_column_int(&q, 3);
    /* Copy content from idm over into idv.  Overwrite idv. */
    fossil_print("UPDATE %s\n", zName);
    if( useUndo ) undo_save(zName);
    if( !dryRunFlag ){
      undo_save(zName);
      db_multi_exec(
        "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d,"
        " mhash=CASE WHEN rid<>%d"
                   " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END"
        " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv
      );
      vfile_to_disk(0, idv, 0, 0);
    }
    db_multi_exec(
      "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr)"
      "VALUES('UPDATE',%Q,%d,%Q,%d,%Q,%d,%Q)",
      /* fnp   */ db_column_text(&q, 4),
      /* ridp  */ db_column_int(&q,5),
      /* fn    */ zName,
      /* ridv  */ db_column_int(&q,6),
      /* fnm   */ db_column_text(&q, 7),
      /* ridm  */ ridm,
      /* fnr   */ zName
    ); 
  }
  db_finalize(&q);

  /*
  ** Do a three-way merge on files that have changes on both P->M and P->V.
  **
  ** Proceed even if the file doesn't exist on P, just like the common ancestor
  ** of M and V is an empty file. In this case, merge conflict marks will be
  ** added to the file and user will be forced to take a decision.
  */
  db_prepare(&q,
        /*  0     1    2     3     4   5   6      7        8 */
    "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm FROM fv"
    "SELECT ridm, idv, ridp, ridv, %z, fn, isexe, islinkv, islinkm,"
        /*  9     10   11   */
    "       fnp,  fnm, chnged"
    "  FROM fv"
    " WHERE idv>0 AND idm>0"
    "   AND ridm!=ridp AND (ridv!=ridp OR chnged)",
    glob_expr("fv.fn", zBinGlob)
  );
  while( db_step(&q)==SQLITE_ROW ){
    int ridm = db_column_int(&q, 0);
    int idv = db_column_int(&q, 1);
    int ridp = db_column_int(&q, 2);
    int ridv = db_column_int(&q, 3);
    int isBinary = db_column_int(&q, 4);
    const char *zName = db_column_text(&q, 5);
    int isExe = db_column_int(&q, 6);
    int islinkv = db_column_int(&q, 7);
    int islinkm = db_column_int(&q, 8);
    int chnged = db_column_int(&q, 11);
    int rc;
    char *zFullPath;
    const char *zType = "MERGE";
    Blob m, p, r;
    /* Do a 3-way merge of idp->idm into idp->idv.  The results go into idv. */
    if( verboseFlag ){
      fossil_print("MERGE %s  (pivot=%d v1=%d v2=%d)\n",
                   zName, ridp, ridm, ridv);
    }else{
      fossil_print("MERGE %s\n", zName);
    }
    if( islinkv || islinkm ){
      fossil_print("***** Cannot merge symlink %s\n", zName);
      nConflict++;
      db_multi_exec(
        "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr,nc,msg)"
        "VALUES('ERROR',%Q,%d,%Q,%d,%Q,%d,%Q,1,'cannot merge symlink')",
        /* fnp  */ db_column_text(&q, 9),
        /* ridp */ ridp,
        /* fn   */ zName,
        /* ridv */ ridv,
        /* fnm  */ db_column_text(&q, 10),
        /* ridm */ ridm,
        /* fnr  */ zName
      ); 
    }else{
      i64 sz;
      const char *zErrMsg = 0;
      int nc = 0;

      if( !dryRunFlag ) undo_save(zName);
      if( useUndo ) undo_save(zName);
      zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
      sz = file_size(zFullPath, ExtFILE);
      content_get(ridp, &p);
      content_get(ridm, &m);
      if( isBinary ){
        rc = -1;
        blob_zero(&r);
      }else{
        unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0;
        if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES;
        rc = merge_3way(&p, zFullPath, &m, &r, mergeFlags);
      }
      if( rc>=0 ){
        if( !dryRunFlag ){
          blob_write_to_file(&r, zFullPath);
          file_setexe(zFullPath, isExe);
        }
        db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv);
        if( rc>0 ){
          fossil_print("***** %d merge conflict%s in %s\n",
                       rc, rc>1 ? "s" : "", zName);
          nConflict++;
          nc = rc;
          zErrMsg = "merge conflicts";
          zType = "CONFLICT";
        }
      }else{
        fossil_print("***** Cannot merge binary file %s\n", zName);
        nConflict++;
        nc = 1;
        zErrMsg = "cannot merge binary file";
        zType = "ERROR";
      }
      db_multi_exec(
        "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)"
        "VALUES(%Q,%Q,%d,%Q,iif(%d,%d,NULL),iif(%d,%lld,NULL),%Q,%d,"
               "%Q,%d,%Q)",
        /* op   */ zType,
        /* fnp  */ db_column_text(&q, 9),
        /* ridp */ ridp,
        /* fn   */ zName,
        /* ridv */ chnged==0, ridv,
        /* sz   */ chnged!=0, sz,
        /* fnm  */ db_column_text(&q, 10),
        /* ridm */ ridm,
        /* fnr  */ zName,
        /* nc   */ nc,
        /* msg  */ zErrMsg
      );
      fossil_free(zFullPath);
      blob_reset(&p);
      blob_reset(&m);
      blob_reset(&r);
    }
    vmerge_insert(idv, ridm);
  }
  db_finalize(&q);

  /*
  ** Drop files that are in P and V but not in M
  */
  db_prepare(&q,
    "SELECT idv, fn, chnged FROM fv"
    "SELECT idv, fn, chnged, ridv FROM fv"
    " WHERE idp>0 AND idv>0 AND idm=0"
  );
  while( db_step(&q)==SQLITE_ROW ){
    int idv = db_column_int(&q, 0);
    const char *zName = db_column_text(&q, 1);
    int chnged = db_column_int(&q, 2);
    int ridv = db_column_int(&q, 3);
    int sz = -1;
    const char *zErrMsg = 0;
    int nc = 0;
    /* Delete the file idv */
    fossil_print("DELETE %s\n", zName);
    if( chnged ){
      char *zFullPath;
      fossil_warning("WARNING: local edits lost for %s", zName);
      nConflict++;
      ridv = 0;
      nc = 1;
      zErrMsg = "local edits lost";
      zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
      sz = file_size(zFullPath, ExtFILE);
      fossil_free(zFullPath);
    }
    if( !dryRunFlag ) undo_save(zName);
    if( useUndo ) undo_save(zName);
    db_multi_exec(
      "UPDATE vfile SET deleted=1 WHERE id=%d", idv
    );
    if( !dryRunFlag ){
      char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
      file_delete(zFullPath);
      free(zFullPath);
    }
    db_multi_exec(
      "INSERT INTO localdb.mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,nc,msg)"
      "VALUES('DELETE',NULL,NULL,%Q,iif(%d,%d,NULL),iif(%d,%d,NULL),"
             "NULL,NULL,%d,%Q)",
      /* fn   */ zName,
      /* ridv */ chnged==0, ridv,
      /* sz   */ chnged!=0, sz,
      /* nc   */ nc,
      /* msg  */ zErrMsg
    );
  }
  db_finalize(&q);

  /* For certain sets of renames (e.g. A -> B and B -> A), a file that is
  ** being renamed must first be moved to a temporary location to avoid
  ** being overwritten by another rename operation. A row is added to the
  ** TMPRN table for each of these temporary renames.
949
950
951
952
953
954
955
956
957






958
959
960
961
962
963
964
1481
1482
1483
1484
1485
1486
1487


1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500







-
-
+
+
+
+
+
+







  );
  while( db_step(&q)==SQLITE_ROW ){
    int idv = db_column_int(&q, 0);
    const char *zOldName = db_column_text(&q, 1);
    const char *zNewName = db_column_text(&q, 2);
    int isExe = db_column_int(&q, 3);
    fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
    if( !dryRunFlag ) undo_save(zOldName);
    if( !dryRunFlag ) undo_save(zNewName);
    if( useUndo ) undo_save(zOldName);
    if( useUndo ) undo_save(zNewName);
    db_multi_exec(
      "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q",
      zOldName
    );
    db_multi_exec(
      "UPDATE vfile SET pathname=NULL, origname=pathname"
      " WHERE vid=%d AND pathname=%Q;"
      "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)"
      " WHERE id=%d;",
      vid, zNewName, zNewName, idv
    );
1004
1005
1006
1007
1008
1009
1010
1011

1012
1013
1014
1015
1016
1017
1018
1540
1541
1542
1543
1544
1545
1546

1547
1548
1549
1550
1551
1552
1553
1554







-
+







    " WHERE pathname IS NULL"
  );

  /*
  ** Insert into V any files that are not in V or P but are in M.
  */
  db_prepare(&q,
    "SELECT idm, fnm FROM fv"
    "SELECT idm, fnm, ridm FROM fv"
    " WHERE idp=0 AND idv=0 AND idm>0"
  );
  while( db_step(&q)==SQLITE_ROW ){
    int idm = db_column_int(&q, 0);
    const char *zName;
    char *zFullName;
    db_multi_exec(
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
1562
1563
1564
1565
1566
1567
1568

1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588

1589
1590
1591
1592
1593
1594
1595







-
+










+
+
+
+
+
+
+
+

-







    );
    zName = db_column_text(&q, 1);
    zFullName = mprintf("%s%s", g.zLocalRoot, zName);
    if( file_isfile_or_link(zFullName)
        && !db_exists("SELECT 1 FROM fv WHERE fn=%Q", zName) ){
      /* Name of backup file with Original content */
      char *zOrig = file_newname(zFullName, "original", 1);
      /* Backup previously unanaged file before to be overwritten */
      /* Backup previously unmanaged file before being overwritten */
      file_copy(zFullName, zOrig);
      fossil_free(zOrig);
      fossil_print("ADDED %s (overwrites an unmanaged file)", zName);
      if( !dryRunFlag ) fossil_print(", original copy backed up locally");
      fossil_print("\n");
      nOverwrite++;
    }else{
      fossil_print("ADDED %s\n", zName);
    }
    fossil_free(zFullName);
    db_multi_exec(
      "INSERT INTO mergestat(op,fnm,ridm,fnr)"
      "VALUES('ADDED',%Q,%d,%Q)",
      /* fnm  */ zName,
      /* ridm */ db_column_int(&q,2),
      /* fnr  */ zName
    );
    if( useUndo ) undo_save(zName);
    if( !dryRunFlag ){
      undo_save(zName);
      vfile_to_disk(0, idm, 0, 0);
    }
  }
  db_finalize(&q);

  /* Report on conflicts
  */
1097
1098
1099
1100
1101
1102
1103
1104

1105
1106
1107
1640
1641
1642
1643
1644
1645
1646

1647
1648
1649
1650







-
+



  }else{
    vmerge_insert(0, mid);
  }
  if( bMultiMerge && nConflict==0 ){
    nMerge++;
    goto merge_next_child;
  }
  if( !dryRunFlag ) undo_finish();
  if( useUndo ) undo_finish();

  db_end_transaction(dryRunFlag);
}
Added src/merge.tcl.


































































































































































































































































































































































































































































































































































































































































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
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
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
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
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
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
636
637
638
639
640
641
642
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
# Show details of a 3-way merge operation.  The left-most column is the
# common ancestor.  The next two columns are edits of that common ancestor.
# The right-most column is the result of the merge.
#
# Several variables will have been initialized:
#
#    ncontext            The number of lines of context to show on each change
#
#    fossilexe           Pathname of the fossil program
#
#    filelist            A list of "merge-type filename" pairs.
#
#    darkmode            Boolean.  True for dark mode
#
#    debug               Boolean.  True for debugging output
#
# If the "filelist" global variable is defined, then it is a list of
# alternating "merge-type names" (ex: UPDATE, MERGE, CONFLICT, ERROR) and
# filenames.  In that case, the initial display shows the changes for
# the first pair on the list and there is a optionmenu that allows the
# user to select other fiels on the list.
#
# This header comment is stripped off by the "mkbuiltin.c" program.
#
package require Tk

array set CFG_light {
  TITLE      {Fossil Merge}
  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    #d0d070
  ADD_BG     #c0ffc0
  RM_BG      #ffc0c0
  HR_FG      #444444
  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
}

array set CFG_dark {
  TITLE      {Fossil Merge}
  LN_COL_BG  #dddddd
  LN_COL_FG  #444444
  TXT_COL_BG #3f3f3f
  TXT_COL_FG #dcdccc
  MKR_COL_BG #444444
  MKR_COL_FG #dddddd
  CHNG_BG    #6a6a00
  ADD_BG     #57934c
  RM_BG      #ef6767
  HR_FG      #444444
  HR_PAD_TOP 4
  HR_PAD_BTM 8
  FN_BG      #5e5e5e
  FN_FG      #ffffff
  FN_PAD     5
  ERR_FG     #ee0000
  PADX       5
  WIDTH      80
  HEIGHT     45
  LB_HEIGHT  25
}

array set CFG_arr {
  0          CFG_light
  1          CFG_dark
}

array set CFG [array get $CFG_arr($darkmode)]

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 .lnB .txtB .lnC .txtC .lnD .txtD]
}

proc colType {c} {
  regexp {[a-z]+} $c type
  return $type
}

proc readMerge {args} {
  global fossilexe ncontext current_file debug
  if {$ncontext=="All"} {
    set cmd "| $fossilexe merge-info -c -1"
  } else {
    set cmd "| $fossilexe merge-info -c $ncontext"
  }
  if {[info exists current_file]} {
    regsub {^[A-Z]+ } $current_file {} fn
    lappend cmd -tcl $fn
  }
  if {$debug} {
    regsub {^\| +} $cmd {} cmd2
    puts $cmd2
    flush stdout
  }
  if {[catch {
    set in [open $cmd r]
    fconfigure $in -encoding utf-8
    set mergetxt [read $in]
    close $in
  } msg]} {
    tk_messageBox -message "Unable to run command: \"$cmd\""
    set mergetxt {}
  }
  foreach c [cols] {
    $c config -state normal
    $c delete 1.0 end
  }
  set lnA 1
  set lnB 1
  set lnC 1
  set lnD 1
  foreach {A B C D} $mergetxt {
    set key1 [string index $A 0]
    if {$key1=="S"} {
      scan [string range $A 1 end] "%d %d %d %d" nA nB nC nD
      foreach x {A B C D} {
        set N [set n$x]
        incr ln$x $N
        if {$N>0} {
          .ln$x insert end ...\n hrln
          .txt$x insert end [string repeat . 30]\n hrtxt
        } else {
          .ln$x insert end \n hrln
          .txt$x insert end \n hrtxt
        }
      }
      continue
    }
    set key2 [string index $B 0]
    set key3 [string index $C 0]
    set key4 [string index $D 0]
    if {$key1=="."} {
      .lnA insert end \n -
      .txtA insert end \n -
    } elseif {$key1=="N"} {
      .nameA config -text [string range $A 1 end]
    } else {
      .lnA insert end $lnA\n -
      incr lnA
      if {$key1=="X"} {
        .txtA insert end [string range $A 1 end]\n rm
      } else {
        .txtA insert end [string range $A 1 end]\n -
      }
    }
    if {$key2=="."} {
      .lnB insert end \n -
      .txtB insert end \n -
    } elseif {$key2=="N"} {
      .nameB config -text [string range $B 1 end]
    } else {
      .lnB insert end $lnB\n -
      incr lnB
      if {$key4=="2"} {set tag chng} {set tag -}
      if {$key2=="1"} {
        .txtB insert end [string range $A 1 end]\n $tag
      } elseif {$key2=="X"} {
        .txtB insert end [string range $B 1 end]\n rm
      } else {
        .txtB insert end [string range $B 1 end]\n $tag
      }
    }
    if {$key3=="."} {
      .lnC insert end \n -
      .txtC insert end \n -
    } elseif {$key3=="N"} {
      .nameC config -text [string range $C 1 end]
    } else {
      .lnC insert end $lnC\n -
      incr lnC
      if {$key4=="3"} {set tag add} {set tag -}
      if {$key3=="1"} {
        .txtC insert end [string range $A 1 end]\n $tag
      } elseif {$key3=="2"} {
        .txtC insert end [string range $B 1 end]\n chng
      } elseif {$key3=="X"} {
        .txtC insert end [string range $C 1 end]\n rm
      } else {
        .txtC insert end [string range $C 1 end]\n $tag
      }
    }
    if {$key4=="."} {
      .lnD insert end \n -
      .txtD insert end \n -
    } elseif {$key4=="N"} {
      .nameD config -text [string range $D 1 end]
    } else {
      .lnD insert end $lnD\n -
      incr lnD
      if {$key4=="1"} {
        .txtD insert end [string range $A 1 end]\n -
      } elseif {$key4=="2"} {
        .txtD insert end [string range $B 1 end]\n chng
      } elseif {$key4=="3"} {
        .txtD insert end [string range $C 1 end]\n add
      } elseif {$key4=="X"} {
        .txtD insert end [string range $D 1 end]\n rm
      } else {
        .txtD insert end [string range $D 1 end]\n -
      }
    }
  }
  foreach c [cols] {
    set type [colType $c]
    if {$type ne "txt"} {
      $c config -width 6; # $widths($type)
    }
    $c config -state disabled
  }
  set mx $lnA
  if {$lnB>$mx} {set mx $lnB}
  if {$lnC>$mx} {set mx $lnC}
  if {$lnD>$mx} {set mx $lnD}
  global lnWidth
  set lnWidth [string length [format +%d $mx]]
  .lnA config -width $lnWidth
  .lnB config -width $lnWidth
  .lnC config -width $lnWidth
  .lnD config -width $lnWidth
  grid columnconfig . {0 2 4 6} -minsize $lnWidth
}

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-y {first last} {
  disableSync y
  foreach c [cols] {
    $c yview moveto $first
  }
  if {$first > 0 || $last < 1} {
    grid .sby
    .sby set $first $last
  } else {
    grid remove .sby
  }
  enableSync y
}

wm withdraw .
wm title . $CFG(TITLE)
wm iconname . $CFG(TITLE)
# Keystroke bindings for on the top-level window for navigation and
# control also fire when those same keystrokes are pressed in the
# Search entry box.  Disable them, to prevent the diff screen from
# disappearing abruptly and unexpectedly when searching for "q".
#
bind . <Control-q> exit
bind . <Control-p> {catch searchPrev; break}
bind . <Control-n> {catch searchNext; break}
bind . <Escape><Escape> exit
bind . <Destroy> {after 0 exit}
bind . <Tab> {cycleDiffs; break}
bind . <<PrevWindow>> {cycleDiffs 1; break}
bind . <Control-f> {searchOnOff; break}
bind . <Control-g> {catch searchNext; break}
bind . <Return> {
  event generate .bb.files <1>
  event generate .bb.files <ButtonRelease-1>
  break
}
foreach {key axis args} {
  Up    y {scroll -5 units}
  k     y {scroll -5 units}
  Down  y {scroll 5 units}
  j     y {scroll 5 units}
  Left  x {scroll -5 units}
  h     x {scroll -5 units}
  Right x {scroll 5 units}
  l     x {scroll 5 units}
  Prior y {scroll -1 page}
  b     y {scroll -1 page}
  Next  y {scroll 1 page}
  space y {scroll 1 page}
  Home  y {moveto 0}
  g     y {moveto 0}
  End   y {moveto 1}
} {
  bind . <$key> "scroll-$axis $args; break"
  bind . <Shift-$key> continue
}

frame .bb
::ttk::menubutton .bb.diff2 -text {2-way diff} -menu .bb.diff2.m
menu .bb.diff2.m -tearoff 0
.bb.diff2.m add command -label {baseline vs. local} -command {two-way 12}
.bb.diff2.m add command -label {baseline vs. merge-in} -command {two-way 13}
.bb.diff2.m add command -label {local vs. merge-in} -command {two-way 23}

# Bring up a separate two-way diff between a pair of columns
# the argument is one of:
#    12       Baseline versus Local
#    13       Baseline versus Merge-in
#    23       Local versus Merge-in
#
proc two-way {mode} {
  global current_file fossilexe debug darkmode ncontext
  regsub {^[A-Z]+ } $current_file {} fn
  set cmd $fossilexe
  lappend cmd merge-info --diff$mode $fn -c $ncontext
  if {$darkmode} {
    lappend cmd --dark
  }
  if {$debug} {
    lappend cmd --tkdebug
    puts $cmd
    flush stdout
  }
  exec {*}$cmd &
}

set useOptionMenu 1
if {[info exists filelist]} {
  set current_file "[lindex $filelist 0] [lindex $filelist 1]"
  if {[llength $filelist]>2} {
    trace add variable current_file write readMerge
  
    if {$tcl_platform(os)=="Darwin" || [llength $filelist]<30} {
      set fnlist {}
      foreach {op fn} $filelist {lappend fnlist "$op $fn"}
      tk_optionMenu .bb.files current_file {*}$fnlist
    } else {
      set useOptionMenu 0
      ::ttk::menubutton .bb.files -text $current_file
      if {[tk windowingsystem] eq "win32"} {
        ::ttk::style theme use winnative
        .bb.files configure -padding {20 1 10 2}
      }
      toplevel .wfiles
      wm withdraw .wfiles
      update idletasks
      wm transient .wfiles .
      wm overrideredirect .wfiles 1
      set ht [expr {[llength $filelist]/2}]
      if {$ht>$CFG(LB_HEIGHT)} {set ht $CFG(LB_HEIGHT)}
      listbox .wfiles.lb -width 0 -height $ht -activestyle none \
        -yscroll {.wfiles.sb set}
      set mx 1
      foreach {op fn} $filelist {
        set n [string length $fn]
        if {$n>$mx} {set mx $n}
        .wfiles.lb insert end "$op $fn"
      }
      .bb.files config -width $mx
      ::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}
      bind .wfiles <Escape> {focus .}
      foreach evt {1 Return} {
        bind .wfiles.lb <$evt> {
          set ii [%W curselection]
          set ::current_file [%W get $ii]
          .bb.files config -text $::current_file
          focus .
          break
        }
      }
      bind .wfiles.lb <Motion> {
        %W selection clear 0 end
        %W selection set @%x,%y
      }
    }
  }
}

label .bb.ctxtag -text "Context:"
set context_choices {3 6 12 25 50 100 All}
if {$ncontext<0} {set ncontext All}
trace add variable ncontext write readMerge
if {$tcl_platform(os)=="Darwin" || $useOptionMenu} {
  tk_optionMenu .bb.ctx ncontext {*}$context_choices
} else {
  ::ttk::menubutton .bb.ctx -text $ncontext
  if {[tk windowingsystem] eq "win32"} {
    ::ttk::style theme use winnative
    .bb.ctx configure -padding {20 1 10 2}
  }
  toplevel .wctx
  wm withdraw .wctx
  update idletasks
  wm transient .wctx .
  wm overrideredirect .wctx 1
  listbox .wctx.lb -width 0 -height 7 -activestyle none
  .wctx.lb insert end {*}$context_choices
  pack .wctx.lb
  bind .bb.ctx <1> {
    set x [winfo rootx %W]
    set y [expr {[winfo rooty %W]+[winfo height %W]}]
    wm geometry .wctx +$x+$y
    wm deiconify .wctx
    focus .wctx.lb
  }
  bind .wctx <FocusOut> {wm withdraw .wctx}
  bind .wctx <Escape> {focus .}
  foreach evt {1 Return} {
    bind .wctx.lb <$evt> {
      set ::ncontext [lindex $::context_choices [%W curselection]]
      .bb.ctx config -text $::ncontext
      focus .
      break
    }
  }
  bind .wctx.lb <Motion> {
    %W selection clear 0 end
    %W selection set @%x,%y
  }
}

foreach {side syncCol} {A .txtA B .txtB C .txtC D .txtD} {
  set ln .ln$side
  text $ln -width 6
  $ln tag config - -justify right

  set txt .txt$side
  text $txt -width $CFG(WIDTH) -height $CFG(HEIGHT) -wrap none \
    -xscroll ".sbx$side set"
  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

set mxwidth [lindex [wm maxsize .] 0]
while {$CFG(WIDTH)>=40} {
  set wanted [expr {([winfo reqwidth .lnA]+[winfo reqwidth .txtA])*4+30}]
  if {$wanted<=$mxwidth} break
  incr CFG(WIDTH) -10
  .txtA config -width $CFG(WIDTH)
  .txtB config -width $CFG(WIDTH)
  .txtC config -width $CFG(WIDTH)
  .txtD config -width $CFG(WIDTH)
}

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 hrln -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \
     -foreground $CFG(HR_FG) -justify right
  $c tag config hrtxt  -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \
     -foreground $CFG(HR_FG) -justify center
  $c tag config fn -spacing1 $CFG(FN_PAD) -spacing3 $CFG(FN_PAD)
  bindtags $c ". $c Text all"
  bind $c <1> {focus %W}
}

label .nameA
label .nameB
label .nameC
label .nameD -text {Merge Result}
::ttk::scrollbar .sby -command {.txtA yview} -orient vertical
::ttk::scrollbar .sbxA -command {.txtA xview} -orient horizontal
::ttk::scrollbar .sbxB -command {.txtB xview} -orient horizontal
::ttk::scrollbar .sbxC -command {.txtC xview} -orient horizontal
::ttk::scrollbar .sbxD -command {.txtD xview} -orient horizontal
frame .spacer

update idletasks

proc searchOnOff {} {
  if {[info exists ::search]} {
    unset ::search
    .txtA tag remove search 1.0 end
    .txtB tag remove search 1.0 end
    .txtC tag remove search 1.0 end
    .txtD tag remove search 1.0 end
    pack forget .bb.sframe
    focus .
  } else {
    set ::search .txtA
    if {![winfo exists .bb.sframe]} {
      frame .bb.sframe
      ::ttk::entry .bb.sframe.e -width 10
      pack .bb.sframe.e -side left -fill y -expand 1
      bind .bb.sframe.e <Return> {searchNext; break}
      ::ttk::button .bb.sframe.nx -text \u2193 -width 1 -command searchNext
      ::ttk::button .bb.sframe.pv -text \u2191 -width 1 -command searchPrev
      tk_optionMenu .bb.sframe.typ ::search_type \
           Exact {No Case} {RegExp} {Whole Word}
      .bb.sframe.typ config -width 10
      set ::search_type Exact
      pack .bb.sframe.nx .bb.sframe.pv .bb.sframe.typ -side left
    }
    pack .bb.sframe -side left
    after idle {focus .bb.sframe.e}
  }
}
proc searchNext {} {searchStep -forwards +1 1.0 end}
proc searchPrev {} {searchStep -backwards -1 end 1.0}
proc searchStep {direction incr start stop} {
  set pattern [.bb.sframe.e get]
  if {$pattern==""} return
  set count 0
  set w $::search
  switch $w {
    .txtA {set other .txtB}
    .txtB {set other .txtC}
    .txtC {set other .txtD}
    default {set other .txtA}
  }
  if {[lsearch [$w mark names] search]<0} {
    $w mark set search $start
  }
  switch $::search_type {
    Exact        {set st -exact}
    {No Case}    {set st -nocase}
    {RegExp}     {set st -regexp}
    {Whole Word} {set st -regexp; set pattern \\y$pattern\\y}
  }
  set idx [$w search -count count $direction $st -- \
              $pattern "search $incr chars" $stop]
  if {"$idx"==""} {
    set idx [$other search -count count $direction $st -- $pattern $start $stop]
    if {"$idx"!=""} {
      set this $w
      set w $other
      set other $this
    } else {
      set idx [$w search -count count $direction $st -- $pattern $start $stop]
    }
  }
  $w tag remove search 1.0 end
  $w mark unset search
  $other tag remove search 1.0 end
  $other mark unset search
  if {"$idx"!=""} {
    $w mark set search $idx
    $w yview -pickplace $idx
    $w tag add search search "$idx +$count chars"
    $w tag config search -background {#fcc000}
  }
  set ::search $w
}
::ttk::button .bb.quit -text {Quit} -command exit
::ttk::button .bb.search -text {Search} -command searchOnOff
pack .bb.quit -side left -fill y
pack .bb.diff2 -side left -fill y
if {[winfo exists .bb.files]} {
  pack .bb.files -side left -fill y
}
pack .bb.ctxtag .bb.ctx -side left -fill y
pack .bb.search -side left -fill y
grid rowconfigure . 1 -weight 1 -minsize [winfo reqheight .nameA]
grid rowconfigure . 2 -weight 100
readMerge
grid .bb -row 0 -columnspan 8
grid .nameA -row 1 -column 1 -sticky ew
grid .nameB -row 1 -column 3 -sticky ew
grid .nameC -row 1 -column 5 -sticky ew
grid .nameD -row 1 -column 7 -sticky ew
eval grid [cols] -row 2 -sticky nsew
grid .sby -row 2 -column 8 -sticky ns
grid .sbxA -row 3 -column 1 -sticky ew
grid .sbxB -row 3 -column 3 -sticky ew
grid .sbxC -row 3 -column 5 -sticky ew
grid .sbxD -row 3 -column 7 -sticky ew
grid columnconfigure . {0 2 4 6} \
   -weight 1 -uniform a -minsize [winfo reqwidth .lnA]
grid columnconfigure . {1 3 5 7} -weight 100 -uniform b

.spacer config -height [winfo height .sbxA]
wm deiconify .
Changes to src/merge3.c.
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
73
74
75
76
77
78
79
80



























































81
82
83
84
85
86
87
88
89
90
91
92
93








-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-





+







  if( aC1[0]!=aC2[0] ) return 0;
  if( aC1[1]!=aC2[1] ) return 0;
  if( aC1[2]!=aC2[2] ) return 0;
  if( sameLines(pV1, pV2, aC1[2]) ) return 1;
  return 0;
}

/*
** The aC[] array contains triples of integers.  Within each triple, the
** elements are:
**
**   (0)  The number of lines to copy
**   (1)  The number of lines to delete
**   (2)  The number of liens to insert
**
** Suppose we want to advance over sz lines of the original file.  This routine
** returns true if that advance would land us on a copy operation.  It
** returns false if the advance would end on a delete.
*/
static int ends_at_CPY(int *aC, int sz){
  while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){
    if( aC[0]>=sz ) return 1;
    sz -= aC[0];
    if( aC[1]>sz ) return 0;
    sz -= aC[1];
    aC += 3;
  }
  return 1;
}

/*
** pSrc contains an edited file where aC[] describes the edit.  Part of
** pSrc has already been output.  This routine outputs additional lines
** of pSrc - lines that correspond to the next sz lines of the original
** unedited file.
**
** Note that sz counts the number of lines of text in the original file.
** But text is output from the edited file.  So the number of lines transfer
** to pOut might be different from sz.  Fewer lines appear in pOut if there
** are deletes.  More lines appear if there are inserts.
**
** The aC[] array is updated and the new index into aC[] is returned.
*/
static int output_one_side(
  Blob *pOut,     /* Write to this blob */
  Blob *pSrc,     /* The edited file that is to be copied to pOut */
  int *aC,        /* Array of integer triples describing the edit */
  int i,          /* Index in aC[] of current location in pSrc */
  int sz,         /* Number of lines in unedited source to output */
  int *pLn        /* Line number counter */
){
  while( sz>0 ){
    if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break;
    if( aC[i]>=sz ){
      blob_copy_lines(pOut, pSrc, sz);  *pLn += sz;
      aC[i] -= sz;
      break;
    }
    blob_copy_lines(pOut, pSrc, aC[i]);      *pLn += aC[i];
    blob_copy_lines(pOut, pSrc, aC[i+2]);    *pLn += aC[i+2];
    sz -= aC[i] + aC[i+1];
    i += 3;
  }
  return i;
}

/*
** Text of boundary markers for merge conflicts.
*/
static const char *const mergeMarker[] = {
 /*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/
  "<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<",
  "####### SUGGESTED CONFLICT RESOLUTION follows ###################",
  "||||||| COMMON ANCESTOR content follows |||||||||||||||||||||||||",
  "======= MERGED IN content follows ===============================",
  ">>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
};

/*
** Return true if the input blob contains any CR/LF pairs on the first
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
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
381
382
383
384
385
386
387
388

389

390
391
392
393
394
395
396
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
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
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
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
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
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

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

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
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891

892
893
894
895
896
897
898
899







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+













-
+






-
-


-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-









+
-
-
+
+






-
-
-
+
+
+


+
+
-
+
+
+
+

+
-
+
-
-
+
-
-
-
-
-
+
+
-
-
-
+
+
+







-

-
-
-
-



-
+
-
-
-







-
+
-
-
-







-
+
-
-
-



-
+




-
+
-
-
-








-
+
+

-
+


-
-
+
-
-
-
+
-
-
-
+
-
-
-
+
-
-
+
-











-
-
-

-
-
+

-
-
+

+
+
+


















+
-
+







*/
void append_merge_mark(Blob *pOut, int iMark, int ln, int useCrLf){
  ensure_line_end(pOut, useCrLf);
  blob_append(pOut, mergeMarker[iMark], -1);
  if( ln>0 ) blob_appendf(pOut, " (line %d)", ln);
  ensure_line_end(pOut, useCrLf);
}

#if INTERFACE
/*
** This is an abstract class for constructing a merge.
** Subclasses of this object format the merge output in different ways.
**
** To subclass, create an instance of the MergeBuilder object and fill
** in appropriate method implementations.
*/
struct MergeBuilder {
  void (*xStart)(MergeBuilder*);
  void (*xSame)(MergeBuilder*, unsigned int);
  void (*xChngV1)(MergeBuilder*, unsigned int, unsigned int);
  void (*xChngV2)(MergeBuilder*, unsigned int, unsigned int);
  void (*xChngBoth)(MergeBuilder*, unsigned int, unsigned int);
  void (*xConflict)(MergeBuilder*, unsigned int, unsigned int, unsigned int);
  void (*xEnd)(MergeBuilder*);
  void (*xDestroy)(MergeBuilder*);
  const char *zPivot;        /* Label or name for the pivot */
  const char *zV1;           /* Label or name for the V1 file */
  const char *zV2;           /* Label or name for the V2 file */
  const char *zOut;          /* Label or name for the output */
  Blob *pPivot;              /* The common ancestor */
  Blob *pV1;                 /* First variant (local copy) */
  Blob *pV2;                 /* Second variant (merged in) */
  Blob *pOut;                /* Write merge results here */
  int useCrLf;               /* Use CRLF line endings */
  int nContext;              /* Size of unchanged line boundaries */
  unsigned int mxPivot;      /* Number of lines in the pivot */
  unsigned int mxV1;         /* Number of lines in V1 */
  unsigned int mxV2;         /* Number of lines in V2 */
  unsigned int lnPivot;      /* Lines read from pivot */
  unsigned int lnV1;         /* Lines read from v1 */
  unsigned int lnV2;         /* Lines read from v2 */
  unsigned int lnOut;        /* Lines written to out */
  unsigned int nConflict;    /* Number of conflicts seen */
  u64 diffFlags;             /* Flags for difference engine */
};
#endif /* INTERFACE */


/************************* Generic MergeBuilder ******************************/
/* These are generic methods for MergeBuilder.  They just output debugging
** information.  But some of them are useful as base methods for other useful
** implementations of MergeBuilder.
*/

/* xStart() and xEnd() are called to generate header and footer information
** in the output.  This is a no-op in the generic implementation.
*/
static void dbgStartEnd(MergeBuilder *p){  (void)p; }

/* The next N lines of PIVOT are unchanged in both V1 and V2
*/
static void dbgSame(MergeBuilder *p, unsigned int N){
  blob_appendf(p->pOut, 
     "COPY %u from BASELINE(%u..%u) or V1(%u..%u) or V2(%u..%u)\n",
     N, p->lnPivot+1, p->lnPivot+N, p->lnV1+1, p->lnV1+N,
     p->lnV2+1, p->lnV2+N);
  p->lnPivot += N;
  p->lnV1 += N;
  p->lnV2 += N;
}

/* The next nPivot lines of the PIVOT are changed into nV1 lines by V1
*/
static void dbgChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
  blob_appendf(p->pOut, "COPY %u from V1(%u..%u)\n",
               nV1, p->lnV1+1, p->lnV1+nV1);
  p->lnPivot += nPivot;
  p->lnV2 += nPivot;
  p->lnV1 += nV1;
}

/* The next nPivot lines of the PIVOT are changed into nV2 lines by V2
*/
static void dbgChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
  blob_appendf(p->pOut, "COPY %u lines FROM V2(%u..%u)\n",
               nV2, p->lnV2+1, p->lnV2+nV2);
  p->lnPivot += nPivot;
  p->lnV1 += nPivot;
  p->lnV2 += nV2;
}

/* The next nPivot lines of the PIVOT are changed into nV lines from V1 and
** V2, which should be the same.  In other words, the same change is found
** in both V1 and V2.
*/
static void dbgChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
  blob_appendf(p->pOut, "COPY %u lines from V1(%u..%u) or V2(%u..%u)\n",
               nV, p->lnV1+1, p->lnV1+nV, p->lnV2+1, p->lnV2+nV);
  p->lnPivot += nPivot;
  p->lnV1 += nV;
  p->lnV2 += nV;
}

/* V1 and V2 have different and overlapping changes.  The next nPivot lines
** of the PIVOT are converted into nV1 lines of V1 and nV2 lines of V2.
*/
static void dbgConflict(
  MergeBuilder *p,
  unsigned int nPivot,
  unsigned int nV1,
  unsigned int nV2
){
  blob_appendf(p->pOut, 
   "CONFLICT %u,%u,%u BASELINE(%u..%u) versus V1(%u..%u) versus V2(%u..%u)\n",
       nPivot, nV1, nV2,
       p->lnPivot+1, p->lnPivot+nPivot,
       p->lnV1+1, p->lnV1+nV1,
       p->lnV2+1, p->lnV2+nV2);
  p->lnV1 += nV1;
  p->lnPivot += nPivot;
  p->lnV2 += nV2;
}

/* Generic destructor for the MergeBuilder object
*/
static void dbgDestroy(MergeBuilder *p){
  memset(p, 0, sizeof(*p));
}

/* Generic initializer for a MergeBuilder object
*/
static void mergebuilder_init(MergeBuilder *p){
  memset(p, 0, sizeof(*p));
  p->xStart = dbgStartEnd;
  p->xSame = dbgSame;
  p->xChngV1 = dbgChngV1;
  p->xChngV2 = dbgChngV2;
  p->xChngBoth = dbgChngBoth;
  p->xConflict = dbgConflict;
  p->xEnd = dbgStartEnd;
  p->xDestroy = dbgDestroy;
}

/************************* MergeBuilderToken ********************************/
/* This version of MergeBuilder actually performs a merge on file that
** are broken up into tokens instead of lines, and puts the result in pOut.
*/
static void tokenSame(MergeBuilder *p, unsigned int N){
  blob_append(p->pOut, p->pPivot->aData+p->pPivot->iCursor, N);
  p->pPivot->iCursor += N;
  p->pV1->iCursor += N;
  p->pV2->iCursor += N;
}
static void tokenChngV1(MergeBuilder *p, unsigned int nPivot, unsigned nV1){
  blob_append(p->pOut, p->pV1->aData+p->pV1->iCursor, nV1);
  p->pPivot->iCursor += nPivot;
  p->pV1->iCursor += nV1;
  p->pV2->iCursor += nPivot;
}
static void tokenChngV2(MergeBuilder *p, unsigned int nPivot, unsigned nV2){
  blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV2);
  p->pPivot->iCursor += nPivot;
  p->pV1->iCursor += nPivot;
  p->pV2->iCursor += nV2;
}
static void tokenChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned nV){
  blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV);
  p->pPivot->iCursor += nPivot;
  p->pV1->iCursor += nV;
  p->pV2->iCursor += nV;
}
static void tokenConflict(
  MergeBuilder *p,
  unsigned int nPivot,
  unsigned int nV1,
  unsigned int nV2
){
  /* For a token-merge conflict, use the text from the merge-in */
  blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV2);
  p->pPivot->iCursor += nPivot;
  p->pV1->iCursor += nV1;
  p->pV2->iCursor += nV2;
}
static void mergebuilder_init_token(MergeBuilder *p){
  mergebuilder_init(p);
  p->xSame = tokenSame;
  p->xChngV1 = tokenChngV1;
  p->xChngV2 = tokenChngV2;
  p->xChngBoth = tokenChngBoth;
  p->xConflict = tokenConflict;
  p->diffFlags = DIFF_BY_TOKEN;
}

/*
** Attempt to do a low-level merge on a conflict.  The conflict is
** described by the first four parameters, which are the same as the
** arguments to the xConflict method of the MergeBuilder object.
** This routine attempts to resolve the conflict by looking at
** elements of the conflict region that are finer grain than complete
** lines of text.
**
** The result is written into Blob pOut.  pOut is initialized by this
** routine.
*/
int merge_try_to_resolve_conflict(
  MergeBuilder *pMB,     /* MergeBuilder that encounter conflict */
  unsigned int nPivot,   /* Lines of conflict in the pivot */
  unsigned int nV1,      /* Lines of conflict in V1 */
  unsigned int nV2,      /* Lines of conflict in V2 */
  Blob *pOut             /* Write resolution text here */
){
  int nConflict;
  MergeBuilder mb;
  Blob pv, v1, v2;
  mergebuilder_init_token(&mb);
  blob_extract_lines(pMB->pPivot, nPivot, &pv);
  blob_extract_lines(pMB->pV1, nV1, &v1);
  blob_extract_lines(pMB->pV2, nV2, &v2);
  blob_zero(pOut);
  blob_materialize(&pv);
  blob_materialize(&v1);
  blob_materialize(&v2);
  mb.pPivot = &pv;
  mb.pV1 = &v1;
  mb.pV2 = &v2;
  mb.pOut = pOut;
  nConflict = merge_three_blobs(&mb);
  blob_reset(&pv);
  blob_reset(&v1);
  blob_reset(&v2);
  /* mb has not allocated any resources, so we do not need to invoke
  ** the xDestroy method. */
  blob_add_final_newline(pOut);
  return nConflict;
}


/************************* MergeBuilderText **********************************/
/* This version of MergeBuilder actually performs a merge on file and puts
** the result in pOut
*/
static void txtStart(MergeBuilder *p){
  /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM),
  ** keep it in the output. This should be secure enough not to cause
  ** unintended changes to the merged file and consistent with what
  ** users are using in their source files.
  */
  if( starts_with_utf8_bom(p->pV1, 0) && starts_with_utf8_bom(p->pV2, 0) ){
    blob_append(p->pOut, (char*)get_utf8_bom(0), -1);
  }
  if( contains_crlf(p->pV1) && contains_crlf(p->pV2) ){
    p->useCrLf = 1;
  }
}
static void txtSame(MergeBuilder *p, unsigned int N){
  blob_copy_lines(p->pOut, p->pPivot, N);  p->lnPivot += N;
  blob_copy_lines(0, p->pV1, N);           p->lnV1 += N;
  blob_copy_lines(0, p->pV2, N);           p->lnV2 += N;
}
static void txtChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
  blob_copy_lines(0, p->pPivot, nPivot);   p->lnPivot += nPivot;
  blob_copy_lines(0, p->pV2, nPivot);      p->lnV2 += nPivot;
  blob_copy_lines(p->pOut, p->pV1, nV1);   p->lnV1 += nV1;
}
static void txtChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
  blob_copy_lines(0, p->pPivot, nPivot);   p->lnPivot += nPivot;
  blob_copy_lines(0, p->pV1, nPivot);      p->lnV1 += nPivot;
  blob_copy_lines(p->pOut, p->pV2, nV2);   p->lnV2 += nV2;
}
static void txtChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
  blob_copy_lines(0, p->pPivot, nPivot);   p->lnPivot += nPivot;
  blob_copy_lines(0, p->pV1, nV);          p->lnV1 += nV;
  blob_copy_lines(p->pOut, p->pV2, nV);    p->lnV2 += nV;
}
static void txtConflict(
  MergeBuilder *p,
  unsigned int nPivot,
  unsigned int nV1,
  unsigned int nV2
){
  int nRes;   /* Lines in the computed conflict resolution */
  Blob res;   /* Text of the conflict resolution */
  
  merge_try_to_resolve_conflict(p, nPivot, nV1, nV2, &res);
  nRes = blob_linecount(&res);

  append_merge_mark(p->pOut, 0, p->lnV1+1, p->useCrLf);
  blob_copy_lines(p->pOut, p->pV1, nV1);         p->lnV1 += nV1;

  if( nRes>0 ){
    append_merge_mark(p->pOut, 1, 0, p->useCrLf);
    blob_copy_lines(p->pOut, &res, nRes);
  }
  blob_reset(&res);

  append_merge_mark(p->pOut, 2, p->lnPivot+1, p->useCrLf);
  blob_copy_lines(p->pOut, p->pPivot, nPivot);   p->lnPivot += nPivot;

  append_merge_mark(p->pOut, 3, p->lnV2+1, p->useCrLf);
  blob_copy_lines(p->pOut, p->pV2, nV2);         p->lnV2 += nV2;

  append_merge_mark(p->pOut, 4, -1, p->useCrLf);
}
static void mergebuilder_init_text(MergeBuilder *p){
  mergebuilder_init(p);
  p->xStart = txtStart;
  p->xSame = txtSame;
  p->xChngV1 = txtChngV1;
  p->xChngV2 = txtChngV2;
  p->xChngBoth = txtChngBoth;
  p->xConflict = txtConflict;
}

/************************* MergeBuilderTcl **********************************/
/* Generate merge output formatted for reading by a TCL script.
**
** The output consists of lines of text, each with 4 tokens.  The tokens
** represent the content for one line from baseline, v1, v2, and output
** respectively.  The first character of each token provides auxiliary
** information:
**
**     .     This line is omitted.
**     N     Name of the file.
**     T     Literal text follows that should have a \n terminator.
**     R     Literal text follows that needs a \r\n terminator.
**     X     Merge conflict.
**     Z     Literal text without a line terminator.
**     S     Skipped lines.  Followed by number of lines to skip.
**     1     Text is a copy of token 1
**     2     Use data from data-token 2
**     3     Use data from data-token 3
*/

/* Write text that goes into the interior of a double-quoted string in TCL */
static void tclWriteQuotedText(Blob *pOut, const char *zIn, int nIn){
  int j;
  for(j=0; j<nIn; j++){
    char c = zIn[j];
    if( c=='\\' ){
      blob_append(pOut, "\\\\", 2);
    }else if( c=='"' ){
      blob_append(pOut, "\\\"", 2);
    }else if( c<' ' || c>0x7e ){
      char z[5];
      z[0] = '\\';
      z[1] = "01234567"[(c>>6)&0x3];
      z[2] = "01234567"[(c>>3)&0x7];
      z[3] = "01234567"[c&0x7];
      z[4] = 0;
      blob_append(pOut, z, 4);
    }else{
      blob_append_char(pOut, c);
    }
  }
}

/* Copy one line of text from pIn and append to pOut, encoded as TCL */
static void tclLineOfText(Blob *pOut, Blob *pIn, char cType){
  int i, k;
  for(i=pIn->iCursor; i<pIn->nUsed && pIn->aData[i]!='\n'; i++){}
  if( i==pIn->nUsed ){
    k = i;
  }else if( i>pIn->iCursor && pIn->aData[i-1]=='\r' ){
    k = i-1;
    i++;
  }else{
    k = i;
    i++;
  }
  blob_append_char(pOut, '"');
  blob_append_char(pOut, cType);
  tclWriteQuotedText(pOut, pIn->aData+pIn->iCursor, k-pIn->iCursor);
  pIn->iCursor = i;
  blob_append_char(pOut, '"');
}
static void tclStart(MergeBuilder *p){
  Blob *pOut = p->pOut;
  blob_append(pOut, "\"N", 2);
  tclWriteQuotedText(pOut, p->zPivot, (int)strlen(p->zPivot));
  blob_append(pOut, "\" \"N", 4);
  tclWriteQuotedText(pOut, p->zV1, (int)strlen(p->zV1));
  blob_append(pOut, "\" \"N", 4);
  tclWriteQuotedText(pOut, p->zV2, (int)strlen(p->zV2));
  blob_append(pOut, "\" \"N", 4);
  if( p->zOut ){
    tclWriteQuotedText(pOut, p->zOut, (int)strlen(p->zOut));
  }else{
    blob_append(pOut, "(Merge Result)", -1);
  }
  blob_append(pOut, "\"\n", 2);
}
static void tclSame(MergeBuilder *p, unsigned int N){
  int i = 0;
  int nSkip;

  if( p->lnPivot>=2 || p->lnV1>2 || p->lnV2>2 ){
    while( i<N && i<p->nContext ){
      tclLineOfText(p->pOut, p->pPivot, 'T');
      blob_append(p->pOut, " 1 1 1\n", 7);
      i++;
    }
    nSkip = N - p->nContext*2;
  }else{
    nSkip = N - p->nContext;
  }
  if( nSkip>0 ){
    blob_appendf(p->pOut, "\"S%d %d %d %d\" . . .\n",
                 nSkip, nSkip, nSkip, nSkip);
    blob_copy_lines(0, p->pPivot, nSkip);
    i += nSkip;
  }

  p->lnPivot += N;
  p->lnV1 += N;
  p->lnV2 += N;

  if( p->lnPivot<p->mxPivot || p->lnV1<p->mxV1 || p->lnV2<p->mxV2 ){
    while( i<N ){
      tclLineOfText(p->pOut, p->pPivot, 'T');
      blob_append(p->pOut, " 1 1 1\n", 7);
      i++;
    }
  }

  blob_copy_lines(0, p->pV1, N);
  blob_copy_lines(0, p->pV2, N);
}
static void tclChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
  int i;
  for(i=0; i<nPivot && i<nV1; i++){
    tclLineOfText(p->pOut, p->pPivot, 'T');
    blob_append_char(p->pOut, ' ');
    tclLineOfText(p->pOut, p->pV1, 'T');
    blob_append(p->pOut, " 1 2\n", 5);
  }
  while( i<nPivot ){
    tclLineOfText(p->pOut, p->pPivot, 'T');
    blob_append(p->pOut, " . 1 .\n", 7);
    i++;
  }
  while( i<nV1 ){
    blob_append(p->pOut, ". ", 2);
    tclLineOfText(p->pOut, p->pV1, 'T');
    blob_append(p->pOut, " . 2\n", 5);
    i++;
  }
  p->lnPivot += nPivot;
  p->lnV1 += nV1;
  p->lnV2 += nPivot;
  blob_copy_lines(0, p->pV2, nPivot);
}
static void tclChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
  int i;
  for(i=0; i<nPivot && i<nV2; i++){
    tclLineOfText(p->pOut, p->pPivot, 'T');
    blob_append(p->pOut, " 1 ", 3);
    tclLineOfText(p->pOut, p->pV2, 'T');
    blob_append(p->pOut, " 3\n", 3);
  }
  while( i<nPivot ){
    tclLineOfText(p->pOut, p->pPivot, 'T');
    blob_append(p->pOut, " 1 . .\n", 7);
    i++;
  }
  while( i<nV2 ){
    blob_append(p->pOut, ". . ", 4);
    tclLineOfText(p->pOut, p->pV2, 'T');
    blob_append(p->pOut, " 3\n", 3);
    i++;
  }
  p->lnPivot += nPivot;
  p->lnV1 += nPivot;
  p->lnV2 += nV2;
  blob_copy_lines(0, p->pV1, nPivot);
}
static void tclChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
  int i;
  for(i=0; i<nPivot && i<nV; i++){
    tclLineOfText(p->pOut, p->pPivot, 'T');
    blob_append_char(p->pOut, ' ');
    tclLineOfText(p->pOut, p->pV1, 'T');
    blob_append(p->pOut, " 2 2\n", 5);
  }
  while( i<nPivot ){
    tclLineOfText(p->pOut, p->pPivot, 'T');
    blob_append(p->pOut, " . . .\n", 7);
    i++;
  }
  while( i<nV ){
    blob_append(p->pOut, ". ", 2);
    tclLineOfText(p->pOut, p->pV1, 'T');
    blob_append(p->pOut, " 2 2\n", 5);
    i++;
  }
  p->lnPivot += nPivot;
  p->lnV1 += nV;
  p->lnV2 += nV;
  blob_copy_lines(0, p->pV2, nV);
}
static void tclConflict(
  MergeBuilder *p,
  unsigned int nPivot,
  unsigned int nV1,
  unsigned int nV2
){
  int mx = nPivot;
  int i;
  int nRes;
  Blob res;
  
  merge_try_to_resolve_conflict(p, nPivot, nV1, nV2, &res);
  nRes = blob_linecount(&res);
  if( nV1>mx ) mx = nV1;
  if( nV2>mx ) mx = nV2;
  if( nRes>mx ) mx = nRes;
  if( nRes>0 ){
    blob_appendf(p->pOut, "\"S0 0 0 %d\" . . .\n", nV2+2);
  }
  for(i=0; i<mx; i++){
    if( i<nPivot ){
      tclLineOfText(p->pOut, p->pPivot, 'X');
    }else{
      blob_append_char(p->pOut, '.');
    }
    blob_append_char(p->pOut, ' ');
    if( i<nV1 ){
      tclLineOfText(p->pOut, p->pV1, 'X');
    }else{
      blob_append_char(p->pOut, '.');
    }
    blob_append_char(p->pOut, ' ');
    if( i<nV2 ){
      tclLineOfText(p->pOut, p->pV2, 'X');
    }else{
      blob_append_char(p->pOut, '.');
    }
    if( i<nRes ){
      blob_append_char(p->pOut, ' ');
      tclLineOfText(p->pOut, &res, 'X');
      blob_append_char(p->pOut, '\n');
    }else{
      blob_append(p->pOut, " .\n", 3);
    }
    if( i==mx-1 ){
      blob_appendf(p->pOut, "\"S0 0 0 %d\" . . .\n", nPivot+nV1+3);
    }
  }
  blob_reset(&res);
  p->lnPivot += nPivot;
  p->lnV1 += nV1;
  p->lnV2 += nV2;
}
void mergebuilder_init_tcl(MergeBuilder *p){
  mergebuilder_init(p);
  p->xStart = tclStart;
  p->xSame = tclSame;
  p->xChngV1 = tclChngV1;
  p->xChngV2 = tclChngV2;
  p->xChngBoth = tclChngBoth;
  p->xConflict = tclConflict;
}
/*****************************************************************************/

/*
** The aC[] array contains triples of integers.  Within each triple, the
** elements are:
**
**   (0)  The number of lines to copy
**   (1)  The number of lines to delete
**   (2)  The number of liens to insert
**
** Suppose we want to advance over sz lines of the original file.  This routine
** returns true if that advance would land us on a copy operation.  It
** returns false if the advance would end on a delete.
*/
static int ends_with_copy(int *aC, int sz){
  while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){
    if( aC[0]>=sz ) return 1;
    sz -= aC[0];
    if( aC[1]>sz ) return 0;
    sz -= aC[1];
    aC += 3;
  }
  return 1;
}

/*
** aC[] is an "edit triple" for changes from A to B.  Advance through
** this triple to determine the number of lines to bypass on B in order
** to match an advance of sz lines on A.
*/
static int skip_conflict(
  int *aC,             /* Array of integer triples describing the edit */
  int i,               /* Index in aC[] of current location */
  int sz,              /* Lines of A that have been skipped */
  unsigned int *pLn    /* OUT: Lines of B to skip to keep alignment with A */
){
  *pLn = 0;
  while( sz>0 ){
    if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break;
    if( aC[i]>=sz ){
      aC[i] -= sz;
      *pLn += sz;
      break;
    }
    *pLn += aC[i];
    *pLn += aC[i+2];
    sz -= aC[i] + aC[i+1];
    i += 3;
  }
  return i;
}

/*
** Do a three-way merge.  Initialize pOut to contain the result.
**
** The merge is an edit against pV2.  Both pV1 and pV2 have a
** common origin at pPivot.  Apply the changes of pPivot ==> pV1
** to pV2.
**
** The return is 0 upon complete success. If any input file is binary,
** -1 is returned and pOut is unmodified.  If there are merge
** conflicts, the merge proceeds as best as it can and the number
** of conflicts is returns
*/
static int blob_merge(Blob *pPivot, Blob *pV1, Blob *pV2, Blob *pOut){
int merge_three_blobs(MergeBuilder *p){
  int *aC1;              /* Changes from pPivot to pV1 */
  int *aC2;              /* Changes from pPivot to pV2 */
  int i1, i2;            /* Index into aC1[] and aC2[] */
  int nCpy, nDel, nIns;  /* Number of lines to copy, delete, or insert */
  int limit1, limit2;    /* Sizes of aC1[] and aC2[] */
  int nConflict = 0;     /* Number of merge conflicts seen so far */
  int useCrLf = 0;
  int ln1, ln2, lnPivot; /* Line numbers for all files */
  DiffConfig DCfg;

  blob_zero(pOut);         /* Merge results stored in pOut */

  /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM),
  ** keep it in the output. This should be secure enough not to cause
  ** unintended changes to the merged file and consistent with what
  ** users are using in their source files.
  */
  if( starts_with_utf8_bom(pV1, 0) && starts_with_utf8_bom(pV2, 0) ){
    blob_append(pOut, (char*)get_utf8_bom(0), -1);
  }

  /* Check once to see if both pV1 and pV2 contains CR/LF endings.
  ** If true, CR/LF pair will be used later to append the
  ** boundary markers for merge conflicts.
  */
  if( contains_crlf(pV1) && contains_crlf(pV2) ){
    useCrLf = 1;
  }

  /* Compute the edits that occur from pPivot => pV1 (into aC1)
  ** and pPivot => pV2 (into aC2).  Each of the aC1 and aC2 arrays is
  ** an array of integer triples.  Within each triple, the first integer
  ** is the number of lines of text to copy directly from the pivot,
  ** the second integer is the number of lines of text to omit from the
  ** pivot, and the third integer is the number of lines of text that are
  ** inserted.  The edit array ends with a triple of 0,0,0.
  */
  diff_config_init(&DCfg, 0);
  DCfg.diffFlags = p->diffFlags;
  aC1 = text_diff(pPivot, pV1, 0, &DCfg);
  aC2 = text_diff(pPivot, pV2, 0, &DCfg);
  aC1 = text_diff(p->pPivot, p->pV1, 0, &DCfg);
  aC2 = text_diff(p->pPivot, p->pV2, 0, &DCfg);
  if( aC1==0 || aC2==0 ){
    free(aC1);
    free(aC2);
    return -1;
  }

  blob_rewind(pV1);        /* Rewind inputs:  Needed to reconstruct output */
  blob_rewind(pV2);
  blob_rewind(pPivot);
  blob_rewind(p->pV1);        /* Rewind inputs:  Needed to reconstruct output */
  blob_rewind(p->pV2);
  blob_rewind(p->pPivot);

  /* Determine the length of the aC1[] and aC2[] change vectors */
  p->mxPivot = 0;
  p->mxV1 = 0;
  for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){}
  for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){
    p->mxPivot += aC1[i1] + aC1[i1+1];
    p->mxV1 += aC1[i1] + aC1[i1+2];
  }
  limit1 = i1;
  p->mxV2 = 0;
  for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){}
  for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){
  limit2 = i2;

    p->mxV2 += aC2[i2] + aC2[i2+2];
  DEBUG(
    for(i1=0; i1<limit1; i1+=3){
      printf("c1: %4d %4d %4d\n", aC1[i1], aC1[i1+1], aC1[i1+2]);
    }
    for(i2=0; i2<limit2; i2+=3){
  }
  limit2 = i2;
      printf("c2: %4d %4d %4d\n", aC2[i2], aC2[i2+1], aC2[i2+2]);
    }
  )

  /* Output header text and do any other required initialization */
  p->xStart(p);

  /* Loop over the two edit vectors and use them to compute merged text
  ** which is written into pOut.  i1 and i2 are multiples of 3 which are
  ** indices into aC1[] and aC2[] to the edit triple currently being
  ** processed
  */
  i1 = i2 = 0;
  ln1 = ln2 = lnPivot = 1;
  while( i1<limit1 && i2<limit2 ){
    DEBUG( printf("%d: %2d %2d %2d   %d: %2d %2d %2d\n",
           i1/3, aC1[i1], aC1[i1+1], aC1[i1+2],
           i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); )

    if( aC1[i1]>0 && aC2[i2]>0 ){
      /* Output text that is unchanged in both V1 and V2 */
      nCpy = min(aC1[i1], aC2[i2]);
      DEBUG( printf("COPY %d\n", nCpy); )
      p->xSame(p, nCpy);
      blob_copy_lines(pOut, pPivot, nCpy); lnPivot += nCpy;
      blob_copy_lines(0, pV1, nCpy);       ln1 += nCpy;
      blob_copy_lines(0, pV2, nCpy);       ln2 += nCpy;
      aC1[i1] -= nCpy;
      aC2[i2] -= nCpy;
    }else
    if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){
      /* Output edits to V2 that occurs within unchanged regions of V1 */
      nDel = aC2[i2+1];
      nIns = aC2[i2+2];
      DEBUG( printf("EDIT -%d+%d left\n", nDel, nIns); )
      p->xChngV2(p, nDel, nIns);
      blob_copy_lines(0, pPivot, nDel);    lnPivot += nDel;
      blob_copy_lines(0, pV1, nDel);       ln1 += nDel;
      blob_copy_lines(pOut, pV2, nIns);    ln2 += nIns;
      aC1[i1] -= nDel;
      i2 += 3;
    }else
    if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){
      /* Output edits to V1 that occur within unchanged regions of V2 */
      nDel = aC1[i1+1];
      nIns = aC1[i1+2];
      DEBUG( printf("EDIT -%d+%d right\n", nDel, nIns); )
      p->xChngV1(p, nDel, nIns);
      blob_copy_lines(0, pPivot, nDel);    lnPivot += nDel;
      blob_copy_lines(0, pV2, nDel);       ln2 += nDel;
      blob_copy_lines(pOut, pV1, nIns);    ln1 += nIns;
      aC2[i2] -= nDel;
      i1 += 3;
    }else
    if( sameEdit(&aC1[i1], &aC2[i2], pV1, pV2) ){
    if( sameEdit(&aC1[i1], &aC2[i2], p->pV1, p->pV2) ){
      /* Output edits that are identical in both V1 and V2. */
      assert( aC1[i1]==0 );
      nDel = aC1[i1+1];
      nIns = aC1[i1+2];
      DEBUG( printf("EDIT -%d+%d both\n", nDel, nIns); )
      p->xChngBoth(p, nDel, nIns);
      blob_copy_lines(0, pPivot, nDel);    lnPivot += nDel;
      blob_copy_lines(pOut, pV1, nIns);    ln1 += nIns;
      blob_copy_lines(0, pV2, nIns);       ln2 += nIns;
      i1 += 3;
      i2 += 3;
    }else
    {
      /* We have found a region where different edits to V1 and V2 overlap.
      ** This is a merge conflict.  Find the size of the conflict, then
      ** output both possible edits separated by distinctive marks.
      */
      int sz = 1;    /* Size of the conflict in lines */
      unsigned int sz = 1;    /* Size of the conflict in the pivot, in lines */
      unsigned int nV1, nV2;  /* Size of conflict in V1 and V2, in lines */
      nConflict++;
      while( !ends_at_CPY(&aC1[i1], sz) || !ends_at_CPY(&aC2[i2], sz) ){
      while( !ends_with_copy(&aC1[i1], sz) || !ends_with_copy(&aC2[i2], sz) ){
        sz++;
      }
      DEBUG( printf("CONFLICT %d\n", sz); )

      i1 = skip_conflict(aC1, i1, sz, &nV1);
      append_merge_mark(pOut, 0, ln1, useCrLf);
      i1 = output_one_side(pOut, pV1, aC1, i1, sz, &ln1);

      i2 = skip_conflict(aC2, i2, sz, &nV2);
      append_merge_mark(pOut, 1, lnPivot, useCrLf);
      blob_copy_lines(pOut, pPivot, sz);   lnPivot += sz;

      p->xConflict(p, sz, nV1, nV2);
      append_merge_mark(pOut, 2, ln2, useCrLf);
      i2 = output_one_side(pOut, pV2, aC2, i2, sz, &ln2);

    }
      append_merge_mark(pOut, 3, -1, useCrLf);
   }
 

    /* If we are finished with an edit triple, advance to the next
    ** triple.
    */
    if( i1<limit1 && aC1[i1]==0 && aC1[i1+1]==0 && aC1[i1+2]==0 ) i1+=3;
    if( i2<limit2 && aC2[i2]==0 && aC2[i2+1]==0 && aC2[i2+2]==0 ) i2+=3;
  }

  /* When one of the two edit vectors reaches its end, there might still
  ** be an insert in the other edit vector.  Output this remaining
  ** insert.
  */
  DEBUG( printf("%d: %2d %2d %2d   %d: %2d %2d %2d\n",
         i1/3, aC1[i1], aC1[i1+1], aC1[i1+2],
         i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); )
  if( i1<limit1 && aC1[i1+2]>0 ){
    DEBUG( printf("INSERT +%d left\n", aC1[i1+2]); )
    blob_copy_lines(pOut, pV1, aC1[i1+2]);
    p->xChngV1(p, 0, aC1[i1+2]);
  }else if( i2<limit2 && aC2[i2+2]>0 ){
    DEBUG( printf("INSERT +%d right\n", aC2[i2+2]); )
    blob_copy_lines(pOut, pV2, aC2[i2+2]);
    p->xChngV2(p, 0, aC2[i2+2]);
  }

  /* Output footer text */
  p->xEnd(p);

  free(aC1);
  free(aC2);
  return nConflict;
}

/*
** Return true if the input string contains a merge marker on a line by
** itself.
*/
int contains_merge_marker(Blob *p){
  int i, j;
  int len = (int)strlen(mergeMarker[0]);
  const char *z = blob_buffer(p);
  int n = blob_size(p) - len + 1;
  assert( len==(int)strlen(mergeMarker[1]) );
  assert( len==(int)strlen(mergeMarker[2]) );
  assert( len==(int)strlen(mergeMarker[3]) );
  assert( len==(int)strlen(mergeMarker[4]) );
  assert( count(mergeMarker)==4 );
  assert( count(mergeMarker)==5 );
  for(i=0; i<n; ){
    for(j=0; j<4; j++){
      if( (memcmp(&z[i], mergeMarker[j], len)==0) ){
        return 1;
      }
    }
    while( i<n && z[i]!='\n' ){ i++; }
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
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
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
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









+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
+


-
+
+




















-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




-
-
+
+

+
+
+
-
+


-
+


-
+


-
-
-
+
+
+
+
+
+
+

+



-
-
+
+
+
+







  Blob file;
  int rc;
  blob_read_from_file(&file, zFullpath, ExtFILE);
  rc = contains_merge_marker(&file);
  blob_reset(&file);
  return rc;
}

/*
** Show merge output in a Tcl/Tk window, in response to the --tk option
** to the "merge" or "3-way-merge" 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.
*/
void merge_tk(const char *zSubCmd, int firstArg){
  int i;
  Blob script;
  const char *zTempFile = 0;
  char *zCmd;
  const char *zTclsh;
  const char *zCnt;
  int bDarkMode = find_option("dark",0,0)!=0;
  int nContext;
  zCnt = find_option("context", "c", 1);
  if( zCnt==0 ){
    nContext = 6;
  }else{
    nContext = atoi(zCnt);
    if( nContext<0 ) nContext = 0xfffffff;
  }
  blob_zero(&script);
  blob_appendf(&script, "set ncontext %d\n", nContext);
  blob_appendf(&script, "set fossilcmd {| \"%/\" %s -tcl",
               g.nameOfExe, zSubCmd);
  find_option("tcl",0,0);
  find_option("debug",0,0);
  zTclsh = find_option("tclsh",0,1);
  if( zTclsh==0 ){
    zTclsh = db_get("tclsh",0);
  }
  /* The undocumented --script FILENAME option causes the Tk script to
  ** be written into the FILENAME instead of being run.  This is used
  ** for testing and debugging. */
  zTempFile = find_option("script",0,1);
  verify_all_options();

  if( (g.argc - firstArg)!=3 ){
    fossil_fatal("Requires 3 filename arguments");
  }

  for(i=firstArg; i<g.argc; i++){
    const char *z = g.argv[i];
    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, "}\nset darkmode %d\n", bDarkMode);
  blob_appendf(&script, "%s", builtin_file("merge.tcl", 0));
  if( zTempFile ){
    blob_write_to_file(&script, zTempFile);
    fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile);
  }else{
#if defined(FOSSIL_ENABLE_TCL)
    Th_FossilInit(TH_INIT_DEFAULT);
    if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script),
                              blob_size(&script), 1, 1, 0)==TCL_OK ){
      blob_reset(&script);
      return;
    }
    /*
     * If evaluation of the Tcl script fails, the reason may be that Tk
     * could not be found by the loaded Tcl, or that Tcl cannot be loaded
     * dynamically (e.g. x64 Tcl with x86 Fossil).  Therefore, fallback
     * to using the external "tclsh", if available.
     */
#endif
    zTempFile = write_blob_to_temp_file(&script);
    zCmd = mprintf("%$ %$", zTclsh, zTempFile);
    fossil_system(zCmd);
    file_delete(zTempFile);
    fossil_free(zCmd);
  }
  blob_reset(&script);
}


/*
** COMMAND: 3-way-merge*
**
** Usage: %fossil 3-way-merge BASELINE V1 V2 MERGED
** Usage: %fossil 3-way-merge BASELINE V1 V2 [MERGED]
**
** Inputs are files BASELINE, V1, and V2.  The file MERGED is generated
** as output.
** as output.  If no MERGED file is specified, output is sent to
** stdout.
**
** BASELINE is a common ancestor of two files V1 and V2 that have diverging
** edits.  The generated output file MERGED is the combination of all
** changes in both V1 and V2.
**
** This command has no effect on the Fossil repository.  It is a utility
** command made available for the convenience of users.  This command can
** be used, for example, to help import changes from an upstream project.
**
** Suppose an upstream project has a file named "Xup.c" which is imported
** with modifications to the local project as "Xlocal.c".  Suppose further
** 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;
  int nConflict;
void merge_3way_cmd(void){
  MergeBuilder s;
  int nConflict;
  Blob pivot, v1, v2, out;
  int noWarn = 0;
  const char *zCnt;

  if( find_option("tk", 0, 0)!=0 ){
    merge_tk("3-way-merge", 2);
    return;
  }
  mergebuilder_init_text(&s);
  if( find_option("debug", 0, 0) ){
    mergebuilder_init(&s);
  }
  if( find_option("tcl", 0, 0) ){
    mergebuilder_init_tcl(&s);
    noWarn = 1;
  }
  zCnt = find_option("context", "c", 1);
  if( zCnt ){
    s.nContext = atoi(zCnt);
    if( s.nContext<0 ) s.nContext = 0xfffffff;
  }else{
    s.nContext = 6;
  }
  blob_zero(&pivot); s.pPivot = &pivot;
  blob_zero(&v1);    s.pV1 = &v1;
  blob_zero(&v2);    s.pV2 = &v2;
  blob_zero(&out);   s.pOut = &out;

  /* We should be done with options.. */
  verify_all_options();

  if( g.argc!=6 ){
    usage("PIVOT V1 V2 MERGED");
  if( g.argc!=6 && g.argc!=5 ){
    usage("[OPTIONS] PIVOT V1 V2 [MERGED]");
  }
  s.zPivot = file_tail(g.argv[2]);
  s.zV1 = file_tail(g.argv[3]);
  s.zV2 = file_tail(g.argv[4]);
  if( blob_read_from_file(&pivot, g.argv[2], ExtFILE)<0 ){
  if( blob_read_from_file(s.pPivot, g.argv[2], ExtFILE)<0 ){
    fossil_fatal("cannot read %s", g.argv[2]);
  }
  if( blob_read_from_file(&v1, g.argv[3], ExtFILE)<0 ){
  if( blob_read_from_file(s.pV1, g.argv[3], ExtFILE)<0 ){
    fossil_fatal("cannot read %s", g.argv[3]);
  }
  if( blob_read_from_file(&v2, g.argv[4], ExtFILE)<0 ){
  if( blob_read_from_file(s.pV2, g.argv[4], ExtFILE)<0 ){
    fossil_fatal("cannot read %s", g.argv[4]);
  }
  nConflict = blob_merge(&pivot, &v1, &v2, &merged);
  if( blob_write_to_file(&merged, g.argv[5])<(int)blob_size(&merged) ){
    fossil_fatal("cannot write %s", g.argv[4]);
  nConflict = merge_three_blobs(&s);
  if( g.argc==6 ){
    s.zOut = file_tail(g.argv[5]);
    blob_write_to_file(s.pOut, g.argv[5]);
  }else{
    s.zOut = "(Merge Result)";
    blob_write_to_file(s.pOut, "-");
  }
  s.xDestroy(&s);
  blob_reset(&pivot);
  blob_reset(&v1);
  blob_reset(&v2);
  blob_reset(&merged);
  if( nConflict>0 ) fossil_warning("WARNING: %d merge conflicts", nConflict);
  blob_reset(&out);
  if( nConflict>0 && !noWarn ){
    fossil_warning("WARNING: %d merge conflicts", nConflict);
  }
}

/*
** aSubst is an array of string pairs.  The first element of each pair is
** a string that begins with %.  The second element is a replacement for that
** string.
**
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
1131
1132
1133
1134
1135
1136
1137

1138
1139
1140
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
1169
1170
1171


1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182


1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193

1194
1195

1196
1197

1198
1199
1200
1201
1202
1203
1204
1205







-
+





-
+




-
+








-
-
+
+












-
-
+
+

+

+
+
+
+
+
+
-
-
+
+









-
+

-
+

-
+







  return blob_str(&x);
}

#if INTERFACE
/*
** Flags to the 3-way merger
*/
#define MERGE_DRYRUN  0x0001
#define MERGE_DRYRUN          0x0001
/*
** The MERGE_KEEP_FILES flag specifies that merge_3way() should retain
** its temporary files on error. By default they are removed after the
** merge, regardless of success or failure.
*/
#define MERGE_KEEP_FILES 0x0002
#define MERGE_KEEP_FILES      0x0002
#endif


/*
** This routine is a wrapper around blob_merge() with the following
** This routine is a wrapper around merge_three_blobs() with the following
** enhancements:
**
**    (1) If the merge-command is defined, then use the external merging
**        program specified instead of the built-in blob-merge to do the
**        merging.  Panic if the external merger fails.
**        ** Not currently implemented **
**
**    (2) If gmerge-command is defined and there are merge conflicts in
**        blob_merge() then invoke the external graphical merger to resolve
**        the conflicts.
**        merge_three_blobs() then invoke the external graphical merger 
**        to resolve the conflicts.
**
**    (3) If a merge conflict occurs and gmerge-command is not defined,
**        then write the pivot, original, and merge-in files to the
**        filesystem.
*/
int merge_3way(
  Blob *pPivot,       /* Common ancestor (older) */
  const char *zV1,    /* Name of file for version merging into (mine) */
  Blob *pV2,          /* Version merging from (yours) */
  Blob *pOut,         /* Output written here */
  unsigned mergeFlags /* Flags that control operation */
){
  Blob v1;            /* Content of zV1 */
  int rc;             /* Return code of subroutines and this routine */
  Blob v1;               /* Content of zV1 */
  int rc;                /* Return code of subroutines and this routine */
  const char *zGMerge;   /* Name of the gmerge command */
  MergeBuilder s;        /* The merge state */

  mergebuilder_init_text(&s);
  s.pPivot = pPivot;
  s.pV1 = &v1;
  s.pV2 = pV2;
  blob_zero(pOut);
  s.pOut = pOut;
  blob_read_from_file(&v1, zV1, ExtFILE);
  rc = blob_merge(pPivot, &v1, pV2, pOut);
  blob_read_from_file(s.pV1, zV1, ExtFILE);
  rc = merge_three_blobs(&s);
  zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0);
  if( (mergeFlags & MERGE_DRYRUN)==0
      && ((zGMerge!=0 && zGMerge[0]!=0)
          || (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){
    char *zPivot;       /* Name of the pivot file */
    char *zOrig;        /* Name of the original content file */
    char *zOther;       /* Name of the merge file */

    zPivot = file_newname(zV1, "baseline", 1);
    blob_write_to_file(pPivot, zPivot);
    blob_write_to_file(s.pPivot, zPivot);
    zOrig = file_newname(zV1, "original", 1);
    blob_write_to_file(&v1, zOrig);
    blob_write_to_file(s.pV1, zOrig);
    zOther = file_newname(zV1, "merge", 1);
    blob_write_to_file(pV2, zOther);
    blob_write_to_file(s.pV2, zOther);
    if( rc>0 ){
      if( zGMerge && zGMerge[0] ){
        char *zOut;     /* Temporary output file */
        char *zCmd;     /* Command to invoke */
        const char *azSubst[8];  /* Strings to be substituted */
        zOut = file_newname(zV1, "output", 1);
        azSubst[0] = "%baseline";  azSubst[1] = zPivot;
588
589
590
591
592
593
594

595
596
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232







+


      file_delete(zOther);
    }
    fossil_free(zPivot);
    fossil_free(zOrig);
    fossil_free(zOther);
  }
  blob_reset(&v1);
  s.xDestroy(&s);
  return rc;
}
Changes to src/name.c.
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
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








+
+
+
+
+
+
+
+
+
+
+
+
+

-
+



-
-
+
+



+

-
-
-
-
+
+
+
+
+
+
+
+

+
+
+
+
-
+
+
+



-
+


-
+





+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








/*
** Check to see if the string might be a compact date/time that omits
** the punctuation.  Example:  "20190327084549" instead of
** "2019-03-27 08:45:49".  If the string is of the appropriate form,
** then return an alternative string (in static space) that is the same
** string with punctuation inserted.
**
** If the bRoundUp parameter is true, then round the resulting date-time
** up to the largest date/time that is consistent with the input value.
** This is because the result will be used for an mtime<=julianday($DATE)
** comparison.  In other words:
**
**     20250317123421 -> 2025-03-17 12:34:21.999
**                                          ^^^^--- Added
**
**     202503171234   -> 2025-03-17 12:34:59.999
**                                       ^^^^^^^--- Added
**     20250317       -> 2025-03-17 23:59:59.999
**                                 ^^^^^^^^^^^^^--- Added
**
** If the bVerifyNotAHash flag is true, then a check is made to see if
** the string is a hash prefix and NULL is returned if it is.  If the
** the input string is a hash prefix and NULL is returned if it is.  If the
** bVerifyNotAHash flag is false, then the result is determined by syntax
** of the input string only, without reference to the artifact table.
*/
char *fossil_expand_datetime(const char *zIn, int bVerifyNotAHash){
  static char zEDate[20];
char *fossil_expand_datetime(const char *zIn,int bVerifyNotAHash,int bRoundUp){
  static char zEDate[24];
  static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' };
  int n = (int)strlen(zIn);
  int i, j;
  int addZulu = 0;

  /* Only three forms allowed:
  **   (1)  YYYYMMDD
  **   (2)  YYYYMMDDHHMM
  **   (3)  YYYYMMDDHHMMSS
  /* These forms are allowed:
  **
  **        123456789 1234           123456789 123456789 1234
  **   (1)  YYYYMMDD            =>   YYYY-MM-DD 23:59:59.999
  **   (2)  YYYYMMDDHHMM        =>   YYYY-MM-DD HH:MM:59.999
  **   (3)  YYYYMMDDHHMMSS      =>   YYYY-MM-DD HH:MM:SS.999
  **
  ** An optional "Z" zulu timezone designator is allowed at the end.
  */
  if( n>0 && (zIn[n-1]=='Z' || zIn[n-1]=='z') ){
    n--;
    addZulu = 1;
  }
  if( n!=8 && n!=12 && n!=14 ) return 0;
  if( n!=8 && n!=12 && n!=14 ){
    return 0;
  }

  /* Every character must be a digit */
  for(i=0; fossil_isdigit(zIn[i]); i++){}
  if( i!=n ) return 0;
  if( i!=n && (!addZulu || i!=n+1) ) return 0;

  /* Expand the date */
  for(i=j=0; zIn[i]; i++){
  for(i=j=0; i<n; i++){
    if( i>=4 && (i%2)==0 ){
      zEDate[j++] = aPunct[i/2];
    }
    zEDate[j++] = zIn[i];
  }
  if( bRoundUp ){
    if( j==10 ){
      memcpy(&zEDate[10], " 23:59:59.999", 13);
      j += 13;
    }else if( j==16 ){
      memcpy(&zEDate[16], ":59.999",7);
      j += 7;
    }else if( j==19 ){
      memcpy(&zEDate[19], ".999", 4);
      j += 4;
    }
  }
  if( addZulu ){
    zEDate[j++] = 'Z';
  }
  zEDate[j] = 0;

  /* Check for reasonable date values.
  ** Offset references:
  **    YYYY-MM-DD HH:MM:SS
  **    0123456789 12345678
  */
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
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
205
206
207
208
209







-
+

















+
+
+
+
+


-
+

+
+
+
+
+


-
+
+




-
+
+




-
+
+







    if( i>24 ) return 0;
    i = atoi(zEDate+14);
    if( i>60 ) return 0;
    if( n==14 && atoi(zEDate+17)>60 ) return 0;
  }

  /* The string is not also a hash prefix */
  if( bVerifyNotAHash ){
  if( bVerifyNotAHash && !addZulu ){
    if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'",zIn) ) return 0;
  }

  /* It looks like this may be a date.  Return it with punctuation added. */
  return zEDate;
}

/*
** The data-time string in the argument is going to be used as an
** upper bound like this:    mtime<=julianday(zDate,'localtime').
** But if the zDate parameter omits the fractional seconds or the
** seconds, or the time, that might mess up the == part of the
** comparison.  So add in missing factional seconds or seconds or time.
**
** The returned string is held in a static buffer that is overwritten
** with each call, or else is just a copy of its input if there are
** no changes.
**
** For reference:
**
**        0123456789 123456789 1234
**        YYYY-MM-DD HH:MM:SS.SSSz
*/
const char *fossil_roundup_date(const char *zDate){
  static char zUp[24];
  static char zUp[28];
  int n = (int)strlen(zDate);
  int addZ = 0;
  if( n>10 && (zDate[n-1]=='z' || zDate[n-1]=='Z') ){
    n--;
    addZ = 1;
  }
  if( n==19 ){  /* YYYY-MM-DD HH:MM:SS */
    memcpy(zUp, zDate, 19);
    memcpy(zUp+19, ".999", 5);
    memcpy(zUp+19, ".999z", 6);
    if( !addZ ) zUp[23] = 0;
    return zUp;
  }
  if( n==16 ){ /* YYYY-MM-DD HH:MM */
    memcpy(zUp, zDate, 16);
    memcpy(zUp+16, ":59.999", 8);
    memcpy(zUp+16, ":59.999z", 8);
    if( !addZ ) zUp[23] = 0;
    return zUp;
  }
  if( n==10 ){ /* YYYY-MM-DD */
    memcpy(zUp, zDate, 10);
    memcpy(zUp+10, " 23:59:59.999", 14);
    memcpy(zUp+10, " 23:59:59.999z", 14);
    if( !addZ ) zUp[23] = 0;
    return zUp;
  }
  return zDate;
}


/*
211
212
213
214
215
216
217
218

219
220
221
222
223
224
225
263
264
265
266
267
268
269

270
271
272
273
274
275
276
277







-
+







** Return 0 if there are no matches.
**
** This is a tricky query to do efficiently.
** If the tag is very common (ex: "trunk") then
** we want to use the query identified below as Q1 - which searches
** the most recent EVENT table entries for the most recent with the tag.
** But if the tag is relatively scarce (anything other than "trunk", basically)
** then we want to do the indexed search show below as Q2.
** then we want to do the indexed search shown below as Q2.
*/
static int most_recent_event_with_tag(const char *zTag, const char *zType){
  return db_int(0,
    "SELECT objid FROM ("
      /* Q1:  Begin by looking for the tag in the 30 most recent events */
      "SELECT objid"
       " FROM (SELECT * FROM event ORDER BY mtime DESC LIMIT 30) AS ex"
451
452
453
454
455
456
457


458
459
460
461
462
463
464

465
466
467
468
469
470
471
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517

518
519
520
521
522
523
524
525







+
+






-
+







      rid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim",
                   ridCkout);
    }else if( fossil_strcmp(zTag, "next")==0 ){
      rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d"
                      "  ORDER BY isprim DESC, mtime DESC", ridCkout);
    }else if( isCheckin>1 && fossil_strcmp(zTag, "ckout")==0 ){
      rid = RID_CKOUT;
      assert(ridCkout>0);
      g.localOpen = ridCkout;
    }
    if( rid ) return rid;
  }

  /* Date and times */
  if( memcmp(zTag, "date:", 5)==0 ){
    zDate = fossil_expand_datetime(&zTag[5],0);
    zDate = fossil_expand_datetime(&zTag[5],0,1);
    if( zDate==0 ) zDate = &zTag[5];
    rid = db_int(0,
      "SELECT objid FROM event"
      " WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'"
      " ORDER BY mtime DESC LIMIT 1",
      fossil_roundup_date(zDate), zType);
    return rid;
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
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







-
+

-
+







-
+







    return start_of_branch(rid, 2);
  }

  /* symbolic-name ":" date-time */
  nTag = strlen(zTag);
  for(i=0; i<nTag-8 && zTag[i]!=':'; i++){}
  if( zTag[i]==':'
   && (fossil_isdate(&zTag[i+1]) || fossil_expand_datetime(&zTag[i+1],0)!=0)
   && (fossil_isdate(&zTag[i+1]) || fossil_expand_datetime(&zTag[i+1],0,0)!=0)
  ){
    char *zDate = mprintf("%s", &zTag[i+1]);
    char *zDate = fossil_strdup(&zTag[i+1]);
    char *zTagBase = mprintf("%.*s", i, zTag);
    char *zXDate;
    int nDate = strlen(zDate);
    if( sqlite3_strnicmp(&zDate[nDate-3],"utc",3)==0 ){
      zDate[nDate-3] = 'z';
      zDate[nDate-2] = 0;
    }
    zXDate = fossil_expand_datetime(zDate,0);
    zXDate = fossil_expand_datetime(zDate,0,1);
    if( zXDate==0 ) zXDate = zDate;
    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 "
609
610
611
612
613
614
615
616

617
618
619
620
621
622
623
663
664
665
666
667
668
669

670
671
672
673
674
675
676
677







-
+








  if( rid>0 ){
    if( startOfBranch ) rid = start_of_branch(rid,1);
    return rid;
  }

  /* Pure numeric date/time */
  zDate = fossil_expand_datetime(zTag, 0);
  zDate = fossil_expand_datetime(zTag, 0,1);
  if( zDate ){
    rid = db_int(0,
      "SELECT objid FROM event"
      " WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'"
      " ORDER BY mtime DESC LIMIT 1",
      fossil_roundup_date(zDate), zType);
    if( rid) return rid;
662
663
664
665
666
667
668























































669
670
671
672
673
674
675
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  ){
    char *zNew = fossil_strndup(zTag, nTag-2);
    rid = symbolic_name_to_rid(zNew,zType);
    fossil_free(zNew);
  }
  return rid;
}

/*
** Convert a symbolic name used as an argument to the a=, b=, or c=
** query parameters of timeline into a julianday mtime value.
**
** If pzDisplay is not null, then display text for the symbolic name might
** be written into *pzDisplay.  But that is not guaranteed.
**
** If bRoundUp is true and the symbolic name is a timestamp with less
** than millisecond resolution, then the timestamp is rounding up to the
** largest millisecond consistent with that timestamp.  If bRoundUp is
** false, then the resulting time is obtained by extending the timestamp
** with zeros (hence rounding down).  Use bRoundUp==1 if the result
** will be used in mtime<=$RESULT and use bRoundUp==0 if the result
** will be used in mtime>=$RESULT.
*/
double symbolic_name_to_mtime(
  const char *z,              /* Input symbolic name */
  const char **pzDisplay,     /* Perhaps write display text here, if not NULL */
  int bRoundUp                /* Round up if true */
){
  double mtime;
  int rid;
  const char *zDate;
  if( z==0 ) return -1.0;
  if( fossil_isdate(z) ){
    mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", z);
    if( mtime>0.0 ) return mtime;
  }
  zDate = fossil_expand_datetime(z, 1, bRoundUp);
  if( zDate!=0 ){
    mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())",
                      bRoundUp ? fossil_roundup_date(zDate) : zDate);
    if( mtime>0.0 ){
      if( pzDisplay ){
        zDate = fossil_expand_datetime(z,0,0);
        *pzDisplay = fossil_strdup(zDate);
      }
      return mtime;
    }
  }
  rid = symbolic_name_to_rid(z, "*");
  if( rid ){
    mtime = mtime_of_rid(rid, 0.0);
  }else{
    mtime = db_double(-1.0,
        "SELECT max(event.mtime) FROM event, tag, tagxref"
        " WHERE tag.tagname GLOB 'event-%q*'"
        "   AND tagxref.tagid=tag.tagid AND tagxref.tagtype"
        "   AND event.objid=tagxref.rid",
        z
    );
  }
  return mtime;
}

/*
** This routine takes a user-entered string and tries to convert it to
** an artifact hash.
**
** We first try to treat the string as an artifact hash, or at least a
** unique prefix of an artifact hash. The input may be in mixed case.
697
698
699
700
701
702
703



704
705
706
707
708
709
710
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822







+
+
+







    fossil_error(iErrPriority, "ambiguous name: %s", zName);
    return 2;
  }else if( rid==0 ){
    fossil_error(iErrPriority, "cannot resolve name: %s", zName);
    return 1;
  }else{
    blob_reset(pName);
    if( RID_CKOUT==rid ) {
      rid = g.localOpen;
    }
    db_blob(pName, "SELECT uuid FROM blob WHERE rid=%d", rid);
    return 0;
  }
}

/*
** This routine is similar to name_to_uuid() except in the form it
872
873
874
875
876
877
878
879

880
881
882
883
884
885
886
984
985
986
987
988
989
990

991
992
993
994
995
996
997
998







-
+







  if( zName==0 || zName[0]==0 || zSrc==0 || zSrc[0]==0 ){
    fossil_redirect_home();
  }
  style_header("Ambiguous Artifact ID");
  @ <p>The artifact hash prefix <b>%h(zName)</b> is ambiguous and might
  @ mean any of the following:
  @ <ol>
  z = mprintf("%s", zName);
  z = fossil_strdup(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="%R/%T(zSrc)/%!S(zUuid)">
    @ %s(zUuid)</a> -
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
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
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
1169
1170
1171
1172
1173
1174
1175







+

















-
+
+
+











+







** Flag values for whatis_rid().
*/
#if INTERFACE
#define WHATIS_VERBOSE   0x01    /* Extra output */
#define WHATIS_BRIEF     0x02    /* Omit unnecessary output */
#define WHATIS_REPO      0x04    /* Show repository name */
#define WHATIS_OMIT_UNK  0x08    /* Do not show "unknown" lines */
#define WHATIS_HASHONLY  0x10    /* Show only the hash */
#endif

/*
** Generate a description of artifact "rid"
*/
void whatis_rid(int rid, int flags){
  Stmt q;
  int cnt;

  /* Basic information about the object. */
  db_prepare(&q,
     "SELECT uuid, size, datetime(mtime,toLocal()), ipaddr"
     "  FROM blob, rcvfrom"
     " WHERE rid=%d"
     "   AND rcvfrom.rcvid=blob.rcvid",
     rid);
  if( db_step(&q)==SQLITE_ROW ){
    if( flags & WHATIS_VERBOSE ){
    if( flags & WHATIS_HASHONLY ){
      fossil_print("%s\n", db_column_text(&q,0));
    }else if( flags & WHATIS_VERBOSE ){
      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);
  if( flags & WHATIS_HASHONLY ) return;

  /* 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 tagxref.tagtype<>0"
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
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
1256
1257







-
+
+
+






-
+






-
+

+







    cnt++;
  }
  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,toLocal()),"
    "       coalesce(euser,user), coalesce(ecomment,comment)"
    "       coalesce(euser,user), coalesce(ecomment,comment),"
    "       coalesce((SELECT value FROM tagxref"
                  "  WHERE tagid=%d AND tagtype>0 AND rid=mlink.mid),'trunk')"
    "  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 %s /*sort*/",
    rid,
    TAG_BRANCH, rid,
    (flags & WHATIS_BRIEF) ? "LIMIT 1" : "DESC");
  while( db_step(&q)==SQLITE_ROW ){
    if( flags & WHATIS_BRIEF ){
      fossil_print("mtime:      %s\n", db_column_text(&q,2));
    }
    fossil_print("file:       %s\n", db_column_text(&q,0));
    fossil_print("            part of [%S] by %s on %s\n",
    fossil_print("            part of [%S] on branch %s by %s on %s\n",
      db_column_text(&q, 1),
      db_column_text(&q, 5),
      db_column_text(&q, 3),
      db_column_text(&q, 2));
    fossil_print("            ");
    comment_print(db_column_text(&q,4), 0, 12, -1, get_comment_format());
    cnt++;
  }
  db_finalize(&q);
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
1249
1250

1251


1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265




1266
1267
1268
1269
1270
1271
1272
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
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
1398
1399







-
+













+
-
+
+
















+

+
+













-
+
+
+
+







        );
    while( db_step(&q)==SQLITE_ROW ){
      if( cnt++ ) fossil_print("%12s---- meaning #%d ----\n", " ", cnt);
      whatis_rid(db_column_int(&q, 0), mFlags);
    }
    db_finalize(&q);
  }else if( rid==0 ){
    if( (mFlags & WHATIS_OMIT_UNK)==0 ){
    if( (mFlags & (WHATIS_OMIT_UNK|WHATIS_HASHONLY))==0 ){
                 /* 0123456789 12 */
      if( zFileName ){
        fossil_print("%-12s%s\n", "name:", zFileName);
      }
      fossil_print("unknown:    %s\n", zName);
    }
  }else{
    if( mFlags & WHATIS_REPO ){
      fossil_print("\nrepository: %s\n", g.zRepositoryName);
    }
    if( zFileName ){
      zName = zFileName;
    }
    if( (mFlags & WHATIS_HASHONLY)==0 ){
    fossil_print("%-12s%s\n", "name:", zName);
      fossil_print("%-12s%s\n", "name:", zName);
    }
    whatis_rid(rid, mFlags);
  }
}

/*
** COMMAND: whatis*
**
** Usage: %fossil whatis NAME
**
** Resolve the symbol NAME into its canonical artifact hash
** artifact name and provide a description of what role that artifact
** plays.
**
** Options:
**    -f|--file            Find artifacts with the same hash as file NAME.
**                         If NAME is "-", read content from standard input.
**    -h|--hash            Show only the hash of matching artifacts.
**    -q|--quiet           Show nothing if NAME is not found
**    -R REPO_FILE         Specifies the repository db to use. Default is
**                         the current check-out's repository.
**    --type TYPE          Only find artifacts of TYPE (one of: 'ci', 't',
**                         'w', 'g', or 'e')
**    -v|--verbose         Provide extra information (such as the RID)
*/
void whatis_cmd(void){
  int mFlags = 0;
  int fileFlag;
  int i;
  const char *zType = 0;
  db_find_and_open_repository(0,0);
  if( find_option("verbose","v",0)!=0 ){
    mFlags |= WHATIS_VERBOSE;
  }
  if( find_option("quiet","q",0)!=0 ){
  if( find_option("hash","h",0)!=0 ){
    mFlags |= WHATIS_HASHONLY;
  }
  if( g.fQuiet ){
    mFlags |= WHATIS_OMIT_UNK | WHATIS_REPO;
  }
  fileFlag = find_option("file","f",0)!=0;
  zType = find_option("type",0,1);

  /* We should be done with options.. */
  verify_all_options();
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
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
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

1776
1777
1778
1779



1780
1781
1782
1783
1784
1785
1786
1787
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
1862
1863
1864
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
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







+









+
+









+
+
+
+
+
+

-
+




+










-
+







+
+








+


+

+
+
+
+
+
+
+






+
-
-
+
+
+
+
+









-
-
+
-
-
-






-
+
+












-
+
-
-
-
-
+
+
+
-







** Return a page showing all artifacts in the repository.  Query parameters:
**
**   n=N         Show N artifacts
**   s=S         Start with artifact number S
**   priv        Show only unpublished or private artifacts
**   phan        Show only phantom artifacts
**   hclr        Color code hash types (SHA1 vs SHA3)
**   recent      Show the most recent N artifacts
*/
void bloblist_page(void){
  Stmt q;
  int s = atoi(PD("s","0"));
  int n = atoi(PD("n","5000"));
  int mx = db_int(0, "SELECT max(rid) FROM blob");
  int privOnly = PB("priv");
  int phantomOnly = PB("phan");
  int hashClr = PB("hclr");
  int bRecent = PB("recent");
  int bUnclst = PB("unclustered");
  char *zRange;
  char *zSha1Bg;
  char *zSha3Bg;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  cgi_check_for_malice();
  style_header("List Of Artifacts");
  style_submenu_element("250 Largest", "bigbloblist");
  if( bRecent==0 || n!=250 ){
    style_submenu_element("Recent","bloblist?n=250&recent");
  }
  if( bUnclst==0 ){
    style_submenu_element("Unclustered","bloblist?unclustered");
  }
  if( g.perm.Admin ){
    style_submenu_element("Artifact Log", "rcvfromlist");
    style_submenu_element("Xfer Log", "rcvfromlist");
  }
  if( !phantomOnly ){
    style_submenu_element("Phantoms", "bloblist?phan");
  }
  style_submenu_element("Clusters","clusterlist");
  if( g.perm.Private || g.perm.Admin ){
    if( !privOnly ){
      style_submenu_element("Private", "bloblist?priv");
    }
  }else{
    privOnly = 0;
  }
  if( g.perm.Write ){
    style_submenu_element("Artifact Stats", "artifact_stats");
  }
  if( !privOnly && !phantomOnly && mx>n && P("s")==0 ){
  if( !privOnly && !phantomOnly && mx>n && P("s")==0 && !bRecent && !bUnclst ){
    int i;
    @ <p>Select a range of artifacts to view:</p>
    @ <ul>
    for(i=1; i<=mx; i+=n){
      @ <li> %z(href("%R/bloblist?s=%d&n=%d",i,n))
      @ %d(i)..%d(i+n-1<mx?i+n-1:mx)</a>
    }
    @ <li> %z(href("%R/bloblist?n=250&recent"))250 most recent</a>
    @ <li> %z(href("%R/bloblist?unclustered"))All unclustered</a>
    @ </ul>
    style_finish_page();
    return;
  }
  if( phantomOnly || privOnly || mx>n ){
    style_submenu_element("Index", "bloblist");
  }
  if( privOnly ){
    @ <h2>Private Artifacts</h2>
    zRange = mprintf("IN private");
  }else if( phantomOnly ){
    @ <h2>Phantom Artifacts</h2>
    zRange = mprintf("IN phantom");
  }else if( bUnclst ){
    @ <h2>Unclustered Artifacts</h2>
    zRange = mprintf("IN unclustered");
  }else if( bRecent ){
    @ <h2>%d(n) Most Recent Artifacts</h2>
    zRange = mprintf(">=(SELECT rid FROM blob"
                     " ORDER BY rid DESC LIMIT 1 OFFSET %d)",n);
  }else{
    zRange = mprintf("BETWEEN %d AND %d", s, s+n-1);
  }
  describe_artifacts(zRange);
  fossil_free(zRange);
  db_prepare(&q,
         /*   0     1        2          3               4    5      6 */
    "SELECT rid, uuid, summary, isPrivate, type='phantom', rcvid, ref"
    "  FROM description ORDER BY rid"
    "SELECT rid, uuid, summary, isPrivate, type='phantom', ref, rcvid, "
    "  datetime(rcvfrom.mtime)"
    "  FROM description LEFT JOIN rcvfrom USING(rcvid)"
    " ORDER BY rid %s",
    ((bRecent||bUnclst)?"DESC":"ASC")/*safe-for-%s*/
  );
  if( skin_detail_boolean("white-foreground") ){
    zSha1Bg = "#714417";
    zSha3Bg = "#177117";
  }else{
    zSha1Bg = "#ebffb0";
    zSha3Bg = "#b0ffb0";
  }
  @ <table cellpadding="2" cellspacing="0" border="1">
  if( g.perm.Admin ){
    @ <tr><th>RID<th>Hash<th>Rcvid<th>Description<th>Ref<th>Remarks
  @ <tr><th>RID<th>Hash<th>Received<th>Description<th>Ref<th>Remarks
  }else{
    @ <tr><th>RID<th>Hash<th>Description<th>Ref<th>Remarks
  }
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q,0);
    const char *zUuid = db_column_text(&q, 1);
    const char *zDesc = db_column_text(&q, 2);
    int isPriv = db_column_int(&q,3);
    int isPhantom = db_column_int(&q,4);
    const char *zRef = db_column_text(&q,6);
    const char *zRef = db_column_text(&q,5);
    const char *zDate = db_column_text(&q,7);
    if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){
      /* Don't show private artifacts to users without Private (x) permission */
      continue;
    }
    if( hashClr ){
      const char *zClr = db_column_bytes(&q,1)>40 ? zSha3Bg : zSha1Bg;
      @ <tr style='background-color:%s(zClr);'><td align="right">%d(rid)</td>
    }else{
      @ <tr><td align="right">%d(rid)</td>
    }
    @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
    if( g.perm.Admin ){
      int rcvid = db_column_int(&q,5);
      int rcvid = db_column_int(&q, 6);
      if( rcvid<=0 ){
        @ <td>&nbsp;
      }else{
        @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%d(rcvid)</a>
      @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%h(zDate)</a>
    }else{
      @ <td>%h(zDate)
      }
    }
    @ <td align="left">%h(zDesc)</td>
    if( zRef && zRef[0] ){
      @ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a>
    }else{
      @ <td>&nbsp;
    }
1871
1872
1873
1874
1875
1876
1877
1878

1879
1880
1881
1882
1883
1884
1885
2018
2019
2020
2021
2022
2023
2024

2025
2026
2027
2028
2029
2030
2031
2032







-
+







** generate extra network traffic on every sync request.
*/
void phantom_list_page(void){
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  style_header("Public Phantom Artifacts");
  if( g.perm.Admin ){
    style_submenu_element("Artifact Log", "rcvfromlist");
    style_submenu_element("Xfer Log", "rcvfromlist");
    style_submenu_element("Artifact List", "bloblist");
  }
  if( g.perm.Write ){
    style_submenu_element("Artifact Stats", "artifact_stats");
  }
  table_of_public_phantoms();
  style_finish_page();
1896
1897
1898
1899
1900
1901
1902
1903

1904
1905
1906
1907
1908
1909
1910
2043
2044
2045
2046
2047
2048
2049

2050
2051
2052
2053
2054
2055
2056
2057







-
+







void bigbloblist_page(void){
  Stmt q;
  int n = atoi(PD("n","250"));

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  if( g.perm.Admin ){
    style_submenu_element("Artifact Log", "rcvfromlist");
    style_submenu_element("Xfer Log", "rcvfromlist");
  }
  if( g.perm.Write ){
    style_submenu_element("Artifact Stats", "artifact_stats");
  }
  style_submenu_element("All Artifacts", "bloblist");
  style_header("%d Largest Artifacts", n);
  db_multi_exec(
2069
2070
2071
2072
2073
2074
2075
2076








2077
2078






2079














































2080


2081
2082
2083
2084
2085
2086
2087
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







-
+
+
+
+
+
+
+
+

-
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+







}

/*
** COMMAND: test-phantoms
**
** Usage: %fossil test-phantoms
**
** Show all phantom artifacts
** Show all phantom artifacts.  A phantom artifact is one for which there
** is no content. Options:
**
**   --count            Show only a count of the number of phantoms.
**   --delta            Show all delta-phantoms.  A delta-phantom is a
**                      artifact for which there is a delta but the delta
**                      source is a phantom.
**   --list             Just list the phantoms.  Do not try to describe them.
*/
void test_phatoms_cmd(void){
void test_phantoms_cmd(void){
  int bDelta;
  int bList;
  int bCount;
  unsigned nPhantom = 0;
  unsigned nDeltaPhantom = 0;
  db_find_and_open_repository(0,0);
  bDelta = find_option("delta", 0, 0)!=0;
  bList = find_option("list", 0, 0)!=0;
  bCount = find_option("count", 0, 0)!=0;
  verify_all_options();
  if( bList || bCount ){
    Stmt q1, q2;
    db_prepare(&q1, "SELECT rid, uuid FROM blob WHERE size<0");
    while( db_step(&q1)==SQLITE_ROW ){
      int rid = db_column_int(&q1, 0);
      nPhantom++;
      if( !bCount ){
        fossil_print("%S (%d)\n", db_column_text(&q1,1), rid);
      }
      db_prepare(&q2,
        "WITH RECURSIVE deltasof(rid) AS ("
        "  SELECT rid FROM delta WHERE srcid=%d"
        "  UNION"
        "  SELECT delta.rid FROM deltasof, delta"
        "    WHERE delta.srcid=deltasof.rid)"
        "SELECT deltasof.rid, blob.uuid FROM deltasof LEFT JOIN blob"
        "    ON blob.rid=deltasof.rid", rid
      );
      while( db_step(&q2)==SQLITE_ROW ){
        nDeltaPhantom++;
        if( !bCount ){
          fossil_print("   %S (%d)\n", db_column_text(&q2,1),
                                       db_column_int(&q2,0));
        }
      }
      db_finalize(&q2);
    }
    db_finalize(&q1);
    if( nPhantom ){
      fossil_print("Phantoms: %u    Delta-phantoms: %u\n",
                    nPhantom, nDeltaPhantom);
    }
  }else if( bDelta ){
    describe_artifacts_to_stdout(
       "IN (WITH RECURSIVE delta_phantom(rid) AS (\n"
       "      SELECT delta.rid FROM blob, delta\n"
       "       WHERE blob.size<0 AND delta.srcid=blob.rid\n"
       "      UNION\n"
       "      SELECT delta.rid FROM delta_phantom, delta\n"
       "       WHERE delta.srcid=delta_phantom.rid)\n"
       "    SELECT rid FROM delta_phantom)", 0);
  }else{
  describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0);
    describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0);
  }
}

/* Maximum number of collision examples to remember */
#define MAX_COLLIDE 25

/*
** Generate a report on the number of collisions in artifact hashes
2161
2162
2163
2164
2165
2166
2167




















































































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
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
  collision_report("SELECT (SELECT uuid FROM blob WHERE rid=objid)"
                   "  FROM event WHERE event.type='ci'"
                   " ORDER BY 1");
  @ <h1>Hash Prefix Collisions on All Artifacts</h1>
  collision_report("SELECT uuid FROM blob ORDER BY 1");
  style_finish_page();
}

/*
** WEBPAGE: clusterlist
**
** Show information about all cluster artifacts in the database.
*/
void clusterlist_page(void){
  Stmt q;
  int cnt = 1;
  sqlite3_int64 szTotal = 0;
  sqlite3_int64 szCTotal = 0;
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  style_header("All Cluster Artifacts");
  style_submenu_element("All Artifactst", "bloblist");
  if( g.perm.Admin ){
    style_submenu_element("Xfer Log", "rcvfromlist");
  }
  style_submenu_element("Phantoms", "bloblist?phan");
  if( g.perm.Write ){
    style_submenu_element("Artifact Stats", "artifact_stats");
  }

  db_prepare(&q,
    "SELECT blob.uuid, "
    "       blob.size, "
    "       octet_length(blob.content), "
    "       datetime(rcvfrom.mtime),"
    "       user.login,"
    "       rcvfrom.ipaddr"
    "  FROM tagxref JOIN blob ON tagxref.rid=blob.rid"
    "       LEFT JOIN rcvfrom ON blob.rcvid=rcvfrom.rcvid"
    "       LEFT JOIN user ON user.uid=rcvfrom.uid"
    " WHERE tagxref.tagid=%d"
    " ORDER BY rcvfrom.mtime, blob.uuid",
    TAG_CLUSTER
  );
  @ <table cellpadding="2" cellspacing="0" border="1">
  @ <tr><th>&nbsp;
  @ <th>Hash
  @ <th>Date&nbsp;Received
  @ <th>Size
  @ <th>Compressed&nbsp;Size
  if( g.perm.Admin ){
    @ <th>User<th>IP-Address
  }
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    sqlite3_int64 sz = db_column_int64(&q, 1);
    sqlite3_int64 szC = db_column_int64(&q, 2);
    const char *zDate = db_column_text(&q, 3);
    const char *zUser = db_column_text(&q, 4);
    const char *zIp = db_column_text(&q, 5);
    szTotal += sz;
    szCTotal += szC;
    @ <tr><td align="right">%d(cnt++)
    @ <td><a href="%R/info/%S(zUuid)">%S(zUuid)</a>
    if( zDate ){
      @ <td>%h(zDate)
    }else{
      @ <td>&nbsp;
    }
    @ <td align="right">%,lld(sz)
    @ <td align="right">%,lld(szC)
    if( g.perm.Admin ){
      if( zUser ){
        @ <td>%h(zUser)
      }else{
        @ <td>&nbsp;
      }
      if( zIp ){
        @ <td>%h(zIp)
      }else{
        @ <td>&nbsp;
      }
    }
    @ </tr>
  }
  @ </table>
  db_finalize(&q);
  @ <p>Total size of all clusters: %,lld(szTotal) bytes,
  @ %,lld(szCTotal) bytes compressed</p>
  style_finish_page();
}
Changes to src/patch.c.
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
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







-
+

-




















-
-
-
-
-
+
+
-
-









-
+
+


-
-
-
+
+

-
+







*/
static void mkdeltaFunc(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  const char *zFile;
  Blob x, y;
  Blob x, y, out;
  int rid;
  char *aOut;
  int nOut;
  sqlite3_int64 sz;

  rid = sqlite3_value_int(argv[0]);
  if( !content_get(rid, &x) ){
    sqlite3_result_error(context, "mkdelta(X,Y): no content for X", -1);
    return;
  }
  zFile = (const char*)sqlite3_value_text(argv[1]);
  if( zFile==0 ){
    sqlite3_result_error(context, "mkdelta(X,Y): NULL Y argument", -1);
    blob_reset(&x);
    return;
  }
  sz = blob_read_from_file(&y, zFile, RepoFILE);
  if( sz<0 ){
    sqlite3_result_error(context, "mkdelta(X,Y): cannot read file Y", -1);
    blob_reset(&x);
    return;
  }
  aOut = sqlite3_malloc64(sz+70);
  if( aOut==0 ){
    sqlite3_result_error_nomem(context);
    blob_reset(&y);
    blob_reset(&x);
  blob_init(&out, 0, 0);
  blob_resize(&out, sz+70);
    return;
  }
  if( blob_size(&x)==blob_size(&y)
   && memcmp(blob_buffer(&x), blob_buffer(&y), blob_size(&x))==0
  ){
    blob_reset(&y);
    blob_reset(&x);
    sqlite3_result_blob64(context, "", 0, SQLITE_STATIC);
    return;
  }
  nOut = delta_create(blob_buffer(&x),blob_size(&x),
                      blob_buffer(&y),blob_size(&y), aOut);
                      blob_buffer(&y),blob_size(&y), blob_buffer(&out));
  blob_resize(&out, nOut);
  blob_reset(&x);
  blob_reset(&y);
  blob_init(&x, aOut, nOut);
  blob_compress(&x, &x);
  sqlite3_result_blob64(context, blob_buffer(&x), blob_size(&x),
  blob_compress(&out, &out);
  sqlite3_result_blob64(context, blob_buffer(&out), blob_size(&out),
                        SQLITE_TRANSIENT);
  blob_reset(&x);
  blob_reset(&out);
}


/*
** Generate a binary patch file and store it into the file
** named zOut.  Or if zOut is NULL, write it into out.
**
385
386
387
388
389
390
391
392

393
394
395
396
397
398
399
379
380
381
382
383
384
385

386
387
388
389
390
391
392
393







-
+








  blob_init(&cmd, 0, 0);
  if( unsaved_changes(0) ){
    if( (mFlags & PATCH_FORCE)==0 ){
      fossil_fatal("Cannot apply patch: there are unsaved changes "
                   "in the current check-out");
    }else{
      blob_appendf(&cmd, "%$ revert", g.nameOfExe);
      blob_appendf(&cmd, "%$ revert --noundo", g.nameOfExe);
      if( mFlags & PATCH_DRYRUN ){
        fossil_print("%s\n", blob_str(&cmd));
      }else{
        int rc = fossil_system(blob_str(&cmd));
        if( rc ){
          fossil_fatal("unable to revert preexisting changes: %s",
                       blob_str(&cmd));
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
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
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502







+









-
+
+

-
+
+

+
















+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







        fossil_fatal("unable to update to the baseline check-out: %s",
                     blob_str(&cmd));
      }
    }
  }
  blob_reset(&cmd);
  if( db_table_exists("patch","patchmerge") ){
    int nMerge = 0;
    db_prepare(&q,
      "SELECT type, mhash, upper(type) FROM patch.patchmerge"
      " WHERE type IN ('merge','cherrypick','backout','integrate')"
      "   AND mhash NOT GLOB '*[^a-fA-F0-9]*';"
    );
    while( db_step(&q)==SQLITE_ROW ){
      const char *zType = db_column_text(&q,0);
      blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
      if( strcmp(zType,"merge")==0 ){
        blob_appendf(&cmd, " merge %s\n", db_column_text(&q,1));
        blob_appendf(&cmd, " merge --noundo --nosync %s\n",
                     db_column_text(&q,1));
      }else{
        blob_appendf(&cmd, " merge --%s %s\n", zType, db_column_text(&q,1));
        blob_appendf(&cmd, " merge --%s --noundo --nosync %s\n",
                     zType, db_column_text(&q,1));
      }
      nMerge++;
      if( mFlags & PATCH_VERBOSE ){
        fossil_print("%-10s %s\n", db_column_text(&q,2),
                    db_column_text(&q,0));
      }
    }
    db_finalize(&q);
    if( mFlags & PATCH_DRYRUN ){
      fossil_print("%s", blob_str(&cmd));
    }else{
      int rc = fossil_unsafe_system(blob_str(&cmd));
      if( rc ){
        fossil_fatal("unable to do merges:\n%s",
                     blob_str(&cmd));
      }
    }
    blob_reset(&cmd);

    /* 2024-12-16 https://fossil-scm.org/home/forumpost/51a37054
    ** If one or more merge operations occurred in the patch and there are
    ** files that are marked as "chnged' in the local VFILE but which
    ** are not mentioned as having been modified in the patch, then
    ** revert those files.
    */
    if( nMerge ){
      int vid = db_lget_int("checkout", 0);
      int nRevert = 0;
      blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
      blob_appendf(&cmd, " revert --noundo ");
      db_prepare(&q,
        "SELECT pathname FROM vfile WHERE vid=%d AND chnged "
        "EXCEPT SELECT pathname FROM chng",
        vid
      );
      while( db_step(&q)==SQLITE_ROW ){
        blob_append_escaped_arg(&cmd, db_column_text(&q,0), 1);
        nRevert++;
      }
      db_finalize(&q);
      if( nRevert ){
        if( mFlags & PATCH_DRYRUN ){
          fossil_print("%s", blob_str(&cmd));
        }else{
          int rc = fossil_unsafe_system(blob_str(&cmd));
          if( rc ){
            fossil_fatal("unable to do reverts:\n%s",
                         blob_str(&cmd));
          }
        }
      }
      blob_reset(&cmd);
    }
  }

  /* Deletions */
  db_prepare(&q, "SELECT pathname FROM patch.chng"
                 " WHERE origname IS NULL AND delta IS NULL");
  while( db_step(&q)==SQLITE_ROW ){
    if( blob_size(&cmd)==0 ){
663
664
665
666
667
668
669
















670
671
672
673
674
675
676
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  }
  if( zDir && file_chdir(zDir,0) ){
    fossil_fatal("cannot change to directory \"%s\"", zDir);
  }
  fossil_free(zToFree);
  return zPatchFile;
}

/*
** Resolves a patch-command remote system name, accounting for patch
** aliases.
**
** If a CONFIG table entry matching name='patch-alias:$zKey' is found,
** the corresponding value is returned, else a fossil_strdup() of zKey
** is returned. The caller is responsible for passing the resulting
** string to fossil_free().
*/
static char *patch_resolve_remote(const char *zKey){
  char *zAlias = db_text(0, "SELECT value FROM config "
                            "WHERE name = 'patch-alias:%q'",
                            zKey);
  return zAlias ? zAlias : fossil_strdup(zKey);
}

/*
** Create a FILE* that will execute the remote side of a push or pull
** using ssh (probably) or fossil for local pushes and pulls.  Return
** a FILE* obtained from popen() into which we write the patch, or from
** which we read the patch, depending on whether this is a push or pull.
*/
694
695
696
697
698
699
700
701

702
703
704
705
706
707
708
743
744
745
746
747
748
749

750
751
752
753
754
755
756
757







-
+







  if( mFlags & PATCH_FORCE )  blob_appendf(&flgs, " -f");
  if( mFlags & PATCH_VERBOSE )  blob_appendf(&flgs, " -v");
  if( mFlags & PATCH_DRYRUN )  blob_appendf(&flgs, " -n");
  zForce = blob_size(&flgs)>0 ? blob_str(&flgs) : "";
  if( g.argc!=4 ){
    usage(mprintf("%s [USER@]HOST:DIRECTORY", zThisCmd));
  }
  zRemote = fossil_strdup(g.argv[3]);
  zRemote = patch_resolve_remote(g.argv[3]);
  zDir = (char*)file_skip_userhost(zRemote);
  if( zDir==0 ){
    if( isRetry ) goto remote_command_error;
    zDir = zRemote;
    blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
    blob_appendf(&cmd, " patch %s%s %$ -", zRemoteCmd, zForce, zDir);
  }else{
743
744
745
746
747
748
749
750

751
752
753
754
755
756
757
792
793
794
795
796
797
798

799
800
801
802
803
804
805
806







-
+







}

/*
** Toggle the use-path-for-ssh setting for the remote host defined
** by g.argv[3].
*/
static void patch_toggle_ssh_needs_path(void){
  char *zRemote = fossil_strdup(g.argv[3]);
  char *zRemote = patch_resolve_remote(g.argv[3]);
  char *zDir = (char*)file_skip_userhost(zRemote);
  if( zDir ){
    *(char*)(zDir - 1) =  0;
    ssh_needs_path_argument(zRemote, 99);
  }
  fossil_free(zRemote);
}
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
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







-










+
+
+
+
+
+
+
+
+
+
+
+







    }
  }
  db_finalize(&q);
  diff_end(pCfg, nErr);
  if( nErr ) fossil_fatal("abort due to prior errors");
}


/*
** COMMAND: patch
**
** Usage: %fossil patch SUBCOMMAND ?ARGS ..?
**
** This command is used to create, view, and apply Fossil binary patches.
** A Fossil binary patch is a single (binary) file that captures all of the
** uncommitted changes of a check-out.  Use Fossil binary patches to transfer
** proposed or incomplete changes between machines for testing or analysis.
**
** > fossil patch alias add|rm|ls|list ?ARGS?
**
**       Manage remote-name aliases, which act as short-form
**       equivalents to REMOTE-CHECKOUT strings. Aliases are local to
**       a given repository and do not sync. Subcommands:
**
**         ... add ALIAS REMOTE-CHECKOUT       Add ALIAS as an alias
**                                             for REMOTE-CHECKOUT.
**         ... ls|list                         List all local aliases.
**         ... rm ALIAS [ALIAS...]             Remove named aliases
**         ... rm --all                        Remove all aliases
**
** > fossil patch create [DIRECTORY] PATCHFILE
**
**       Create a new binary patch in PATCHFILE that captures all uncommitted
**       changes in the check-out at DIRECTORY, or the current directory if
**       DIRECTORY is omitted.  If PATCHFILE is "-" then the binary patch
**       is written to standard output.
**
961
962
963
964
965
966
967
968

969
970
971






























































972
973
974
975
976
977
978
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







-
+



+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







**
*/
void patch_cmd(void){
  const char *zCmd;
  size_t n;
  if( g.argc<3 ){
    patch_usage:
    usage("apply|create|diff|gdiff|pull|push|view");
    usage("alias|apply|create|diff|gdiff|pull|push|view");
  }
  zCmd = g.argv[2];
  n = strlen(zCmd);
  if( strncmp(zCmd, "alias", n)==0 ){
    const char * zArg = g.argc>3 ? g.argv[3] : 0;
    db_must_be_within_tree();
    if( 0==zArg ){
      goto usage_patch_alias;
    }else if( 0==strcmp("ls",zArg) || 0==strcmp("list",zArg) ){
      /* alias ls|list */
      Stmt q;
      int nAlias = 0;

      verify_all_options();
      db_prepare(&q, "SELECT substr(name,13), value FROM config "
                 "WHERE name GLOB 'patch-alias:*' ORDER BY name");
      while( SQLITE_ROW==db_step(&q) ){
        const char *zName = db_column_text(&q, 0);
        const char *zVal = db_column_text(&q, 1);
        ++nAlias;
        fossil_print("%s = %s\n", zName, zVal);
      }
      db_finalize(&q);
      if( 0==nAlias ){
        fossil_print("No patch aliases defined\n");
      }
    }else if( 0==strcmp("add", zArg) ){
      /* alias add localName remote */
      verify_all_options();
      if( 6!=g.argc ){
        usage("alias add localName remote");
      }
      db_unprotect(PROTECT_CONFIG);
      db_multi_exec("REPLACE INTO config (name, value, mtime) "
                    "VALUES ('patch-alias:%q', %Q, unixepoch())",
                    g.argv[4], g.argv[5]);
      db_protect_pop();
    }else if( 0==strcmp("rm", zArg) ){
      /* alias rm */
      const int fAll = 0!=find_option("all", 0, 0);
      if( fAll ? g.argc<4 : g.argc<5 ){
        usage("alias rm [-all] [aliasGlob [...aliasGlobN]]");
      }
      verify_all_options();
      db_unprotect(PROTECT_CONFIG);
      if( 0!=fAll ){
        db_multi_exec("DELETE FROM config WHERE name GLOB 'patch-alias:*'");
      }else{
        Stmt q;
        int i;
        db_prepare(&q, "DELETE FROM config WHERE name "
                   "GLOB 'patch-alias:' || :pattern");
        for(i = 4; i < g.argc; ++i){
          db_bind_text(&q, ":pattern", g.argv[i]);
          db_step(&q);
          db_reset(&q);
        }
        db_finalize(&q);
      }
      db_protect_pop();
    }else{
    usage_patch_alias:
      usage("alias ls|list|add|rm ...");
    }
  }else
  if( strncmp(zCmd, "apply", n)==0 ){
    char *zIn;
    unsigned flags = 0;
    if( find_option("dry-run","n",0) )  flags |= PATCH_DRYRUN;
    if( find_option("verbose","v",0) )  flags |= PATCH_VERBOSE;
    if( find_option("force","f",0) )    flags |= PATCH_FORCE;
    zIn = patch_find_patch_filename("apply");
998
999
1000
1001
1002
1003
1004




1005
1006
1007
1008

1009
1010
1011
1012
1013
1014
1015
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133

1134
1135
1136
1137
1138
1139
1140
1141







+
+
+
+



-
+








    if( find_option("tk",0,0)!=0 ){
      db_close(0);
      diff_tk("patch diff", 3);
      return;
    }
    db_find_and_open_repository(0, 0);
    if( gdiff_using_tk(zCmd[0]=='g') ){
      diff_tk("patch diff", 3);
      return;
    }
    if( find_option("force","f",0) )    flags |= PATCH_FORCE;
    diff_options(&DCfg, zCmd[0]=='g', 0);
    verify_all_options();
    zIn = patch_find_patch_filename("apply");
    zIn = patch_find_patch_filename("diff");
    patch_attach(zIn, stdin, 0);
    patch_diff(flags, &DCfg);
    fossil_free(zIn);
  }else
  if( strncmp(zCmd, "pull", n)==0 ){
    FILE *pIn = 0;
    unsigned flags = 0;
Changes to src/path.c.
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
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







+









+
+


-
+


-
+







-
+

-


+
+



+


















+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+






+
+



-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+











+


-
+







**
** This file contains code used to trace paths of through the
** directed acyclic graph (DAG) of check-ins.
*/
#include "config.h"
#include "path.h"
#include <assert.h>
#include <math.h>

#if INTERFACE
/* Nodes for the paths through the DAG.
*/
struct PathNode {
  int rid;                 /* ID for this node */
  u8 fromIsParent;         /* True if pFrom is the parent of rid */
  u8 isPrim;               /* True if primary side of common ancestor */
  u8 isHidden;             /* Abbreviate output in "fossil bisect ls" */
  char *zBranch;           /* Branch name for this node.  Might be NULL */
  double mtime;            /* Date/time of this check-in */
  PathNode *pFrom;         /* Node we came from */
  union {
    PathNode *pPeer;       /* List of nodes of the same generation */
    double rCost;          /* Cost of getting to this node from pStart */
    PathNode *pTo;         /* Next on path from beginning to end */
  } u;
  PathNode *pAll;        /* List of all nodes */
  PathNode *pAll;          /* List of all nodes */
};
#endif

/*
** Local variables for this module
*/
static struct {
  PathNode *pCurrent;   /* Current generation of nodes */
  PQueue pending;       /* Nodes pending review for inclusion in the graph */
  PathNode *pAll;       /* All nodes */
  Bag seen;             /* Nodes seen before */
  int nStep;            /* Number of steps from first to last */
  int nNotHidden;       /* Number of steps not counting hidden nodes */
  int brCost;           /* Extra cost for moving to a different branch */
  int revCost;          /* Extra cost for changing directions */
  PathNode *pStart;     /* Earliest node */
  PathNode *pEnd;       /* Most recent */
} path;
static int path_debug = 0;  /* Flag to enable debugging */

/*
** Return the first (last) element of the computed path.
*/
PathNode *path_first(void){ return path.pStart; }
PathNode *path_last(void){ return path.pEnd; }

/*
** Return the number of steps in the computed path.
*/
int path_length(void){ return path.nStep; }

/*
** Return the number of non-hidden steps in the computed path.
*/
int path_length_not_hidden(void){ return path.nNotHidden; }

/*
** Used for debugging only.
**
** Given a RID, return the ISO date/time string and branch for the
** corresponding check-in.  Memory is held locally and is overwritten
** with each call.
*/
char *path_rid_desc(int rid){
  static Stmt q;
  static char *zDesc = 0;
  db_static_prepare(&q, 
    "SELECT concat(strftime('%%Y%%m%%d%%H%%M',event.mtime),'/',value)"
    "  FROM event, tagxref"
    " WHERE event.objid=:rid"
    "   AND tagxref.rid=:rid"
    "   AND tagxref.tagid=%d"
    "   AND tagxref.tagtype>0",
    TAG_BRANCH
  );
  fossil_free(zDesc);
  db_bind_int(&q, ":rid", rid);
  if( db_step(&q)==SQLITE_ROW ){
    zDesc = fossil_strdup(db_column_text(&q,0));
  }
  db_reset(&q);
  return zDesc ? zDesc : "???";
}

/*
** Create a new node
** Create a new node and insert it into the path.pending queue.
*/
static PathNode *path_new_node(int rid, PathNode *pFrom, int isParent){
  PathNode *p;

  p = fossil_malloc( sizeof(*p) );
  memset(p, 0, sizeof(*p));
  p->pAll = path.pAll;
  path.pAll = p;
  p->rid = rid;
  p->fromIsParent = isParent;
  p->pFrom = pFrom;
  p->u.pPeer = path.pCurrent;
  path.pCurrent = p;
  p->pAll = path.pAll;
  path.pAll = p;
  bag_insert(&path.seen, rid);
  p->u.rCost = pFrom ? pFrom->u.rCost : 0.0;
  if( path.brCost ){
    p->zBranch = branch_of_rid(rid);
    p->mtime = mtime_of_rid(rid, 0.0);
    if( pFrom ){
      p->u.rCost +=  fabs(pFrom->mtime - p->mtime);
      if( fossil_strcmp(p->zBranch, pFrom->zBranch)!=0 ){
        p->u.rCost += path.brCost;
      }
    }
  }else{
    /* When brCost==0, we try to minimize the number of nodes
    ** along the path.  The cost is just the number of nodes back
    ** to the start.  We do not need to know the branch name nor
    ** the mtime */
    p->u.rCost += 1.0;
  }
  if( path_debug ){
    fossil_print("PUSH %-50s cost = %g\n", path_rid_desc(p->rid), p->u.rCost);
  }
  pqueuex_insert_ptr(&path.pending, (void*)p, p->u.rCost);
  return p;
}

/*
** Reset memory used by the shortest path algorithm.
*/
void path_reset(void){
  PathNode *p;
  while( path.pAll ){
    p = path.pAll;
    path.pAll = p->pAll;
    fossil_free(p->zBranch);
    fossil_free(p);
  }
  bag_clear(&path.seen);
  pqueuex_clear(&path.pending);
  memset(&path, 0, sizeof(path));
}

/*
** Construct the path from path.pStart to path.pEnd in the u.pTo fields.
*/
static void path_reverse_path(void){
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
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
252
253
254







-
+
+


-
+



+



+














-
+





-
+


-
-
-
+
+
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-







** Return NULL if no path is found.
*/
PathNode *path_shortest(
  int iFrom,          /* Path starts here */
  int iTo,            /* Path ends here */
  int directOnly,     /* No merge links if true */
  int oneWayOnly,     /* Parent->child only if true */
  Bag *pHidden        /* Hidden nodes */
  Bag *pHidden,       /* Hidden nodes */
  int branchCost      /* Add extra cost to changing branches */
){
  Stmt s;
  PathNode *pPrev;
  Bag seen;
  PathNode *p;

  path_reset();
  path.brCost = branchCost;
  path.pStart = path_new_node(iFrom, 0, 0);
  if( iTo==iFrom ){
    path.pEnd = path.pStart;
    path.pEnd->u.pTo = 0;
    return path.pStart;
  }
  if( oneWayOnly && directOnly ){
    db_prepare(&s,
        "SELECT cid, 1 FROM plink WHERE pid=:pid AND isprim"
    );
  }else if( oneWayOnly ){
    db_prepare(&s,
        "SELECT cid, 1 FROM plink WHERE pid=:pid "
    );
  }else if( directOnly ){
    db_prepare(&s,
        "SELECT cid, 1 FROM plink WHERE pid=:pid AND isprim "
        "UNION ALL "
        "SELECT pid, 0 FROM plink WHERE cid=:pid AND isprim"
        "SELECT pid, 0 FROM plink WHERE :back AND cid=:pid AND isprim"
    );
  }else{
    db_prepare(&s,
        "SELECT cid, 1 FROM plink WHERE pid=:pid "
        "UNION ALL "
        "SELECT pid, 0 FROM plink WHERE cid=:pid"
        "SELECT pid, 0 FROM plink WHERE :back AND cid=:pid"
    );
  }
  while( path.pCurrent ){
    path.nStep++;
    pPrev = path.pCurrent;
  bag_init(&seen);
  while( (p = pqueuex_extract_ptr(&path.pending))!=0 ){
    if( path_debug ){
    path.pCurrent = 0;
    while( pPrev ){
      db_bind_int(&s, ":pid", pPrev->rid);
      printf("PULL %s %g\n", path_rid_desc(p->rid), p->u.rCost);
      while( db_step(&s)==SQLITE_ROW ){
        int cid = db_column_int(&s, 0);
        int isParent = db_column_int(&s, 1);
        if( bag_find(&path.seen, cid) ) continue;
        p = path_new_node(cid, pPrev, isParent);
        if( pHidden && bag_find(pHidden,cid) ) p->isHidden = 1;
        if( cid==iTo ){
          db_finalize(&s);
          path.pEnd = p;
          path_reverse_path();
          for(p=path.pStart->u.pTo; p; p=p->u.pTo ){
            if( !p->isHidden ) path.nNotHidden++;
          }
          return path.pStart;
        }
      }
      db_reset(&s);
    }
    if( p->rid==iTo ){
      db_finalize(&s);
      path.pEnd = p;
      path_reverse_path();
      for(p=path.pStart->u.pTo; p; p=p->u.pTo ){
        if( !p->isHidden ) path.nNotHidden++;
      }
      return path.pStart;
    }
    if( bag_find(&seen, p->rid) ) continue;
    bag_insert(&seen, p->rid);
    db_bind_int(&s, ":pid", p->rid);
    if( !oneWayOnly ) db_bind_int(&s, ":back", !p->fromIsParent);
    while( db_step(&s)==SQLITE_ROW ){
      int cid = db_column_int(&s, 0);
      int isParent = db_column_int(&s, 1);
      PathNode *pNew;
      if( bag_find(&seen, cid) ) continue;
      pNew = path_new_node(cid, p, isParent);
      if( pHidden && bag_find(pHidden,cid) ) pNew->isHidden = 1;
    }
    db_reset(&s);
      pPrev = pPrev->u.pPeer;
    }
  }
  db_finalize(&s);
  path_reset();
  return 0;
}

/*
213
214
215
216
217
218
219












220
221
222
223
224
225
226
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







+
+
+
+
+
+
+
+
+
+
+
+







*/
PathNode *path_next(void){
  PathNode *p;
  p = path.pStart;
  if( p ) p = p->u.pTo;
  return p;
}

/*
** Return the branch for a path node.
**
** Storage space is managed by the path subsystem.  The returned value
** is valid until the path is reset.
*/
const char *path_branch(PathNode *p){
  if( p==0 ) return 0;
  if( p->zBranch==0 ) p->zBranch = branch_of_rid(p->rid);
  return p->zBranch;
}

/*
** Return an estimate of the number of comparisons remaining in order
** to bisect path.  This is based on the log2() of path.nStep.
*/
int path_search_depth(void){
  int i, j;
236
237
238
239
240
241
242
243

244
245
246
247
248
249
250
305
306
307
308
309
310
311

312
313
314
315
316
317
318
319







-
+







void path_shortest_stored_in_ancestor_table(
  int origid,     /* RID for check-in at start of the path */
  int cid         /* RID for check-in at the end of the path */
){
  PathNode *pPath;
  int gen = 0;
  Stmt ins;
  pPath = path_shortest(cid, origid, 1, 0, 0);
  pPath = path_shortest(cid, origid, 1, 0, 0, 0);
  db_multi_exec(
    "CREATE TEMP TABLE IF NOT EXISTS ancestor("
    "  rid INT UNIQUE,"
    "  generation INTEGER PRIMARY KEY"
    ");"
    "DELETE FROM ancestor;"
  );
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
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
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423


424
425
426
427
428
429
430







-
+

-
-
+
+
+
+
+
+
+








+




+
+



-
+
+




-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
+








-
+














-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-







  db_finalize(&ins);
  path_reset();
}

/*
** COMMAND: test-shortest-path
**
** Usage: %fossil test-shortest-path ?--no-merge? VERSION1 VERSION2
** Usage: %fossil test-shortest-path [OPTIONS] VERSION1 VERSION2
**
** Report the shortest path between two check-ins.  If the --no-merge flag
** is used, follow only direct parent-child paths and omit merge links.
** Report the shortest path between two check-ins.  Options:
**
**    --branch-cost N    Additional cost N for changing branches
**    --debug            Show debugging output
**    --one-way          One-way forwards in time, parent->child only
**    --no-merge         Follow only direct parent-child paths and omit
**                       merge links.
*/
void shortest_path_test_cmd(void){
  int iFrom;
  int iTo;
  PathNode *p;
  int n;
  int directOnly;
  int oneWay;
  const char *zBrCost;

  db_find_and_open_repository(0,0);
  directOnly = find_option("no-merge",0,0)!=0;
  oneWay = find_option("one-way",0,0)!=0;
  zBrCost = find_option("branch-cost",0,1);
  if( find_option("debug",0,0)!=0 ) path_debug = 1;
  if( g.argc!=4 ) usage("VERSION1 VERSION2");
  iFrom = name_to_rid(g.argv[2]);
  iTo = name_to_rid(g.argv[3]);
  p = path_shortest(iFrom, iTo, directOnly, oneWay, 0);
  p = path_shortest(iFrom, iTo, directOnly, oneWay, 0,
                    zBrCost ? atoi(zBrCost) : 0);
  if( p==0 ){
    fossil_fatal("no path from %s to %s", g.argv[1], g.argv[2]);
  }
  for(n=1, p=path.pStart; p; p=p->u.pTo, n++){
    char *z;
    z = db_text(0,
      "SELECT substr(uuid,1,12) || ' ' || datetime(mtime)"
      "  FROM blob, event"
      " WHERE blob.rid=%d AND event.objid=%d AND event.type='ci'",
      p->rid, p->rid);
    fossil_print("%4d: %5d %s", n, p->rid, z);
    fossil_free(z);
    if( p->u.pTo ){
      fossil_print(" is a %s of\n",
                   p->u.pTo->fromIsParent ? "parent" : "child");
    }else{
      fossil_print("\n");
    }
    fossil_print("%4d: %s\n", n, path_rid_desc(p->rid));
  }
  }
  path_debug = 0;
}

/*
** Find the closest common ancestor of two nodes.  "Closest" means the
** fewest number of arcs.
*/
int path_common_ancestor(int iMe, int iYou){
  Stmt s;
  PathNode *pPrev;
  PathNode *pThis;
  PathNode *p;
  Bag me, you;

  if( iMe==iYou ) return iMe;
  if( iMe==0 || iYou==0 ) return 0;
  path_reset();
  path.pStart = path_new_node(iMe, 0, 0);
  path.pStart->isPrim = 1;
  path.pEnd = path_new_node(iYou, 0, 0);
  db_prepare(&s, "SELECT pid FROM plink WHERE cid=:cid");
  bag_init(&me);
  bag_insert(&me, iMe);
  bag_init(&you);
  bag_insert(&you, iYou);
  while( path.pCurrent ){
    pPrev = path.pCurrent;
    path.pCurrent = 0;
    while( pPrev ){
      db_bind_int(&s, ":cid", pPrev->rid);
      while( db_step(&s)==SQLITE_ROW ){
        int pid = db_column_int(&s, 0);
        if( bag_find(pPrev->isPrim ? &you : &me, pid) ){
          /* pid is the common ancestor */
          PathNode *pNext;
          for(p=path.pAll; p && p->rid!=pid; p=p->pAll){}
          assert( p!=0 );
          pNext = p;
          while( pNext ){
            pNext = p->pFrom;
            p->pFrom = pPrev;
            pPrev = p;
            p = pNext;
          }
          if( pPrev==path.pStart ) path.pStart = path.pEnd;
          path.pEnd = pPrev;
          path_reverse_path();
          db_finalize(&s);
          return pid;
        }else if( bag_find(&path.seen, pid) ){
          /* pid is just an alternative path on one of the legs */
          continue;
        }
        p = path_new_node(pid, pPrev, 0);
        p->isPrim = pPrev->isPrim;
        bag_insert(pPrev->isPrim ? &me : &you, pid);
      }
      db_reset(&s);
  while( (pThis = pqueuex_extract_ptr(&path.pending))!=0 ){
    db_bind_int(&s, ":cid", pThis->rid);
    while( db_step(&s)==SQLITE_ROW ){
      int pid = db_column_int(&s, 0);
      if( bag_find(pThis->isPrim ? &you : &me, pid) ){
        /* pid is the common ancestor */
        PathNode *pNext;
        for(p=path.pAll; p && p->rid!=pid; p=p->pAll){}
        assert( p!=0 );
        pNext = p;
        while( pNext ){
          pNext = p->pFrom;
          p->pFrom = pThis;
          pThis = p;
          p = pNext;
        }
        if( pThis==path.pStart ) path.pStart = path.pEnd;
        path.pEnd = pThis;
        path_reverse_path();
        db_finalize(&s);
        return pid;
      }else if( bag_find(pThis->isPrim ? &me : &you, pid) ){
        /* pid is just an alternative path to a node we've already visited */
        continue;
      }
      p = path_new_node(pid, pThis, 0);
      p->isPrim = pThis->isPrim;
      bag_insert(pThis->isPrim ? &me : &you, pid);
    }
    db_reset(&s);
      pPrev = pPrev->u.pPeer;
    }
  }
  db_finalize(&s);
  path_reset();
  return 0;
}

/*
451
452
453
454
455
456
457
458

459
460
461
462
463
464
465
512
513
514
515
516
517
518

519
520
521
522
523
524
525
526







-
+







  if(0==iFrom){
    fossil_fatal("Invalid 'from' RID: 0");
  }else if(0==iTo){
    fossil_fatal("Invalid 'to' RID: 0");
  }
  if( iFrom==iTo ) return;
  path_reset();
  p = path_shortest(iFrom, iTo, 1, revOK==0, 0);
  p = path_shortest(iFrom, iTo, 1, revOK==0, 0, 0);
  if( p==0 ) return;
  path_reverse_path();
  db_prepare(&q1,
     "SELECT pfnid, fnid FROM mlink"
     " WHERE mid=:mid AND (pfnid>0 OR fid==0)"
     " ORDER BY pfnid"
  );
540
541
542
543
544
545
546

547
548
549
550
551
552
553
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615







+







    *pnChng = i/2;
    while( pAll ){
      pChng = pAll;
      pAll = pAll->pNext;
      fossil_free(pChng);
    }
  }
  path_reset();
}

/*
** COMMAND: test-name-changes
**
** Usage: %fossil test-name-changes [--debug] VERSION1 VERSION2
**
Changes to src/piechart.c.
24
25
26
27
28
29
30
31

32
33
34
35
36
37
38
24
25
26
27
28
29
30

31
32
33
34
35
36
37
38







-
+








#ifndef M_PI
# define M_PI 3.1415926535897932385
#endif

/*
** Return an RGB color name given HSV values.  The HSV values
** must each be between between 0 and 255.  The string
** must each be between 0 and 255.  The string
** returned is held in a static buffer and is overwritten
** on each call.
*/
const char *rgbName(unsigned char h, unsigned char s, unsigned char v){
  static char zColor[8];
  unsigned char A, B, C, r, g, b;
  unsigned int i, m;
Changes to src/pikchrshow.c.
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
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







-
-














-
-
+

-
+
-
-

-
-
-
+
+
+



-
-
-
-
-
-
-
-
-
-
-
-
-







#if INTERFACE
/* These are described in pikchr_process()'s docs. */
/* The first two must match the values from pikchr.c */
#define PIKCHR_PROCESS_PLAINTEXT_ERRORS  0x0001
#define PIKCHR_PROCESS_DARK_MODE         0x0002
/* end of flags supported directly by pikchr() */
#define PIKCHR_PROCESS_PASSTHROUGH       0x0003   /* Pass through these flags */
#define PIKCHR_PROCESS_TH1               0x0004
#define PIKCHR_PROCESS_TH1_NOSVG         0x0008
#define PIKCHR_PROCESS_NONCE             0x0010
#define PIKCHR_PROCESS_ERR_PRE           0x0020
#define PIKCHR_PROCESS_SRC               0x0040
#define PIKCHR_PROCESS_DIV               0x0080
#define PIKCHR_PROCESS_DIV_INDENT        0x0100
#define PIKCHR_PROCESS_DIV_CENTER        0x0200
#define PIKCHR_PROCESS_DIV_FLOAT_LEFT    0x0400
#define PIKCHR_PROCESS_DIV_FLOAT_RIGHT   0x0800
#define PIKCHR_PROCESS_DIV_TOGGLE        0x1000
#define PIKCHR_PROCESS_DIV_SOURCE        0x2000
#define PIKCHR_PROCESS_DIV_SOURCE_INLINE 0x4000
#endif

/*
** Processes a pikchr script, optionally with embedded TH1, and
** produces HTML code for it. zIn is the NUL-terminated input
** Processes a pikchr script. zIn is the NUL-terminated input
** script. pikFlags may be a bitmask of any of the PIKCHR_PROCESS_xxx
** flags documented below. thFlags may be a bitmask of any of the
** flags documented below. Output is sent to pOut,
** TH_INIT_xxx and/or TH_R2B_xxx flags. Output is sent to pOut,
** appending to it without modifying any prior contents.
**
** Returns 0 on success, 1 if TH1 processing failed, or 2 if pikchr
** processing failed. In either case, the error message (if any) from
** TH1 or pikchr will be appended to pOut.
** Returns 0 on success, or non-zero if pikchr processing failed.
** In either case, the error message (if any) from pikchr will be
** appended to pOut.
**
** pikFlags flag descriptions:
**
** - PIKCHR_PROCESS_TH1 means to run zIn through TH1, using the TH1
** init flags specified in the 3rd argument. If thFlags is non-0 then
** this flag is assumed even if it is not specified.
**
** - PIKCHR_PROCESS_TH1_NOSVG means that processing stops after the
** TH1 eval step, thus the output will be (presumably) a
** TH1-generated/processed pikchr script (or whatever else the TH1
** outputs). If this flag is set, PIKCHR_PROCESS_TH1 is assumed even
** if it is not specified.
**
** All of the remaining flags listed below are ignored if
** PIKCHR_PROCESS_TH1_NOSVG is specified!
**
** - PIKCHR_PROCESS_DIV: if set, the SVG result is wrapped in a DIV
** element which specifies a max-width style value based on the SVG's
** calculated size. This flag has multiple mutually exclusive forms:
**
**  - PIKCHR_PROCESS_DIV uses default element alignment.
**  - PIKCHR_PROCESS_DIV_INDENT indents the div.
**  - PIKCHR_PROCESS_DIV_CENTER centers the div.
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
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
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
188
189
190
191
192
193
194
195


196
197
198

199
200
201
202
203
204
205







-
+
-
-

+
+















-
-
-
-
-



-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-



-







** PIKCHR_PROCESS_DIV_SOURCE or PIKCHR_PROCESS_DIV_SOURCE_INLINE is
** set, this flag is automatically implied.
**
** - PIKCHR_PROCESS_ERR_PRE: if set and pikchr() fails, the resulting
** error report is wrapped in a PRE element, else it is retained
** as-is (intended only for console output).
*/
int pikchr_process(const char * zIn, int pikFlags, int thFlags,
int pikchr_process(const char *zIn, int pikFlags, Blob * pOut){
                   Blob * pOut){
  Blob bIn = empty_blob;
  int isErr = 0;
  int w = 0, h = 0;
  char *zOut;
  const char *zNonce = (PIKCHR_PROCESS_NONCE & pikFlags)
    ? safe_html_nonce(1) : 0;

  if(!(PIKCHR_PROCESS_DIV & pikFlags)
     /* If any DIV_xxx flags are set, set DIV */
     && (PIKCHR_PROCESS_DIV_INDENT
         | PIKCHR_PROCESS_DIV_CENTER
         | PIKCHR_PROCESS_DIV_FLOAT_RIGHT
         | PIKCHR_PROCESS_DIV_FLOAT_LEFT
         | PIKCHR_PROCESS_DIV_SOURCE
         | PIKCHR_PROCESS_DIV_SOURCE_INLINE
         | PIKCHR_PROCESS_DIV_TOGGLE
         ) & pikFlags){
    pikFlags |= PIKCHR_PROCESS_DIV;
  }
  if(!(PIKCHR_PROCESS_TH1 & pikFlags)
     /* If any TH1_xxx flags are set, set TH1 */
     && (PIKCHR_PROCESS_TH1_NOSVG & pikFlags || thFlags!=0)){
    pikFlags |= PIKCHR_PROCESS_TH1;
  }
  if(zNonce){
    blob_appendf(pOut, "%s\n", zNonce);
  }
  if(PIKCHR_PROCESS_TH1 & pikFlags){
    Blob out = empty_blob;
    isErr = Th_RenderToBlob(zIn, &out, thFlags)
      ? 1 : 0;
    if(isErr){
      blob_append(pOut, blob_str(&out), blob_size(&out));
      blob_reset(&out);
    }else{
      bIn = out;
    }
  }else{
    blob_init(&bIn, zIn, -1);
  }
  if(!isErr){
    if(PIKCHR_PROCESS_TH1_NOSVG & pikFlags){
      blob_append(pOut, blob_str(&bIn), blob_size(&bIn));
    }else{
      int w = 0, h = 0;
      const char * zContent = blob_str(&bIn);
      char *zOut;
      zOut = pikchr(zContent, "pikchr",
                    0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
                    &w, &h);
      if( w>0 && h>0 ){
        const char * zClassToggle = "";
        const char * zClassSource = "";
        const char * zWrapperClass = "";
        if(PIKCHR_PROCESS_DIV & pikFlags){
          if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
            zWrapperClass = " center";
          }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
            zWrapperClass = " indent";
          }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
            zWrapperClass = " float-left";
          }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
            zWrapperClass = " float-right";
          }
          if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
            zClassToggle = " toggle";
          }
          if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
            if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
              zClassSource = " source source-inline";
            }else{
              zClassSource = " source-inline";
            }
            pikFlags |= PIKCHR_PROCESS_SRC;
          }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
            zClassSource = " source";
            pikFlags |= PIKCHR_PROCESS_SRC;
          }
          blob_appendf(pOut,"<div class='pikchr-wrapper"
                       "%s%s%s'>"
                       "<div class=\"pikchr-svg\" "
                       "style=\"max-width:%dpx\">\n",
                       zWrapperClass/*safe-for-%s*/,
                       zClassToggle/*safe-for-%s*/,
                       zClassSource/*safe-for-%s*/, w);
        }
        blob_append(pOut, zOut, -1);
        if(PIKCHR_PROCESS_DIV & pikFlags){
          blob_append(pOut, "</div>\n", 7);
        }
        if(PIKCHR_PROCESS_SRC & pikFlags){
          static int counter = 0;
          ++counter;
          blob_appendf(pOut, "<div class='pikchr-src'>"
                       "<pre id='pikchr-src-%d'>%h</pre>"
                       "<span class='hidden'>"
                       "<a href='%R/pikchrshow?fromSession' "
                       "class='pikchr-src-pikchrshow' target='_new-%d' "
                       "data-pikchrid='pikchr-src-%d' "
                       "title='Open this pikchr in /pikchrshow'"
                       ">&rarr; /pikchrshow</a></span>"
                       "</div>\n",
                       counter, blob_str(&bIn), counter, counter);
        }
        if(PIKCHR_PROCESS_DIV & pikFlags){
          blob_append(pOut, "</div>\n", 7);
        }
      }else{
        isErr = 2;
        if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
          blob_append(pOut, "<pre class='error'>\n", 20);
        }
        blob_appendf(pOut, "%h", zOut);
        if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
          blob_append(pOut, "\n</pre>\n", 8);
        }
      }
      fossil_free(zOut);
  zOut = pikchr(zIn, "pikchr",
                0x01 | (pikFlags&PIKCHR_PROCESS_PASSTHROUGH),
                &w, &h);
  if( w>0 && h>0 ){
    const char * zClassToggle = "";
    const char * zClassSource = "";
    const char * zWrapperClass = "";
    if(PIKCHR_PROCESS_DIV & pikFlags){
      if(PIKCHR_PROCESS_DIV_CENTER & pikFlags){
        zWrapperClass = " center";
      }else if(PIKCHR_PROCESS_DIV_INDENT & pikFlags){
        zWrapperClass = " indent";
      }else if(PIKCHR_PROCESS_DIV_FLOAT_LEFT & pikFlags){
        zWrapperClass = " float-left";
      }else if(PIKCHR_PROCESS_DIV_FLOAT_RIGHT & pikFlags){
        zWrapperClass = " float-right";
      }
      if(PIKCHR_PROCESS_DIV_TOGGLE & pikFlags){
        zClassToggle = " toggle";
      }
      if(PIKCHR_PROCESS_DIV_SOURCE_INLINE & pikFlags){
        if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
          zClassSource = " source source-inline";
        }else{
          zClassSource = " source-inline";
        }
        pikFlags |= PIKCHR_PROCESS_SRC;
      }else if(PIKCHR_PROCESS_DIV_SOURCE & pikFlags){
        zClassSource = " source";
        pikFlags |= PIKCHR_PROCESS_SRC;
      }
      blob_appendf(pOut,"<div class='pikchr-wrapper"
                   "%s%s%s'>"
                   "<div class=\"pikchr-svg\" "
                   "style=\"max-width:%dpx\">\n",
                   zWrapperClass/*safe-for-%s*/,
                   zClassToggle/*safe-for-%s*/,
                   zClassSource/*safe-for-%s*/, w);
    }
    blob_append(pOut, zOut, -1);
    if(PIKCHR_PROCESS_DIV & pikFlags){
      blob_append(pOut, "</div>\n", 7);
    }
    if(PIKCHR_PROCESS_SRC & pikFlags){
      static int counter = 0;
      ++counter;
      blob_appendf(pOut, "<div class='pikchr-src'>"
                   "<pre id='pikchr-src-%d'>%h</pre>"
                   "<span class='hidden'>"
                   "<a href='%R/pikchrshow?fromSession' "
                   "class='pikchr-src-pikchrshow' target='_new-%d' "
                   "data-pikchrid='pikchr-src-%d' "
                   "title='Open this pikchr in /pikchrshow'"
                   ">&rarr; /pikchrshow</a></span>"
                   "</div>\n",
                   counter, zIn, counter, counter);
    }
    if(PIKCHR_PROCESS_DIV & pikFlags){
      blob_append(pOut, "</div>\n", 7);
    }
  }else{
    isErr = 2;
    if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
      blob_append(pOut, "<pre class='error'>\n", 20);
    }
    blob_appendf(pOut, "%h", zOut);
    if(PIKCHR_PROCESS_ERR_PRE & pikFlags){
      blob_append(pOut, "\n</pre>\n", 8);
    }
  }
  fossil_free(zOut);
    }
  }
  if(zNonce){
    blob_appendf(pOut, "%s\n", zNonce);
  }
  blob_reset(&bIn);
  return isErr;
}

/*
** Legacy impl of /pikchrshow. pikchrshow_page() will delegate to
** this one if the "legacy" or "ajax" request arguments are set.
**
277
278
279
280
281
282
283
284

285
286
287
288
289
290
291
231
232
233
234
235
236
237

238
239
240
241
242
243
244
245







-
+







  if(P("ajax")!=0){
    /* Called from the JS-side preview updater.
       TODO: respond with JSON instead.*/
    cgi_set_content_type("text/html");
    if(zContent && *zContent){
      Blob out = empty_blob;
      const int isErr =
        pikchr_process(zContent, pikFlags, 0, &out);
        pikchr_process(zContent, pikFlags, &out);
      if(isErr){
        cgi_printf_header("x-pikchrshow-is-error: %d\r\n", isErr);
      }
      CX("%b", &out);
      blob_reset(&out);
    }else{
      CX("<pre>No content! Nothing to render</pre>");
382
383
384
385
386
387
388
389

390
391
392
393
394
395
396
336
337
338
339
340
341
342

343
344
345
346
347
348
349
350







-
+







    CX("<fieldset id='pikchrshow-output-wrapper'>"); {
      CX("<legend></legend>"
         /* Reminder: Firefox does not properly flexbox a LEGEND
            element, always flowing it in column mode. */);
      CX("<div id='pikchrshow-output'>");
      if(*zContent){
        Blob out = empty_blob;
        pikchr_process(zContent, pikFlags, 0, &out);
        pikchr_process(zContent, pikFlags, &out);
        CX("%b", &out);
        blob_reset(&out);
      } CX("</div>"/*#pikchrshow-output*/);
    } CX("</fieldset>"/*#pikchrshow-output-wrapper*/);
  } CX("</div>"/*sbs-wrapper*/);
  builtin_fossil_js_bundle_or("fetch", "copybutton", "popupwidget",
                              "storage", "pikchr", NULL);
506
507
508
509
510
511
512
513
514


515
516
517
518
519
520
521
460
461
462
463
464
465
466


467
468
469
470
471
472
473
474
475







-
-
+
+







          CX("  selected, only that part is evaluated.\n*/\n");
        CX("%s</textarea></div>",zContent/*safe-for-%s*/);
      } CX("</fieldset><!-- .zone-wrapper.input -->");
      CX("<fieldset class='zone-wrapper output'>"); {
        CX("<legend><div class='button-bar'>");
          CX("<button id='btn-render-mode'>Render Mode</button> ");
          CX("<span style='white-space:nowrap'>"
             "<span id='preview-copy-button' "
             "title='Tap to copy to clipboard.'></span>"
             "<button id='preview-copy-button' "
             "title='Tap to copy to clipboard.'><span></span></button>"
             "<label for='preview-copy-button' "
             "title='Tap to copy to clipboard.'></label>"
             "</span>");
        CX("</div></legend>");
        CX("<div id='pikchr-output-wrapper'>");
          CX("<div id='pikchr-output'></div>");
          CX("<textarea class='hidden' id='pikchr-output-text'></textarea>");
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
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







-
-
-
-
-
-
-
-
-
-
-
-
-




-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-






-
-



-
-
-
-







**    -div-source  Set the 'source' CSS class on the div, which tells
**                 CSS to hide the SVG and reveal the source by default.
**
**    -src       Store the input pikchr's source code in the output as
**               a separate element adjacent to the SVG one. Implied
**               by -div-source.
**
**
**    -th        Process the input using TH1 before passing it to pikchr
**
**    -th-novar  Disable $var and $<var> TH1 processing. Use this if the
**               pikchr script uses '$' for its own purposes and that
**               causes issues. This only affects parsing of '$' outside
**               of TH1 script blocks. Code in such blocks is unaffected.
**
**    -th-nosvg  When using -th, output the post-TH1'd script
**               instead of the pikchr-rendered output
**
**    -th-trace  Trace TH1 execution (for debugging purposes)
**
**    -dark      Change pikchr colors to assume a dark-mode theme.
**
**
** The -div-indent/center/left/right flags may not be combined.
**
** TH1-related Notes and Caveats:
**
** If the -th flag is used, this command must open a fossil database
** for certain functionality to work (via a check-out or the -R REPO
** flag). If opening a db fails, execution will continue but any TH1
** commands which require a db will trigger a fatal error.
**
** In Fossil skins, TH1 variables in the form $varName are expanded
** as-is and those in the form $<varName> are htmlized in the
** resulting output. This processor disables the htmlizing step, so $x
** and $<x> are equivalent unless the TH1-processed pikchr script
** invokes the TH1 command [enable_htmlify 1] to enable it. Normally
** that option will interfere with pikchr output, however, e.g. by
** HTML-encoding double-quotes.
**
** Many of the fossil-installed TH1 functions simply do not make any
** sense for pikchr scripts.
*/
void pikchr_cmd(void){
  Blob bIn = empty_blob;
  Blob bOut = empty_blob;
  const char * zInfile = "-";
  const char * zOutfile = "-";
  const int fTh1 = find_option("th",0,0)!=0;
  const int fNosvg = find_option("th-nosvg",0,0)!=0;
  int isErr = 0;
  int pikFlags = find_option("src",0,0)!=0
    ? PIKCHR_PROCESS_SRC : 0;
  u32 fThFlags = TH_INIT_NO_ENCODE
    | (find_option("th-novar",0,0)!=0 ? TH_R2B_NO_VARS : 0);

  Th_InitTraceLog()/*processes -th-trace flag*/;

  if(find_option("div",0,0)!=0){
    pikFlags |= PIKCHR_PROCESS_DIV;
  }else if(find_option("div-indent",0,0)!=0){
    pikFlags |= PIKCHR_PROCESS_DIV_INDENT;
  }else if(find_option("div-center",0,0)!=0){
    pikFlags |= PIKCHR_PROCESS_DIV_CENTER;
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
559
560
561
562
563
564
565







566

567



568
569
570
571

572
573
574







-
-
-
-
-
-
-
+
-

-
-
-
+



-



  if(g.argc>2){
    zInfile = g.argv[2];
  }
  if(g.argc>3){
    zOutfile = g.argv[3];
  }
  blob_read_from_file(&bIn, zInfile, ExtFILE);
  if(fTh1){
    db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0)
      /* ^^^ needed for certain TH1 functions to work */;
    pikFlags |= PIKCHR_PROCESS_TH1;
    if(fNosvg) pikFlags |= PIKCHR_PROCESS_TH1_NOSVG;
  }
  isErr = pikchr_process(blob_str(&bIn), pikFlags,
  isErr = pikchr_process(blob_str(&bIn), pikFlags, &bOut);
                         fTh1 ? fThFlags : 0, &bOut);
  if(isErr){
    fossil_fatal("%s ERROR:%c%b", 1==isErr ? "TH1" : "pikchr",
                 1==isErr ? ' ' : '\n',
                 &bOut);
    fossil_fatal("pikchr ERROR: %b", &bOut);
  }else{
    blob_write_to_file(&bOut, zOutfile);
  }
  Th_PrintTraceLog();
  blob_reset(&bIn);
  blob_reset(&bOut);
}
Changes to src/pqueue.c.
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
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







-
-
+
+
+

-
-
-
-
+
+
+
+
+
+
+
+
+
+



















+
-
+
+
+







**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code used to implement a priority queue.
** A priority queue is a list of items order by a floating point
** value.  We can insert integers with each integer tied to its
** value then extract the integer with the smallest value.
** value.  Each value can be associated with either a pointer or
** an integer.  Items are inserted into the queue in an arbitrary
** order, but are returned in order of the floating point value.
**
** The way this queue is used, we never expect it to contain more
** than 2 or 3 elements, so a simple array is sufficient as the
** implementation.  This could give worst case O(N) insert times,
** but because of the nature of the problem we expect O(1) performance.
** This implementation uses a heap of QueueElement objects.  The
** root of the heap is PQueue.a[0].  Each node a[x] has two daughter
** nodes a[x*2+1] and a[x*2+2].  The mother node of a[y] is a[(y-1)/2]
** (assuming integer division rounded down).  The following is always true:
**
**    The value of any node is less than or equal two the values
**    of both daughter nodes.  (The Heap Property).
**
** A consequence of the heap property is that a[0] always contains
** the node with the smallest value.
**
** Compatibility note:  Some versions of OpenSSL export a symbols
** like "pqueue_insert".  This is, technically, a bug in OpenSSL.
** We work around it here by using "pqueuex_" instead of "pqueue_".
*/
#include "config.h"
#include "pqueue.h"
#include <assert.h>


#if INTERFACE
/*
** An integer can appear in the bag at most once.
** Integers must be positive.
*/
struct PQueue {
  int cnt;   /* Number of entries in the queue */
  int sz;    /* Number of slots in a[] */
  struct QueueElement {
    union {
    int id;          /* ID of the element */
      int id;          /* ID of the element */
      void *p;         /* Pointer to an object */
    } u;
    double value;    /* Value of element.  Kept in ascending order */
  } *a;
};
#endif

/*
** Initialize a PQueue structure
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






























































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
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









+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
+
+
-
-
-
-
-
-








-
+



-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/*
** Change the size of the queue so that it contains N slots
*/
static void pqueuex_resize(PQueue *p, int N){
  p->a = fossil_realloc(p->a, sizeof(p->a[0])*N);
  p->sz = N;
}

/*
** Allocate a new queue entry and return a pointer to it.
*/
static struct QueueElement *pqueuex_new_entry(PQueue *p){
  if( p->cnt+1>p->sz ){
    pqueuex_resize(p, p->cnt+7);
  }
  return &p->a[p->cnt++];
}

/*
** Element p->a[p->cnt-1] has just been inserted.  Shift entries
** around so as to preserve the heap property.
*/
static void pqueuex_rebalance(PQueue *p){
  int i, j;
  struct QueueElement *a = p->a;
  i = p->cnt-1;
  while( (j = (i-1)/2)>=0 && a[j].value>a[i].value ){
    struct QueueElement t = a[j];
    a[j] = a[i];
    a[i] = t;
    i = j;
  }
}

/*
** Insert element e into the queue.
*/
void pqueuex_insert(PQueue *p, int e, double v){
  struct QueueElement *pE = pqueuex_new_entry(p);
  pE->value = v;
  pE->u.id = e;
  pqueuex_rebalance(p);
}
void pqueuex_insert_ptr(PQueue *p, void *pPtr, double v){
  struct QueueElement *pE = pqueuex_new_entry(p);
  pE->value = v;
  pE->u.p = pPtr;
  pqueuex_rebalance(p);
}

/*
** Remove and discard p->a[0] element from the queue.  Rearrange
** nodes to preserve the heap property.
*/
static void pqueuex_pop(PQueue *p){
  int i, j;
  struct QueueElement *a = p->a;
  struct QueueElement tmp;
  i = 0;
  if( p->cnt+1>p->sz ){
    pqueuex_resize(p, p->cnt+5);
  a[0] = a[p->cnt-1];
  p->cnt--;
  }
  for(i=0; i<p->cnt; i++){
    if( p->a[i].value>v ){
  while( (j = i*2+1)<p->cnt ){
    if( j+1<p->cnt && a[j].value > a[j+1].value ) j++;
    if( a[i].value < a[j].value ) break;
      for(j=p->cnt; j>i; j--){
        p->a[j] = p->a[j-1];
      }
    tmp = a[i];
    a[i] = a[j];
    a[j] = tmp;
    i = j;
  }
      break;
    }
  }
  p->a[i].id = e;
  p->a[i].value = v;
  p->cnt++;
}

/*
** Extract the first element from the queue (the element with
** the smallest value) and return its ID.  Return 0 if the queue
** is empty.
*/
int pqueuex_extract(PQueue *p){
  int e, i;
  int e;
  if( p->cnt==0 ){
    return 0;
  }
  e = p->a[0].id;
  for(i=0; i<p->cnt-1; i++){
    p->a[i] = p->a[i+1];
  }
  p->cnt--;
  return e;
  e = p->a[0].u.id;
  pqueuex_pop(p);
  return e;
}
void *pqueuex_extract_ptr(PQueue *p){
  void *pPtr;
  if( p->cnt==0 ){
    return 0;
  }
  pPtr = p->a[0].u.p;
  pqueuex_pop(p);
  return pPtr;
}

/*
** Print the entire heap associated with the test-pqueue command.
*/
static void pqueuex_test_print(PQueue *p){
  int j;
  for(j=0; j<p->cnt; j++){
    fossil_print("(%d) %g/%s ",j,p->a[j].value,p->a[j].u.p);
  }
  fossil_print("\n");
}

/*
** COMMAND: test-pqueue
**
** This command is used for testing the PQueue object.  There are one
** or more arguments, each of the form:
**
**     (1)    NUMBER/TEXT
**     (2)    ^
**     (3)    -v
**
** Form (1) arguments add an entry to the queue with value NUMBER and
** content TEXT.  Form (2) pops off the queue entry with the smallest
** value.  Form (3) (the -v option) causes the heap to be displayed after
** each subsequent operation.
*/
void pqueuex_test_cmd(void){
  int i;
  PQueue x;
  const char *zId;
  int bDebug = 0;

  pqueuex_init(&x);
  for(i=2; i<g.argc; i++){
    const char *zArg = g.argv[i];
    if( strcmp(zArg,"-v")==0 ){
      bDebug = 1;
    }else if( strcmp(zArg, "^")==0 ){
      zId = pqueuex_extract_ptr(&x);
      if( zId==0 ){
        fossil_print("%2d: POP     NULL\n", i);
      }else{
        fossil_print("%2d: POP     \"%s\"\n", i, zId);
      }
      if( bDebug) pqueuex_test_print(&x);
    }else{
      double r = atof(zArg);
      zId = strchr(zArg,'/');
      if( zId==0 ) zId = zArg;
      if( zId[0]=='/' ) zId++;
      pqueuex_insert_ptr(&x, (void*)zId, r);
      fossil_print("%2d: INSERT  \"%s\"\n", i, zId);
      if( bDebug) pqueuex_test_print(&x);
    }
  }
  while( (zId = pqueuex_extract_ptr(&x))!=0 ){
    fossil_print("... POP     \"%s\"\n", zId);
    if( bDebug) pqueuex_test_print(&x);
  }
  pqueuex_clear(&x);
}
Changes to src/printf.c.
11
12
13
14
15
16
17
18

19
20
21
22
23
24
25
11
12
13
14
15
16
17

18
19
20
21
22
23
24
25







-
+







**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains implementions of routines for formatting output
** This file contains implementations of routines for formatting output
** (ex: mprintf()) and for output to the console.
*/
#include "config.h"
#include "printf.h"
#if defined(_WIN32)
#   include <io.h>
#   include <fcntl.h>
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
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







-
+















+







#define etERROR      10 /* Used to indicate no such conversion type */
/* The rest are extensions, not normally found in printf() */
#define etBLOB       11 /* Blob objects.  %b */
#define etBLOBSQL    12 /* Blob objects quoted for SQL.  %B */
#define etSQLESCAPE  13 /* Strings with '\'' doubled.  %q */
#define etSQLESCAPE2 14 /* Strings with '\'' doubled and enclosed in '',
                          NULL pointers replaced by SQL NULL.  %Q */
#define etSQLESCAPE3 15 /* Double '"' characters within an indentifier.  %w */
#define etSQLESCAPE3 15 /* Double '"' characters within an identifier.  %w */
#define etPOINTER    16 /* The %p conversion */
#define etHTMLIZE    17 /* Make text safe for HTML */
#define etHTTPIZE    18 /* Make text safe for HTTP.  "/" encoded as %2f */
#define etURLIZE     19 /* Make text safe for HTTP.  "/" not encoded */
#define etFOSSILIZE  20 /* The fossil header encoding format. */
#define etPATH       21 /* Path type */
#define etWIKISTR    22 /* Timeline comment text rendered from a char*: %W */
#define etSTRINGID   23 /* String with length limit for a hash prefix: %S */
#define etROOT       24 /* String value of g.zTop: %R */
#define etJSONSTR    25 /* String encoded as a JSON string literal: %j
                           Use %!j to include double-quotes around it. */
#define etSHELLESC   26 /* Escape a filename for use in a shell command: %$
                           See blob_append_escaped_arg() for details
                           "%$"  -> adds "./" prefix if necessary.
                           "%!$" -> omits the "./" prefix. */
#define etHEX        27 /* Encode a string as hexadecimal */


/*
** An "etByte" is an 8-bit unsigned value.
*/
typedef unsigned char etByte;

140
141
142
143
144
145
146
147

148
149
150
151
152
153
154
141
142
143
144
145
146
147

148
149
150
151
152
153
154
155







-
+







** most frequently used conversion types first.
**
** NB: When modifying this table is it vital that you also update the fmtchr[]
** variable to match!!!
*/
static const char aDigits[] = "0123456789ABCDEF0123456789abcdef";
static const char aPrefix[] = "-x0\000X0";
static const char fmtchr[] = "dsgzqQbBWhRtTwFSjcouxXfeEGin%p/$";
static const char fmtchr[] = "dsgzqQbBWhRtTwFSjcouxXfeEGin%p/$H";
static const et_info fmtinfo[] = {
  {  'd', 10, 1, etRADIX,      0,  0 },
  {  's',  0, 4, etSTRING,     0,  0 },
  {  'g',  0, 1, etGENERIC,    30, 0 },
  {  'z',  0, 6, etDYNSTRING,  0,  0 },
  {  'q',  0, 4, etSQLESCAPE,  0,  0 },
  {  'Q',  0, 4, etSQLESCAPE2, 0,  0 },
174
175
176
177
178
179
180

181
182
183
184
185
186
187
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189







+







  {  'G',  0, 1, etGENERIC,    14, 0 },
  {  'i', 10, 1, etRADIX,      0,  0 },
  {  'n',  0, 0, etSIZE,       0,  0 },
  {  '%',  0, 0, etPERCENT,    0,  0 },
  {  'p', 16, 0, etPOINTER,    0,  1 },
  {  '/',  0, 0, etPATH,       0,  0 },
  {  '$',  0, 0, etSHELLESC,   0,  0 },
  {  'H',  0, 0, etHEX,        0,  0 },
  {  etERROR, 0,0,0,0,0}  /* Must be last */
};
#define etNINFO count(fmtinfo)

/*
** Verify that the fmtchr[] and fmtinfo[] arrays are in agreement.
**
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+






-
-
+
+
+
-
+
+

-
+

+

-
-
+
-
-
-







static int StrNLen32(const char *z, int N){
  int n = 0;
  while( (N-- != 0) && *(z++)!=0 ){ n++; }
  return n;
}
#endif

/*
** SETTING: timeline-plaintext         boolean default=off
**
** If enabled, no wiki-formatting is done for timeline comment messages.
** Hyperlinks are activated, but they show up on screen using the 
** complete input text, not just the display text.  No other formatting
** is done.
*/
/*
** SETTING: timeline-hard-newlines     boolean default=off
**
** If enabled, the timeline honors newline characters in check-in comments.
** In other words, newlines are converted into <br> for HTML display.
** The default behavior, when this setting is off, is that newlines are
** treated like any other whitespace character.
*/

/*
** Return an appropriate set of flags for wiki_convert() for displaying
** comments on a timeline.  These flag settings are determined by
** configuration parameters.
**
** The altForm2 argument is true for "%!W" (with the "!" alternate-form-2
** flags) and is false for plain "%W".  The ! indicates that the text is
** to be rendered on a form rather than the timeline and that block markup
** flags) and is false for plain "%W".  The ! flag indicates that the
** formatting is for display of a check-in comment on the timeline.  Such
** comments used to be rendered differently, but ever since 2020, they
** is acceptable even if the "timeline-block-markup" setting is false.
** have been rendered identically, so the ! flag does not make any different
** in the output any more.
*/
static int wiki_convert_flags(int altForm2){
int wiki_convert_flags(int altForm2){
  static int wikiFlags = 0;
  (void)altForm2;
  if( wikiFlags==0 ){
    if( altForm2 || db_get_boolean("timeline-block-markup", 0) ){
      wikiFlags = WIKI_INLINE | WIKI_NOBADLINKS;
    wikiFlags = WIKI_INLINE | WIKI_NOBADLINKS;
    }else{
      wikiFlags = WIKI_INLINE | WIKI_NOBLOCK | WIKI_NOBADLINKS;
    }
    if( db_get_boolean("timeline-plaintext", 0) ){
      wikiFlags |= WIKI_LINKSONLY;
    }
    if( db_get_boolean("timeline-hard-newlines", 0) ){
      wikiFlags |= WIKI_NEWLINE;
    }
  }
436
437
438
439
440
441
442
443

444
445
446
447
448
449
450
454
455
456
457
458
459
460

461
462
463
464
465
466
467
468







-
+







    **   flag_altform2               TRUE if a '!' is present.
    **   flag_plussign               TRUE if a '+' is present.
    **   flag_leftjustify            TRUE if a '-' is present or if the
    **                               field width was negative.
    **   flag_zeropad                TRUE if the width began with 0.
    **   flag_long                   TRUE if the letter 'l' (ell) prefixed
    **                               the conversion character.
    **   flag_longlong               TRUE if the letter 'll' (ell ell) prefixed
    **   flag_longlong               TRUE if the letters 'll' (ell ell) prefixed
    **                               the conversion character.
    **   flag_blanksign              TRUE if a ' ' is present.
    **   width                       The specified field width.  This is
    **                               always non-negative.  Zero is the default.
    **   precision                   The specified precision.  The default
    **                               is -1.
    **   xtype                       The class of the conversion.
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
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







+



















-







        int needQuote;
        int limit = flag_alternateform ? va_arg(ap,int) : -1;
        char q = ((xtype==etSQLESCAPE3)?'"':'\'');  /* Quote characters */
        char *escarg = va_arg(ap,char*);
        isnull = escarg==0;
        if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)");
        if( limit<0 ) limit = strlen(escarg);
        if( precision>=0 && precision<limit ) limit = precision;
        for(i=n=0; i<limit; i++){
          if( escarg[i]==q )  n++;
        }
        needQuote = !isnull && xtype==etSQLESCAPE2;
        n += i + 1 + needQuote*2;
        if( n>etBUFSIZE ){
          bufpt = zExtra = fossil_malloc( n );
        }else{
          bufpt = buf;
        }
        j = 0;
        if( needQuote ) bufpt[j++] = q;
        for(i=0; i<limit; i++){
          bufpt[j++] = ch = escarg[i];
          if( ch==q ) bufpt[j++] = ch;
        }
        if( needQuote ) bufpt[j++] = q;
        bufpt[j] = 0;
        length = j;
        if( precision>=0 && precision<length ) length = precision;
        break;
      }
      case etHTMLIZE: {
        int limit = flag_alternateform ? va_arg(ap,int) : -1;
        char *zMem = va_arg(ap,char*);
        if( zMem==0 ) zMem = "";
        zExtra = bufpt = htmlize(zMem, limit);
841
842
843
844
845
846
847











848
849
850
851
852
853
854
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







+
+
+
+
+
+
+
+
+
+
+







        break;
      }
      case etSHELLESC: {
        char *zArg = va_arg(ap, char*);
        blob_append_escaped_arg(pBlob, zArg, !flag_altform2);
        length = width = 0;
        break;
      }
      case etHEX: {
        char *zArg = va_arg(ap, char*);
        int szArg = (int)strlen(zArg);
        int szBlob = blob_size(pBlob);
        u8 *aBuf;
        blob_resize(pBlob, szBlob+szArg*2+1);
        aBuf = (u8*)&blob_buffer(pBlob)[szBlob];
        encode16((const u8*)zArg, aBuf, szArg);
        length = width = 0;
        break;
      }
      case etERROR:
        buf[0] = '%';
        buf[1] = c;
        errorflag = 0;
        idx = 1+(c!=0);
        blob_append(pBlob,"%",idx);
1046
1047
1048
1049
1050
1051
1052

1053
1054
1055
1056
1057
1058
1059
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089







+







void fossil_errorlog(const char *zFormat, ...){
  struct tm *pNow;
  time_t now;
  FILE *out;
  const char *z;
  int i;
  int bDetail = 0;
  int bBrief = 0;
  va_list ap;
  static const char *const azEnv[] = { "HTTP_HOST", "HTTP_REFERER",
      "HTTP_USER_AGENT",
      "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD",
      "REQUEST_URI", "SCRIPT_NAME" };
  if( g.zErrlog==0 ) return;
  if( g.zErrlog[0]=='-' && g.zErrlog[1]==0 ){
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
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







+
+


-
+


+
+
-
+












-
+







  fprintf(out, "------------- %04d-%02d-%02d %02d:%02d:%02d UTC ------------\n",
          pNow->tm_year+1900, pNow->tm_mon+1, pNow->tm_mday,
          pNow->tm_hour, pNow->tm_min, pNow->tm_sec);
  va_start(ap, zFormat);
  if( zFormat[0]=='X' ){
    bDetail = 1;
    zFormat++;
  }else if( strncmp(zFormat,"SMTP:",5)==0 ){
    bBrief = 1;
  }
  vfprintf(out, zFormat, ap);
  fprintf(out, "\n");
  fprintf(out, " (pid %d)\n", (int)getpid());
  va_end(ap);
  if( g.zPhase!=0 ) fprintf(out, "while in %s\n", g.zPhase);
  if( bBrief ){
    /* Say nothing more */
  if( bDetail ){
  }else if( bDetail ){
    cgi_print_all(1,3,out);
  }else{
    for(i=0; i<count(azEnv); i++){
      char *p;
      if( (p = fossil_getenv(azEnv[i]))!=0 && p[0]!=0 ){
        fprintf(out, "%s=%s\n", azEnv[i], p);
        fossil_path_free(p);
      }else if( (z = P(azEnv[i]))!=0 && z[0]!=0 ){
        fprintf(out, "%s=%s\n", azEnv[i], z);
      }
    }
  }
  fclose(out);
  if( out!=stderr ) fclose(out);
}

/*
** The following variable becomes true while processing a fatal error
** or a panic.  If additional "recursive-fatal" errors occur while
** shutting down, the recursive errors are silently ignored.
*/
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
1148
1149
1150
1151

1152
1153

1154
1155
1156
1157
1158
1159
1160
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
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192

1193
1194

1195
1196
1197
1198
1199
1200
1201
1202







+
-
+
+


+
+
+
+
+
-
-
+
+
+

















-
+

-
+







  }
  else
#endif
  if( g.cgiOutput==1 && g.db ){
    g.cgiOutput = 2;
    cgi_reset_content();
    cgi_set_content_type("text/html");
    if( g.zLogin!=0 ){
    style_set_current_feature("error");
      style_set_current_feature("error");
    }
    style_header("Bad Request");
    etag_cancel();
    if( g.zLogin==0 ){
      /* Do not give unnecessary clues about a malfunction to robots */
      @ <p>Something did not work right.</p>
      @ <p>%h(z)</p>
    }else{
    @ <p class="generalError">%h(z)</p>
    cgi_set_status(400, "Bad Request");
      @ <p class="generalError">%h(z)</p>
      cgi_set_status(400, "Bad Request");
    }
    style_finish_page();
    cgi_reply();
  }else if( !g.fQuiet ){
    fossil_force_newline();
    fossil_trace("%s\n", z);
  }
  return rc;
}

/*
** Print an error message, rollback all databases, and quit.  These
** routines never return and produce a non-zero process exit status.
**
** The main difference between fossil_fatal() and fossil_panic() is that
** fossil_panic() makes an entry in the error log whereas fossil_fatal()
** does not. On POSIX platforms, if there is not an error log, then both
** routines work similarly with respect to user-visible effects.  Hence,
** the routines are interchangable for commands and only act differently
** the routines are interchangeable for commands and only act differently
** when processing web pages. On the Windows platform, fossil_panic()
** also displays a pop-up stating that an error has occured and allowing
** also displays a pop-up stating that an error has occurred and allowing
** just-in-time debugging to commence. On all platforms, fossil_panic()
** ends execution with a SIGABRT signal, bypassing atexit processing.
** This signal can also produce a core dump on POSIX platforms.
**
** Use fossil_fatal() for malformed inputs that should be reported back
** to the user, but which do not represent a configuration problem or bug.
**
Changes to src/purge.c.
60
61
62
63
64
65
66
67

68
69
70
71
72
73
74
60
61
62
63
64
65
66

67
68
69
70
71
72
73
74







-
+







#if INTERFACE
#define PURGE_MOVETO_GRAVEYARD  0x0001    /* Move artifacts in graveyard */
#define PURGE_EXPLAIN_ONLY      0x0002    /* Show what would have happened */
#define PURGE_PRINT_SUMMARY     0x0004    /* Print a summary report at end */
#endif

/*
** This routine purges multiple artifacts from the repository, transfering
** This routine purges multiple artifacts from the repository, transferring
** those artifacts into the PURGEITEM table.
**
** Prior to invoking this routine, the caller must create a (TEMP) table
** named zTab that contains the RID of every artifact to be purged.
**
** This routine does the following:
**
118
119
120
121
122
123
124
125

126
127
128
129
130
131
132
118
119
120
121
122
123
124

125
126
127
128
129
130
131
132







-
+







  if( purgeFlags & PURGE_EXPLAIN_ONLY ){
    db_end_transaction(0);
    return 0;
  }

  /* Make sure we are not removing a manifest that is the baseline of some
  ** manifest that is being left behind.  This step is not strictly necessary.
  ** is is just a safety check. */
  ** It is just a safety check. */
  if( purge_baseline_out_from_under_delta(zTab) ){
    fossil_panic("attempt to purge a baseline manifest without also purging "
                 "all of its deltas");
  }

  /* Make sure that no delta that is left behind requires a purged artifact
  ** as its basis.  If such artifacts exist, go ahead and undelta them now.
254
255
256
257
258
259
260
261

262
263
264
265
266
267
268
254
255
256
257
258
259
260

261
262
263
264
265
266
267
268







-
+







** If the bExclusive flag is true, then the set is only expanded by
** artifacts that are used exclusively by the check-ins in the set.
** When bExclusive is false, then all artifacts used by the check-ins
** are added even if those artifacts are also used by other check-ins
** not in the set.
**
** The "fossil publish" command with the (undocumented) --test and
** --exclusive options can be used for interactiving testing of this
** --exclusive options can be used for interactive testing of this
** function.
*/
void find_checkin_associates(const char *zTab, int bExclusive){
  db_begin_transaction();

  /* Compute the set of files that need to be added to zTab */
  db_multi_exec("CREATE TEMP TABLE \"%w_files\"(fid INTEGER PRIMARY KEY)",zTab);
Changes to src/rebuild.c.
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
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







+
-
+











-
-
+
+







}

/*
** Check to see if the "sym-trunk" tag exists.  If not, create it
** and attach it to the very first check-in.
*/
static void rebuild_tag_trunk(void){
  const char *zMainBranch = db_main_branch();
  int tagid = db_int(0, "SELECT 1 FROM tag WHERE tagname='sym-trunk'");
  int tagid = db_int(0, "SELECT 1 FROM tag WHERE tagname='sym-%q'",zMainBranch);
  int rid;
  char *zUuid;

  if( tagid>0 ) return;
  rid = db_int(0, "SELECT pid FROM plink AS x WHERE NOT EXISTS("
                  "  SELECT 1 FROM plink WHERE cid=x.pid)");
  if( rid==0 ) return;

  /* Add the trunk tag to the root of the whole tree */
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  if( zUuid==0 ) return;
  tag_add_artifact("sym-", "trunk", zUuid, 0, 2, 0, 0);
  tag_add_artifact("", "branch", zUuid, "trunk", 2, 0, 0);
  tag_add_artifact("sym-", zMainBranch, zUuid, 0, 2, 0, 0);
  tag_add_artifact("", "branch", zUuid, zMainBranch, 2, 0, 0);
}

/*
** Core function to rebuild the information in the derived tables of a
** fossil repository from the blobs. This function is shared between
** 'rebuild_database' ('rebuild') and 'reconstruct_cmd'
** ('reconstruct'), both of which have to regenerate this information
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
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







+
+
+

-
-
+
+






-
+





+
+
+







  if (ttyOutput && !g.fQuiet) {
    percent_complete(0);
  }
  manifest_disable_event_triggers();
  rebuild_update_schema();
  blob_init(&sql, 0, 0);
  db_unprotect(PROTECT_ALL);
#ifndef SQLITE_PREPARE_DONT_LOG
  g.dbIgnoreErrors++;
#endif
  db_prepare(&q,
     "SELECT name FROM sqlite_schema /*scan*/"
     " WHERE type='table'"
     "SELECT name FROM pragma_table_list /*scan*/"
     " WHERE schema='repository' AND type IN ('table','virtual')"
     " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
                       "'config','shun','private','reportfmt',"
                       "'concealed','accesslog','modreq',"
                       "'purgeevent','purgeitem','unversioned',"
                       "'subscriber','pending_alert','chat')"
     " AND name NOT GLOB 'sqlite_*'"
     " AND name NOT GLOB 'fx_*'"
     " AND name NOT GLOB 'fx_*';"
  );
  while( db_step(&q)==SQLITE_ROW ){
    blob_appendf(&sql, "DROP TABLE IF EXISTS \"%w\";\n", db_column_text(&q,0));
  }
  db_finalize(&q);
#ifndef SQLITE_PREPARE_DONT_LOG
  g.dbIgnoreErrors--;
#endif
  db_multi_exec("%s", blob_str(&sql)/*safe-for-%s*/);
  blob_reset(&sql);
  db_multi_exec("%s", zRepositorySchema2/*safe-for-%s*/);
  ticket_create_table(0);
  shun_artifacts();

  db_multi_exec(
1406
1407
1408
1409
1410
1411
1412
1413

1414
1415
1416
1417
1418
1419
1420
1413
1414
1415
1416
1417
1418
1419

1420
1421
1422
1423
1424
1425
1426
1427







-
+







               zPassword);
  hash_user_password(g.zLogin);
}

/*
** COMMAND: deconstruct*
**
** Usage %fossil deconstruct ?OPTIONS? DESTINATION
** 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.
Changes to src/regexp.c.
51
52
53
54
55
56
57


58
59
60
61
62
63

64
65
66
67
68
69
70
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73







+
+






+







** A nondeterministic finite automaton (NFA) is used for matching, so the
** performance is bounded by O(N*M) where N is the size of the regular
** expression and M is the size of the input string.  The matcher never
** exhibits exponential behavior.  Note that the X{p,q} operator expands
** to p copies of X following by q-p copies of X? and that the size of the
** regular expression in the O(N*M) performance bound is computed after
** this expansion.
**
** To help prevent DoS attacks, the maximum size of the NFA is restricted.
*/
#include "config.h"
#include "regexp.h"

/* The end-of-input character */
#define RE_EOF            0    /* End of input */
#define RE_START  0xfffffff    /* Start of input - larger than an UTF-8 */

/* The NFA is implemented as sequence of opcodes taken from the following
** set.  Each opcode has a single integer argument.
*/
#define RE_OP_MATCH       1    /* Match the one character in the argument */
#define RE_OP_ANY         2    /* Match any one character.  (Implements ".") */
#define RE_OP_ANYSTAR     3    /* Special optimized version of .* */
78
79
80
81
82
83
84

85
86
87
88
89
90
91
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95







+







#define RE_OP_WORD       11    /* Perl word character [A-Za-z0-9_] */
#define RE_OP_NOTWORD    12    /* Not a perl word character */
#define RE_OP_DIGIT      13    /* digit:  [0-9] */
#define RE_OP_NOTDIGIT   14    /* Not a digit */
#define RE_OP_SPACE      15    /* space:  [ \t\n\r\v\f] */
#define RE_OP_NOTSPACE   16    /* Not a digit */
#define RE_OP_BOUNDARY   17    /* Boundary between word and non-word */
#define RE_OP_ATSTART    18    /* Currently at the start of the string */

/* Each opcode is a "state" in the NFA */
typedef unsigned short ReStateNumber;

/* Because this is an NFA and not a DFA, multiple states can be active at
** once.  An instance of the following object records all active states in
** the NFA.  The implementation is optimized for the common case where the
111
112
113
114
115
116
117
118

119
120

121
122
123
124
125
126
127
115
116
117
118
119
120
121

122
123
124
125
126
127
128
129
130
131
132







-
+


+







struct ReCompiled {
  ReInput sIn;                /* Regular expression text */
  const char *zErr;           /* Error message to return */
  char *aOp;                  /* Operators for the virtual machine */
  int *aArg;                  /* Arguments to each operator */
  unsigned (*xNextChar)(ReInput*);  /* Next character function */
  unsigned char zInit[12];    /* Initial text to match */
  int nInit;                  /* Number of characters in zInit */
  int nInit;                  /* Number of bytes in zInit */
  unsigned nState;            /* Number of entries in aOp[] and aArg[] */
  unsigned nAlloc;            /* Slots allocated for aOp[] and aArg[] */
  unsigned mxAlloc;           /* Complexity limit */
};
#endif

/* Add a state to the given state set if it is not already there */
static void re_add_state(ReStateSet *pSet, int newState){
  unsigned i;
  for(i=0; i<pSet->nState; i++) if( pSet->aState[i]==newState ) return;
142
143
144
145
146
147
148
149

150
151
152
153
154
155
156
147
148
149
150
151
152
153

154
155
156
157
158
159
160
161







-
+







      c = (c&0x1f)<<6 | (p->z[p->i++]&0x3f);
      if( c<0x80 ) c = 0xfffd;
    }else if( (c&0xf0)==0xe0 && p->i+1<p->mx && (p->z[p->i]&0xc0)==0x80
           && (p->z[p->i+1]&0xc0)==0x80 ){
      c = (c&0x0f)<<12 | ((p->z[p->i]&0x3f)<<6) | (p->z[p->i+1]&0x3f);
      p->i += 2;
      if( c<=0x7ff || (c>=0xd800 && c<=0xdfff) ) c = 0xfffd;
    }else if( (c&0xf8)==0xf0 && p->i+3<p->mx && (p->z[p->i]&0xc0)==0x80
    }else if( (c&0xf8)==0xf0 && p->i+2<p->mx && (p->z[p->i]&0xc0)==0x80
           && (p->z[p->i+1]&0xc0)==0x80 && (p->z[p->i+2]&0xc0)==0x80 ){
      c = (c&0x07)<<18 | ((p->z[p->i]&0x3f)<<12) | ((p->z[p->i+1]&0x3f)<<6)
                       | (p->z[p->i+2]&0x3f);
      p->i += 3;
      if( c<=0xffff || c>0x10ffff ) c = 0xfffd;
    }else{
      c = 0xfffd;
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
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







-
+


















+







*/
int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){
  ReStateSet aStateSet[2], *pThis, *pNext;
  ReStateNumber aSpace[100];
  ReStateNumber *pToFree;
  unsigned int i = 0;
  unsigned int iSwap = 0;
  int c = RE_EOF+1;
  int c = RE_START;
  int cPrev = 0;
  int rc = 0;
  ReInput in;

  in.z = zIn;
  in.i = 0;
  in.mx = nIn>=0 ? nIn : (int)strlen((char const*)zIn);

  /* Look for the initial prefix match, if there is one. */
  if( pRe->nInit ){
    unsigned char x = pRe->zInit[0];
    while( in.i+pRe->nInit<=in.mx
     && (zIn[in.i]!=x ||
         strncmp((const char*)zIn+in.i, (const char*)pRe->zInit, pRe->nInit)!=0)
    ){
      in.i++;
    }
    if( in.i+pRe->nInit>in.mx ) return 0;
    c = RE_START-1;
  }

  if( pRe->nState<=(sizeof(aSpace)/(sizeof(aSpace[0])*2)) ){
    pToFree = 0;
    aStateSet[0].aState = aSpace;
  }else{
    pToFree = fossil_malloc( sizeof(ReStateNumber)*2*pRe->nState );
229
230
231
232
233
234
235




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







+
+
+
+







    pNext->nState = 0;
    for(i=0; i<pThis->nState; i++){
      int x = pThis->aState[i];
      switch( pRe->aOp[x] ){
        case RE_OP_MATCH: {
          if( pRe->aArg[x]==c ) re_add_state(pNext, x+1);
          break;
        }
        case RE_OP_ATSTART: {
          if( cPrev==RE_START ) re_add_state(pThis, x+1);
          break;
        }
        case RE_OP_ANY: {
          if( c!=0 ) re_add_state(pNext, x+1);
          break;
        }
        case RE_OP_WORD: {
          if( re_word_char(c) ) re_add_state(pNext, x+1);
282
283
284
285
286
287
288
289

290
291

292
293
294
295
296
297
298
292
293
294
295
296
297
298

299
300

301
302
303
304
305
306
307
308







-
+

-
+







        }
        case RE_OP_ACCEPT: {
          rc = 1;
          goto re_match_end;
        }
        case RE_OP_CC_EXC: {
          if( c==0 ) break;
          /* fall-through */
          /* fall-through */ goto re_op_cc_inc;
        }
        case RE_OP_CC_INC: {
        case RE_OP_CC_INC: re_op_cc_inc: {
          int j = 1;
          int n = pRe->aArg[x];
          int hit = 0;
          for(j=1; j>0 && j<n; j++){
            if( pRe->aOp[x+j]==RE_OP_CC_VALUE ){
              if( pRe->aArg[x+j]==c ){
                hit = 1;
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
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







+
+
-
+











+

-
+


-
+







          if( hit ) re_add_state(pNext, x+n);
          break;
        }
      }
    }
  }
  for(i=0; i<pNext->nState; i++){
    int x = pNext->aState[i];
    while( pRe->aOp[x]==RE_OP_GOTO ) x += pRe->aArg[x];
    if( pRe->aOp[pNext->aState[i]]==RE_OP_ACCEPT ){ rc = 1; break; }
    if( pRe->aOp[x]==RE_OP_ACCEPT ){ rc = 1; break; }
  }
re_match_end:
  fossil_free(pToFree);
  return rc;
}

/* Resize the opcode and argument arrays for an RE under construction.
*/
static int re_resize(ReCompiled *p, int N){
  char *aOp;
  int *aArg;
  if( N>p->mxAlloc ){ p->zErr = "REGEXP pattern too big"; return 1; }
  aOp = fossil_realloc(p->aOp, N*sizeof(p->aOp[0]));
  if( aOp==0 ) return 1;
  if( aOp==0 ){ p->zErr = "out of memory"; return 1; }
  p->aOp = aOp;
  aArg = fossil_realloc(p->aArg, N*sizeof(p->aArg[0]));
  if( aArg==0 ) return 1;
  if( aArg==0 ){ p->zErr = "out of memory"; return 1; }
  p->aArg = aArg;
  p->nAlloc = N;
  return 0;
}

/* Insert a new opcode and argument into an RE under construction.  The
** insertion point is just prior to existing opcode iBefore.
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
479
480
481
482
483
484
485

486
487
488
489
490
491
492







-







  int iStart;
  unsigned c;
  const char *zErr;
  while( (c = p->xNextChar(&p->sIn))!=0 ){
    iStart = p->nState;
    switch( c ){
      case '|':
      case '$':
      case ')': {
        p->sIn.i--;
        return 0;
      }
      case '(': {
        zErr = re_subcompile_re(p);
        if( zErr ) return zErr;
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
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








+
+
+
+
+
+
+
+

-
-
+
+

-
+
+
+
+
+




-
-
+
+
+
+
+
+

-
+





+









-
+




-
+







        re_append(p, RE_OP_FORK, iPrev - p->nState);
        break;
      }
      case '?': {
        if( iPrev<0 ) return "'?' without operand";
        re_insert(p, iPrev, RE_OP_FORK, p->nState - iPrev+1);
        break;
      }
      case '$': {
        re_append(p, RE_OP_MATCH, RE_EOF);
        break;
      }
      case '^': {
        re_append(p, RE_OP_ATSTART, 0);
        break;
      }
      case '{': {
        int m = 0, n = 0;
        int sz, j;
        unsigned int m = 0, n = 0;
        unsigned int sz, j;
        if( iPrev<0 ) return "'{m,n}' without operand";
        while( (c=rePeek(p))>='0' && c<='9' ){ m = m*10 + c - '0'; p->sIn.i++; }
        while( (c=rePeek(p))>='0' && c<='9' ){
          m = m*10 + c - '0';
          if( m*2>p->mxAlloc ) return "REGEXP pattern too big";
          p->sIn.i++;
        }
        n = m;
        if( c==',' ){
          p->sIn.i++;
          n = 0;
          while( (c=rePeek(p))>='0' && c<='9' ){ n = n*10 + c-'0'; p->sIn.i++; }
        }
          while( (c=rePeek(p))>='0' && c<='9' ){
            n = n*10 + c-'0';
            if( n*2>p->mxAlloc ) return "REGEXP pattern too big";
            p->sIn.i++;
          }
        }
        if( c!='}' ) return "unmatched '{'";
        if( n>0 && n<m ) return "n less than m in '{m,n}'";
        if( n<m ) return "n less than m in '{m,n}'";
        p->sIn.i++;
        sz = p->nState - iPrev;
        if( m==0 ){
          if( n==0 ) return "both m and n are zero in '{m,n}'";
          re_insert(p, iPrev, RE_OP_FORK, sz+1);
          iPrev++;
          n--;
        }else{
          for(j=1; j<m; j++) re_copy(p, iPrev, sz);
        }
        for(j=m; j<n; j++){
          re_append(p, RE_OP_FORK, sz+1);
          re_copy(p, iPrev, sz);
        }
        if( n==0 && m>0 ){
          re_append(p, RE_OP_FORK, -sz);
          re_append(p, RE_OP_FORK, -(int)sz);
        }
        break;
      }
      case '[': {
        int iFirst = p->nState;
        unsigned int iFirst = p->nState;
        if( rePeek(p)=='^' ){
          re_append(p, RE_OP_CC_EXC, 0);
          p->sIn.i++;
        }else{
          re_append(p, RE_OP_CC_INC, 0);
        }
        while( (c = p->xNextChar(&p->sIn))!=0 ){
559
560
561
562
563
564
565
566

567
568
569
570
571
572
573
588
589
590
591
592
593
594

595
596
597
598
599
600
601
602







-
+







            re_append(p, RE_OP_CC_RANGE, c);
          }else{
            re_append(p, RE_OP_CC_VALUE, c);
          }
          if( rePeek(p)==']' ){ p->sIn.i++; break; }
        }
        if( c==0 ) return "unclosed '['";
        p->aArg[iFirst] = p->nState - iFirst;
        if( p->nState>iFirst ) p->aArg[iFirst] = p->nState - iFirst;
        break;
      }
      case '\\': {
        int specialOp = 0;
        switch( rePeek(p) ){
          case 'b': specialOp = RE_OP_BOUNDARY;   break;
          case 'd': specialOp = RE_OP_DIGIT;      break;
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
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







-
+
+
+
+
+
+











+

+

-
+














-
-
-
-
-
+












-
+





-
+

-
+



-
+








/*
** Compile a textual regular expression in zIn[] into a compiled regular
** expression suitable for us by re_match() and return a pointer to the
** compiled regular expression in *ppRe.  Return NULL on success or an
** error message if something goes wrong.
*/
const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){
static const char *re_compile(
  ReCompiled **ppRe,      /* OUT: write compiled NFA here */
  const char *zIn,        /* Input regular expression */
  int mxRe,               /* Complexity limit */
  int noCase              /* True for caseless comparisons */
){
  ReCompiled *pRe;
  const char *zErr;
  int i, j;

  *ppRe = 0;
  pRe = fossil_malloc( sizeof(*pRe) );
  if( pRe==0 ){
    return "out of memory";
  }
  memset(pRe, 0, sizeof(*pRe));
  pRe->xNextChar = noCase ? re_next_char_nocase : re_next_char;
  pRe->mxAlloc = mxRe;
  if( re_resize(pRe, 30) ){
    zErr = pRe->zErr;
    re_free(pRe);
    return "out of memory";
    return zErr;
  }
  if( zIn[0]=='^' ){
    zIn++;
  }else{
    re_append(pRe, RE_OP_ANYSTAR, 0);
  }
  pRe->sIn.z = (unsigned char*)zIn;
  pRe->sIn.i = 0;
  pRe->sIn.mx = (int)strlen(zIn);
  zErr = re_subcompile_re(pRe);
  if( zErr ){
    re_free(pRe);
    return zErr;
  }
  if( rePeek(pRe)=='$' && pRe->sIn.i+1>=pRe->sIn.mx ){
    re_append(pRe, RE_OP_MATCH, RE_EOF);
    re_append(pRe, RE_OP_ACCEPT, 0);
    *ppRe = pRe;
  }else if( pRe->sIn.i>=pRe->sIn.mx ){
  if( pRe->sIn.i>=pRe->sIn.mx ){
    re_append(pRe, RE_OP_ACCEPT, 0);
    *ppRe = pRe;
  }else{
    re_free(pRe);
    return "unrecognized character";
  }

  /* The following is a performance optimization.  If the regex begins with
  ** ".*" (if the input regex lacks an initial "^") and afterwards there are
  ** one or more matching characters, enter those matching characters into
  ** zInit[].  The re_match() routine can then search ahead in the input
  ** string looking for the initial match without having to run the whole
  ** regex engine over the string.  Do not worry able trying to match
  ** regex engine over the string.  Do not worry about trying to match
  ** unicode characters beyond plane 0 - those are very rare and this is
  ** just an optimization. */
  if( pRe->aOp[0]==RE_OP_ANYSTAR && !noCase ){
    for(j=0, i=1; j<(int)sizeof(pRe->zInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){
      unsigned x = pRe->aArg[i];
      if( x<=127 ){
      if( x<=0x7f ){
        pRe->zInit[j++] = (unsigned char)x;
      }else if( x<=0xfff ){
      }else if( x<=0x7ff ){
        pRe->zInit[j++] = (unsigned char)(0xc0 | (x>>6));
        pRe->zInit[j++] = 0x80 | (x&0x3f);
      }else if( x<=0xffff ){
        pRe->zInit[j++] = (unsigned char)(0xd0 | (x>>12));
        pRe->zInit[j++] = (unsigned char)(0xe0 | (x>>12));
        pRe->zInit[j++] = 0x80 | ((x>>6)&0x3f);
        pRe->zInit[j++] = 0x80 | (x&0x3f);
      }else{
        break;
      }
    }
    if( j>0 && pRe->zInit[j-1]==0 ) j--;
706
707
708
709
710
711
712
713

714
715




716
717
718
719
720
721
722
723
724
738
739
740
741
742
743
744

745
746
747
748
749
750
751
752

753
754
755
756
757
758
759







-
+


+
+
+
+

-







  int setAux = 0;           /* True to invoke sqlite3_set_auxdata() */

  (void)argc;  /* Unused */
  pRe = sqlite3_get_auxdata(context, 0);
  if( pRe==0 ){
    zPattern = (const char*)sqlite3_value_text(argv[0]);
    if( zPattern==0 ) return;
    zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0);
    zErr = fossil_re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0);
    if( zErr ){
      re_free(pRe);
      /* The original SQLite function from which this code was copied raises
      ** an error if the REGEXP contained a syntax error.  This variant
      ** silently fails to match, as that works better for Fossil.
      ** sqlite3_result_error(context, zErr, -1); */
      sqlite3_result_int(context, 0);
      /* sqlite3_result_error(context, zErr, -1); */
      return;
    }
    if( pRe==0 ){
      sqlite3_result_error_nomem(context);
      return;
    }
    setAux = 1;
734
735
736
737
738
739
740
741


742
743
744
745
746


747
748
749
750



























































751
752
753
754
755
756
757
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
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
843
844
845
846
847
848
849
850
851
852
853







-
+
+




-
+
+




+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








/*
** Invoke this routine to register the regexp() function with the
** SQLite database connection.
*/
int re_add_sql_func(sqlite3 *db){
  int rc;
  rc = sqlite3_create_function(db, "regexp", 2, SQLITE_UTF8|SQLITE_INNOCUOUS,
  rc = sqlite3_create_function(db, "regexp", 2,
                           SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC,
                               0, re_sql_func, 0, 0);
  if( rc==SQLITE_OK ){
    /* The regexpi(PATTERN,STRING) function is a case-insensitive version
    ** of regexp(PATTERN,STRING). */
    rc = sqlite3_create_function(db, "regexpi", 2, SQLITE_UTF8|SQLITE_INNOCUOUS,
    rc = sqlite3_create_function(db, "regexpi", 2,
                           SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC,
                                 (void*)db, re_sql_func, 0, 0);
  }
  return rc;
}

/*
** The input zIn is a string that we want to match exactly as part of
** a regular expression.  Return a new string (in space obtained from
** fossil_malloc() or the equivalent) that escapes all regexp syntax
** characters in zIn.
*/
char *re_quote(const char *zIn){
  Blob out;
  blob_init(&out, 0, 0);
  while( zIn[0] ){
    switch( zIn[0] ){
      case '.':
      case '?':
      case '*':
      case '+':
      case '\\':
      case '(':
      case ')':
      case '[':
      case ']':
      case '|':
      case '^':
      case '$':
      case '{':
      case '}': {
        blob_appendf(&out,"\\x%02x", (unsigned char)zIn[0]);
        break;
      }
      default: {
        blob_append_char(&out, zIn[0]);
        break;
      }
    }
    zIn++;
  }
  blob_materialize(&out);
  return out.aData;
}

/*
** SETTING:  regexp-limit                  width=8 default=1000
**
** Limit the size of the bytecode used to implement a regular expression
** to this many steps.  It is important to limit this to avoid possible
** DoS attacks.
*/

/*
** Compile an RE using re_maxlen().
*/
const char *fossil_re_compile(
  ReCompiled **ppRe,      /* OUT: write compiled NFA here */
  const char *zIn,        /* Input regular expression */
  int noCase              /* True for caseless comparisons */
){
  int mxLen = g.db ? db_get_int("regexp-limit",1000) : 1000;
  return re_compile(ppRe, zIn, mxLen, noCase);
}

/*
** Run a "grep" over a single file read from disk.
*/
static void grep_file(ReCompiled *pRe, const char *zFile, FILE *in){
  int ln = 0;
  int n;
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
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







+




+

+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+

-
+



-
+







** Usage: %fossil test-grep REGEXP [FILE...]
**
** Run a regular expression match over the named disk files, or against
** standard input if no disk files are named on the command-line.
**
** Options:
**   -i|--ignore-case    Ignore case
**   --robot-exception   Use the robot-exception setting as the REGEXP
*/
void re_test_grep(void){
  ReCompiled *pRe;
  const char *zErr;
  int iFileList = 3;
  int ignoreCase = find_option("ignore-case","i",0)!=0;
  int bRobot = find_option("robot-exception",0,0)!=0;
  if( bRobot ){
    const char *zRe;
    db_find_and_open_repository(0,0);
    verify_all_options();
    zRe = db_get("robot-exception","^$");
    zErr = fossil_re_compile(&pRe, zRe, ignoreCase);
    iFileList = 2;
  }else{
    verify_all_options();
  if( g.argc<3 ){
    usage("REGEXP [FILE...]");
  }
  zErr = re_compile(&pRe, g.argv[2], ignoreCase);
    if( g.argc<3 ){
      usage("REGEXP [FILE...]");
    }
    zErr = fossil_re_compile(&pRe, g.argv[2], ignoreCase);
  }
  if( zErr ) fossil_fatal("%s", zErr);
  if( g.argc==3 ){
  if( g.argc==iFileList ){
    grep_file(pRe, "-", stdin);
  }else{
    int i;
    for(i=3; i<g.argc; i++){
    for(i=iFileList; i<g.argc; i++){
      FILE *in = fossil_fopen(g.argv[i], "rb");
      if( in==0 ){
        fossil_warning("cannot open \"%s\"", g.argv[i]);
      }else{
        grep_file(pRe, g.argv[i], in);
        fclose(in);
      }
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
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







-
+















-
+







  int nSearch = 0;
  Stmt q;


  if( find_option("ignore-case","i",0)!=0 ) ignoreCase = 1;
  if( find_option("files-with-matches","l",0)!=0 ) flags |= GREP_EXISTS;
  if( find_option("verbose",0,0)!=0 ) bVerbose = 1;
  if( find_option("quiet","q",0) ) flags |= GREP_QUIET|GREP_EXISTS;
  if( g.fQuiet ) flags |= GREP_QUIET|GREP_EXISTS;
  bNoMsg = find_option("no-messages","s",0)!=0;
  bOnce = find_option("once",0,0)!=0;
  bInvert = find_option("invert-match","v",0)!=0;
  if( bInvert ){
    flags |= GREP_QUIET|GREP_EXISTS;
  }
  cntFlag = find_option("count","c",0)!=0;
  if( cntFlag ){
    flags |= GREP_QUIET|GREP_EXISTS;
  }
  db_find_and_open_repository(0, 0);
  verify_all_options();
  if( g.argc<4 ){
    usage("REGEXP FILENAME ...");
  }
  zErr = re_compile(&pRe, g.argv[2], ignoreCase);
  zErr = fossil_re_compile(&pRe, g.argv[2], ignoreCase);
  if( zErr ) fossil_fatal("%s", zErr);

  add_content_sql_commands(g.db);
  db_multi_exec("CREATE TEMP TABLE arglist(iname,fname,fnid);");
  for(ii=3; ii<g.argc; ii++){
    const char *zTarget = g.argv[ii];
    if( file_tree_name(zTarget, &fullName, 0, 1) ){
975
976
977
978
979
980
981

















































































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
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
1169
1170
1171







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
    if( bInvert ){
      fossil_print("%d\n", nSearch-nMatch);
    }else{
      fossil_print("%d\n", nMatch);
    }
  }
}

/*
** WEBPAGE: re_rules
**
** Show a summary of the regular expression matching rules for Fossil.
*/
void re_rules_page(void){
  style_set_current_feature("wiki");
  style_header("Regular Expression Syntax");
  @ <p>Syntax rules for regular expression matching in Fossil:</p>
  @ 
  @ <table border="0" cellpadding="0" cellspacing="0">
  @ <tr><th>&emsp;&emsp;&emsp;<th>Pattern
  @     <th>&emsp;&emsp;&emsp;<th align="left">Match
  @ <tr><td><td><i>X</i><b>*</b>
  @     <td><td>Zero or more occurrences of <i>X</i>
  @ <tr><td><td><i>X</i><b>+</b>
  @     <td><td>One or more occurrences of <i>X</i>
  @ <tr><td><td><i>X</i><b>?</b>
  @     <td><td>Zero or one occurrences of <i>X</i>
  @ <tr><td><td><i>X</i><b>{</b><i>P</i><b>,</b><i>Q</i><b>}</b>
  @     <td><td>Between P and Q occurrences of <i>X</i>
  @ <tr><td><td><b>(</b><i>X</i><b>)</b>
  @     <td><td><i>X</i>
  @ <tr><td><td><i>X</i><b>|</b><i>Y</i>
  @     <td><td><i>X</i> or <i>Y</i>
  @ <tr><td><td><b>^</b><i>X</i>
  @     <td><td><i>X</i> at the beginning of the string
  @ <tr><td><td><i>X</i><b>$</b>
  @     <td><td><i>X</i> at the end of the string
  @ <tr><td><td><b>.</b>
  @     <td><td>Any single character
  @ <tr><td><td><b>\</b><i>C</i>
  @     <td><td>Character <i>C</i> if <i>C</i> is one of: <b>\{}()[]|*+?</b>
  @ <tr><td><td><b>\</b><i>C</i>
  @     <td><td>C-language escapes if <i>C</i> is one of: <b>afnrtv</b>
  @ <tr><td><td><b>\u</b><i>HHHH</i>
  @     <td><td>Unicode character U+HHHH where <i>HHHH</i> is four hex digits
  @ <tr><td><td><b>\</b><i>HH</i>
  @     <td><td>Unicode character U+00HH where <i>HH</i> is two hex digits
  @ <tr><td><td><b>[</b><i>abc</i><b>]</b>
  @     <td><td>Any single character from <i>abc</i>
  @ <tr><td><td><b>[^</b><i>abc</i><b>]</b>
  @     <td><td>Any single character not in <i>abc</i>
  @ <tr><td><td><b>[</b><i>a-z</i><b>]</b>
  @     <td><td>Any single character between <i>a</i> and <i>z</i>, inclusive
  @ <tr><td><td><b>[^</b><i>a-z</i><b>]</b>
  @     <td><td>Any single character not between <i>a</i> and <i>z</i>
  @ <tr><td><td><b>\b</b>
  @     <td><td>Word boundary
  @ <tr><td><td><b>\w</b>
  @     <td><td>A word character: a-zA-Z0-9 or _
  @ <tr><td><td><b>\W</b>
  @     <td><td>A non-word character
  @ <tr><td><td><b>\d</b>
  @     <td><td>A digit.  0-9
  @ <tr><td><td><b>\D</b>
  @     <td><td>A non-digit character
  @ <tr><td><td><b>\s</b>
  @     <td><td>A whitespace character
  @ <tr><td><td><b>\S</b>
  @     <td><td>A non-whitespace character
  @ </table>
  @ 
  @ <p>In the "Pattern" column of the table above:</p>
  @ <ul>
  @ <li> "<i>X</i>" and "<i>Y</i>" mean any subpattern
  @ <li> "<i>P</i>" and "<i>Q</i>" mean integers
  @ <li> "<i>C</i>" means a single character
  @ <li> "<i>H</i>" means a hexadecimal digit
  @ <li> "<i>abc</i>" means any sequences of one or more characters
  @ <li> "<i>a-z</i>" means any single character, a single "<b>-</b>"
  @      character, and then one additional character.
  @ <li> All other symbols in the patterns are literal text
  @ </ul>
  @ 
  @ <p>The "<i>X</i><b>|</b><i>Y</i>" pattern has lower precedence
  @ than the others.  Use "<b>(</b>...<b>)</b>" for grouping, as
  @ necessary.
  style_finish_page();
}
Changes to src/repolist.c.
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
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







+


















+







struct RepoInfo {
  char *zRepoName;      /* Name of the repository file */
  int isValid;          /* True if zRepoName is a valid Fossil repository */
  int isRepolistSkin;   /* 1 or 2 if this repository wants to be the skin
                        ** for the repository list.  2 means do use this
                        ** repository but do not display it in the list. */
  char *zProjName;      /* Project Name.  Memory from fossil_malloc() */
  char *zProjDesc;      /* Project Description.  Memory from fossil_malloc() */
  char *zLoginGroup;    /* Name of login group, or NULL.  Malloced() */
  double rMTime;        /* Last update.  Julian day number */
};
#endif

/*
** Discover information about the repository given by
** pRepo->zRepoName.  The discovered information is stored in other
** fields of the RepoInfo object.
*/
static void remote_repo_info(RepoInfo *pRepo){
  sqlite3 *db;
  sqlite3_stmt *pStmt;
  int rc;

  pRepo->isRepolistSkin = 0;
  pRepo->isValid = 0;
  pRepo->zProjName = 0;
  pRepo->zProjDesc = 0;
  pRepo->zLoginGroup = 0;
  pRepo->rMTime = 0.0;

  g.dbIgnoreErrors++;
  rc = sqlite3_open_v2(pRepo->zRepoName, &db, SQLITE_OPEN_READWRITE, 0);
  if( rc ) goto finish_repo_list;
  rc = sqlite3_prepare_v2(db, "SELECT value FROM config"
69
70
71
72
73
74
75









76
77
78
79
80
81
82
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93







+
+
+
+
+
+
+
+
+







  rc = sqlite3_prepare_v2(db, "SELECT value FROM config"
                              " WHERE name='project-name'",
                          -1, &pStmt, 0);
  if( rc ) goto finish_repo_list;
  if( sqlite3_step(pStmt)==SQLITE_ROW ){
    pRepo->zProjName = fossil_strdup((char*)sqlite3_column_text(pStmt,0));
  }
  sqlite3_finalize(pStmt);
  if( rc ) goto finish_repo_list;
  rc = sqlite3_prepare_v2(db, "SELECT value FROM config"
                              " WHERE name='project-description'",
                          -1, &pStmt, 0);
  if( rc ) goto finish_repo_list;
  if( sqlite3_step(pStmt)==SQLITE_ROW ){
    pRepo->zProjDesc = fossil_strdup((char*)sqlite3_column_text(pStmt,0));
  }
  sqlite3_finalize(pStmt);
  rc = sqlite3_prepare_v2(db, "SELECT value FROM config"
                              " WHERE name='login-group-name'",
                          -1, &pStmt, 0);
  if( rc==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){
    pRepo->zLoginGroup = fossil_strdup((char*)sqlite3_column_text(pStmt,0));
  }
113
114
115
116
117
118
119



120
121





122
123
124
125
126
127
128
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147







+
+
+


+
+
+
+
+







  Blob base;           /* document root for all repositories */
  int n = 0;           /* Number of repositories found */
  int allRepo;         /* True if running "fossil ui all".
                       ** False if a directory scan of base for repos */
  Blob html;           /* Html for the body of the repository list */
  char *zSkinRepo = 0; /* Name of the repository database used for skins */
  char *zSkinUrl = 0;  /* URL for the skin database */
  const char *zShow;   /* Value of FOSSIL_REPOLIST_SHOW environment variable */
  int bShowDesc = 0;   /* True to show the description column */
  int bShowLg = 0;     /* True to show the login-group column */

  assert( g.db==0 );
  zShow = P("FOSSIL_REPOLIST_SHOW");
  if( zShow ){
    bShowDesc = strstr(zShow,"description")!=0;
    bShowLg = strstr(zShow,"login-group")!=0;
  }
  blob_init(&html, 0, 0);
  if( fossil_strcmp(g.zRepositoryName,"/")==0 && !g.fJail ){
    /* For the special case of the "repository directory" being "/",
    ** show all of the repositories named in the ~/.fossil database.
    **
    ** On unix systems, then entries are of the form "repo:/home/..."
    ** and on Windows systems they are like on unix, starting with a "/"
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
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







+

+
+



+
-
+
+

















+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+

-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







       "   WHERE name GLOB 'repo:*'"
    );
    allRepo = 1;
  }else{
    /* The default case:  All repositories under the g.zRepositoryName
    ** directory.
    */
    Glob *pExclude;
    blob_init(&base, g.zRepositoryName, -1);
    db_close(0);
    assert( g.db==0 );
    sqlite3_open(":memory:", &g.db);
    db_multi_exec("CREATE TABLE sfile(pathname TEXT);");
    db_multi_exec("CREATE TABLE vfile(pathname);");
    pExclude = glob_create("*/proc,proc");
    vfile_scan(&base, blob_size(&base), 0, 0, 0, ExtFILE);
    vfile_scan(&base, blob_size(&base), 0, pExclude, 0, ExtFILE);
    glob_free(pExclude);
    db_multi_exec("DELETE FROM sfile WHERE pathname NOT GLOB '*[^/].fossil'"
#if USE_SEE
                  " AND pathname NOT GLOB '*[^/].efossil'"
#endif
    );
    allRepo = 0;
  }
  n = db_int(0, "SELECT count(*) FROM sfile");
  if( n==0 ){
    sqlite3_close(g.db);
    g.db = 0;
    g.repositoryOpen = 0;
    g.localOpen = 0;
    return 0;
  }else{
    Stmt q;
    double rNow;
    char zType[16];   /* Column type letters for class "sortable" */
    int nType;
    zType[0] = 't';  /* Repo name */
    zType[1] = 'x';  /* Space between repo-name and project-name */
    zType[2] = 't';  /* Project name */
    nType = 3;
    if( bShowDesc ){
      zType[nType++] = 'x';  /* Space between name and description */
      zType[nType++] = 't';  /* Project description */
    }
    zType[nType++] = 'x';    /* space before age */
    zType[nType++] = 'k';    /* Project age */
    if( bShowLg ){
      zType[nType++] = 'x';  /* space before login-group */
      zType[nType++] = 't';  /* Login Group */
    }
    zType[nType] = 0;
    blob_append_sql(&html,
    blob_appendf(&html,
      "<table border='0' class='sortable' data-init-sort='1'"
      " data-column-types='txtxkxt'><thead>\n"
      "<tr><th>Filename<th width='20'>"
      "<th>Project Name<th width='20'>"
      "<th>Last Modified<th width='20'>"
      "<th>Login Group</tr>\n"
      "</thead><tbody>\n");
      " data-column-types='%s' cellspacing='0' cellpadding='0'><thead>\n"
      "<tr><th>Filename</th><th>&emsp;</th>\n"
      "<th%s><nobr>Project Name</nobr></th>\n",
      zType, (bShowDesc ? " width='25%'" : ""));
    if( bShowDesc ){
      blob_appendf(&html,
        "<th>&emsp;</th>\n"
        "<th width='25%%'><nobr>Project Description</nobr></th>\n"
      );
    }
    blob_appendf(&html,
      "<th>&emsp;</th>"
      "<th><nobr>Last Modified</nobr></th>\n"
    );
    if( bShowLg ){
      blob_appendf(&html,
        "<th>&emsp;</th>"
        "<th><nobr>Login Group</nobr></th></tr>\n"
      );
    }
    blob_appendf(&html,"</thead><tbody>\n");
    db_prepare(&q, "SELECT pathname"
                   " FROM sfile ORDER BY pathname COLLATE nocase;");
    rNow = db_double(0, "SELECT julianday('now')");
    while( db_step(&q)==SQLITE_ROW ){
      const char *zName = db_column_text(&q, 0);
      int nName = (int)strlen(zName);
      int nSuffix = 7; /* ".fossil" */
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
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







-
+









-
-
+
+







      if( nName<nSuffix ) continue;
      zUrl = sqlite3_mprintf("%.*s", nName-nSuffix, zName);
      if( zName[0]=='/'
#ifdef _WIN32
          || sqlite3_strglob("[a-zA-Z]:/*", zName)==0
#endif
      ){
        zFull = mprintf("%s", zName);
        zFull = fossil_strdup(zName);
      }else if ( allRepo ){
        zFull = mprintf("/%s", zName);
      }else{
        zFull = mprintf("%s/%s", g.zRepositoryName, zName);
      }
      x.zRepoName = zFull;
      remote_repo_info(&x);
      if( x.isRepolistSkin ){
        if( zSkinRepo==0 ){
          zSkinRepo = mprintf("%s", x.zRepoName);
          zSkinUrl = mprintf("%s", zUrl);
          zSkinRepo = fossil_strdup(x.zRepoName);
          zSkinUrl = fossil_strdup(zUrl);
        }
      }
      fossil_free(zFull);
      if( !x.isValid
#if USE_SEE
       && !bEncrypted
#endif
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
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







-
+




-
+


-
+

-
+




















-
+



-
+





-
+



+

+
-
+


-
+

+
+
+
+
+
+
+
+
+
-
-
+
+
+


+
+
-
-
+
+
+
+


-
+




-
+







      iAge = (sqlite3_int64)((rNow - x.rMTime)*86400);
      zAge = human_readable_age(rNow - x.rMTime);
      if( x.rMTime==0.0 ){
        /* This repository has no entry in the "event" table.
        ** Its age will still be maximum, so data-sortkey will work. */
        zAge = mprintf("unknown");
      }
      blob_append_sql(&html, "<tr><td valign='top'>");
      blob_appendf(&html, "<tr><td valign='top'><nobr>");
      if( !file_ends_with_repository_extension(zName,0) ){
        /* The "fossil server DIRECTORY" and "fossil ui DIRECTORY" commands
        ** do not work for repositories whose names do not end in ".fossil".
        ** So do not hyperlink those cases. */
        blob_append_sql(&html,"%h",zName);
        blob_appendf(&html,"%h",zName);
      } else if( sqlite3_strglob("*/.*", zName)==0 ){
        /* Do not show hyperlinks for hidden repos */
        blob_append_sql(&html, "%h (hidden)", zName);
        blob_appendf(&html, "%h (hidden)", zName);
      } else if( allRepo && sqlite3_strglob("[a-zA-Z]:/?*", zName)!=0 ){
        blob_append_sql(&html,
        blob_appendf(&html,
          "<a href='%R/%T/home' target='_blank'>/%h</a>\n",
          zUrl, zName);
      }else if( file_ends_with_repository_extension(zName,1) ){
        /* As described in
        ** https://fossil-scm.org/forum/info/f50f647c97c72fc1: if
        ** foo.fossil and foo/bar.fossil both exist and we create a
        ** link to foo/bar/... then the URI dispatcher will instead
        ** see that as a link to foo.fossil. In such cases, do not
        ** emit a link to foo/bar.fossil. */
        char * zDirPart = file_dirname(zName);
        if( db_exists("SELECT 1 FROM sfile "
                      "WHERE pathname=(%Q || '.fossil') COLLATE nocase"
#if USE_SEE
                      "  OR pathname=(%Q || '.efossil') COLLATE nocase"
#endif
                      , zDirPart
#if USE_SEE
                      , zDirPart
#endif
        ) ){
          blob_append_sql(&html,
          blob_appendf(&html,
            "<s>%h</s> (directory/repo name collision)\n",
            zName);
        }else{
          blob_append_sql(&html,
          blob_appendf(&html,
            "<a href='%R/%T/home' target='_blank'>%h</a>\n",
            zUrl, zName);
        }
        fossil_free(zDirPart);
      }else{
        blob_append_sql(&html,
        blob_appendf(&html,
          "<a href='%R/%T/home' target='_blank'>%h</a>\n",
          zUrl, zName);
      }
      blob_appendf(&html,"</nobr></td>\n");
      if( x.zProjName ){
        blob_appendf(&html, "<td>&emsp;</td><td valign='top'>%h</td>\n",
        blob_append_sql(&html, "<td></td><td>%h</td>\n", x.zProjName);
                      x.zProjName);
        fossil_free(x.zProjName);
      }else{
        blob_append_sql(&html, "<td></td><td></td>\n");
        blob_appendf(&html, "<td>&emsp;</td><td></td>\n");
      }
      if( !bShowDesc ){
        /* Do nothing */
      }else if( x.zProjDesc ){
        blob_appendf(&html, "<td>&emsp;</td><td valign='top'>%h</td>\n",
                        x.zProjDesc);
        fossil_free(x.zProjDesc);
      }else{
        blob_appendf(&html, "<td>&emsp;</td><td></td>\n");
      }
      blob_append_sql(&html,
        "<td></td><td data-sortkey='%08x'>%h</td>\n",
      blob_appendf(&html,
        "<td>&emsp;</td><td data-sortkey='%08x' align='center' valign='top'>"
        "<nobr>%h</nobr></td>\n",
        (int)iAge, zAge);
      fossil_free(zAge);
      if( !bShowLg ){
        blob_appendf(&html, "</tr>\n");
      if( x.zLoginGroup ){
        blob_append_sql(&html, "<td></td><td>%h</td></tr>\n", x.zLoginGroup);
      }else if( x.zLoginGroup ){
        blob_appendf(&html, "<td>&emsp;</td><td valign='top'>"
                               "<nobr>%h</nobr></td></tr>\n",
                        x.zLoginGroup);
        fossil_free(x.zLoginGroup);
      }else{
        blob_append_sql(&html, "<td></td><td></td></tr>\n");
        blob_appendf(&html, "<td>&emsp;</td><td></td></tr>\n");
      }
      sqlite3_free(zUrl);
    }
    db_finalize(&q);
    blob_append_sql(&html,"</tbody></table>\n");
    blob_appendf(&html,"</tbody></table>\n");
  }
  if( zSkinRepo ){
    char *zNewBase = mprintf("%s/%s", g.zBaseURL, zSkinUrl);
    g.zBaseURL = 0;
    set_base_url(zNewBase);
    db_open_repository(zSkinRepo);
    fossil_free(zSkinRepo);
314
315
316
317
318
319
320

321
322
323
324
325
326
327

328
329
330

331
332
333
334
335
336
337
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







+






-
+


-
+







    login_check_credentials();
    style_set_current_feature("repolist");
    style_header("Repository List");
    @ %s(blob_str(&html))
    style_table_sorter();
    style_finish_page();
  }else{
    const char *zTitle = PD("FOSSIL_REPOLIST_TITLE","Repository List");
    /* If no repositories were found that had the "repolist_skin"
    ** property set, then use a default skin */
    @ <html>
    @ <head>
    @ <base href="%s(g.zBaseURL)/">
    @ <meta name="viewport" content="width=device-width, initial-scale=1.0">
    @ <title>Repository List</title>
    @ <title>%h(zTitle)</title>
    @ </head>
    @ <body>
    @ <h1 align="center">Fossil Repositories</h1>
    @ <h1 align="center">%h(zTitle)</h1>
    @ %s(blob_str(&html))
    @ <script>%s(builtin_text("sorttable.js"))</script>
    @ </body>
    @ </html>
  }
  blob_reset(&html);
  cgi_reply();
Changes to src/report.c.
445
446
447
448
449
450
451
452

453
454
455
456
457
458
459
445
446
447
448
449
450
451

452
453
454
455
456
457
458
459







-
+







*/
void view_edit(void){
  int rn;
  const char *zTitle;           /* Title of the report */
  const char *z;
  const char *zOwner;           /* Owner of the report */
  const char *zClrKey;          /* Color key - used to add colors to lines */
  char *zSQL;                   /* The SQL text that gnerates the report */
  char *zSQL;                   /* The SQL text that generates the report */
  char *zErr = 0;               /* An error message */
  const char *zDesc;            /* Extra descriptive text about the report */
  const char *zMimetype;        /* Mimetype for zDesc */
  const char *zTag;             /* Symbolic name for this report */
  int dflt = P("dflt") ? 1 : 0;

  login_check_credentials();
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
581
582
583
584
585
586
587



588
589
590
591
592
593
594







-
-
-







      rn = 0;
      zTitle = mprintf("Copy Of %s", zTitle);
      zOwner = g.zLogin;
    }
  }
  if( zOwner==0 ) zOwner = g.zLogin;
  style_submenu_element("Cancel", "%R/reportlist");
  if( rn>0 ){
    style_submenu_element("Delete", "%R/rptedit/%d?del1=1", rn);
  }
  style_header("%s", rn>0 ? "Edit Report Format":"Create New Report Format");
  if( zErr ){
    @ <blockquote class="reportError">%h(zErr)</blockquote>
  }
  @ <form action="rptedit" method="post"><div>
  @ <input type="hidden" name="rn" value="%d(rn)">
  @ <p>Report Title:<br>
895
896
897
898
899
900
901
902


903
904
905
906
907
908
909
892
893
894
895
896
897
898

899
900
901
902
903
904
905
906
907







-
+
+







  }
  ++pState->nCount;

  /* Output the separator above each entry in a table which has multiple lines
  ** per database entry.
  */
  if( pState->iNewRow>=0 ){
    @ <tr><td colspan=%d(pState->nCol)><font size=1>&nbsp;</font></td></tr>
    @ <tr><td colspan="%d(pState->nCol)" style="padding:0px">
    @ <hr style="margin:0px"></td></tr>
  }

  /* 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)">
Added src/robot.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
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
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
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
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
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
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
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/*
** Copyright (c) 2025 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 that attempts to prevent robots and
** especially bot-nets from consume excess CPU and bandwidth when
** Fossil is run as a service.
*/
#include "config.h"
#include "robot.h"
#include <assert.h>
#include <time.h>

/*
** The name of the cookie used to demonstrate that the client has been
** tested and is believed to be operated by a human, not by a robot.
*/
#if INTERFACE
#define ROBOT_COOKIE  "fossil-client-ok"
#endif

/*
** Values computed only once and then cached.
*/
static struct RobotCache {
  unsigned int h1, h2;       /* Proof-of-work hash values */
  unsigned int resultCache;  /* 0: unknown.  1: human  2: might-be-robot */
} robot = { 0, 0, 0 };

/*
** Allowed values for robot.resultCache.
**
** The names are slightly misleading.  KNOWN_NOT_ROBOT might be set even
** if the client is a robot, but only if the robot is an approved robot.
** A better name might be "KNOWN_NOT_UNAUTHORIZED_ROBOT", but that is too
** long of a name.
*/
#define KNOWN_NOT_ROBOT  1   /* Approved to consume CPU and bandwidth */
#define MIGHT_BE_ROBOT   2   /* Might be an unapproved robot */

/*
** Compute two hashes, robot.h1 and robot.h2, that are used as
** part of determining whether or not the HTTP client is a robot.
** These hashes are based on current time, client IP address,
** and User-Agent.  robot.h1 is for the current time slot and
** robot.h2 is the previous.
**
** The hashes are integer values between 100,000,000 and 999,999,999
** inclusive.
*/
static void robot_pow_hash(void){
  const char *az[2], *z;
  sqlite3_int64 tm;
  unsigned int h1, h2, k;

  if( robot.h1 ) return;   /* Already computed */

  /* Construct a proof-of-work value based on the IP address of the
  ** sender and the sender's user-agent string.  The current time also
  ** affects the pow value, so actually compute two values, one for the
  ** current 900-second interval and one for the previous.  Either can
  ** match.  The pow-value is an integer between 100,000,000 and
  ** 999,999,999.
  */
  az[0] = P("REMOTE_ADDR");
  az[1] = P("HTTP_USER_AGENT");
  tm = time(0);
  h1 = (unsigned)(tm/900)&0xffffffff;
  h2 = h1 - 1;
  for(k=0; k<2; k++){
    z = az[k];
    if( z==0 ) continue;
    while( *z ){
      h1 = (h1 + *(unsigned char*)z)*0x9e3779b1;
      h2 = (h2 + *(unsigned char*)z)*0x9e3779b1;
      z++;
    }
  }
  robot.h1 = (h1 % 900000000) + 100000000;
  robot.h2 = (h2 % 900000000) + 100000000;
}

/*
** Return true if the HTTP client has not demonstrated that it is
** human interactive.  Return false is the HTTP client might be
** a non-interactive robot.
**
** For this routine, any of the following is considered proof that
** the HTTP client is not a robot:
**
**   1.   There is a valid login, including "anonymous".  User "nobody"
**        is not a valid login, but every other user is.
**
**   2.   There exists a ROBOT_COOKIE with the correct proof-of-work
**        value.
**
**   3.   There exists a proof=VALUE query parameter where VALUE is
**        a correct proof-of-work value.
**
**   4.   There exists a valid token=VALUE query parameter.
**
** After being run once, this routine caches its findings and
** returns very quickly on subsequent invocations.
*/
int client_might_be_a_robot(void){
  const char *z;

  /* Only do this computation once, then cache the results for future
  ** use */
  if( robot.resultCache ){
    return robot.resultCache==MIGHT_BE_ROBOT;
  }

  /* Condition 1:  Is there a valid login?
  */
  if( g.userUid==0 ){
    login_check_credentials();
  }
  if( g.zLogin!=0 ){
    robot.resultCache = KNOWN_NOT_ROBOT;
    return 0;
  }

  /* Condition 2:  If there is already a proof-of-work cookie
  ** with a correct value, then the user agent has been authenticated.
  */
  z = P(ROBOT_COOKIE);
  if( z ){
    unsigned h = atoi(z);
    robot_pow_hash();
    if( (h==robot.h1 || h==robot.h2) && !cgi_is_qp(ROBOT_COOKIE) ){
      robot.resultCache = KNOWN_NOT_ROBOT;
      return 0;
    }
  }

  /* Condition 3:  There is a "proof=VALUE" query parameter with a valid
  ** VALUE attached.  If this is the case, also set the robot cookie
  ** so that future requests will hit condition 2 above.
  */
  z = P("proof");
  if( z ){
    unsigned h = atoi(z);
    robot_pow_hash();
    if( h==robot.h1 || h==robot.h2 ){
      cgi_set_cookie(ROBOT_COOKIE,z,"/",900);
      robot.resultCache = KNOWN_NOT_ROBOT;
      return 0;
    }
    cgi_tag_query_parameter("proof");
  }

  /* Condition 4:  If there is a "token=VALUE" query parameter with a
  ** valid VALUE argument, then assume that the request is coming from
  ** either an interactive human session, or an authorized robot that we
  ** want to treat as human.  Allow it through and also set the robot cookie.
  */
  z = P("token");
  if( z!=0 ){
    if( db_exists("SELECT 1 FROM config"
                  " WHERE name='token-%q'"
                  "   AND json_valid(value,6)"
                  "   AND value->>'user' IS NOT NULL", z)
    ){
      char *zVal;
      robot_pow_hash();
      zVal = mprintf("%u", robot.h1);
      cgi_set_cookie(ROBOT_COOKIE,zVal,"/",900);
      fossil_free(zVal);
      robot.resultCache = KNOWN_NOT_ROBOT;
      return 0;                /* There is a valid token= query parameter */
    }
    cgi_tag_query_parameter("token");
  }

  /* We have no proof that the request is coming from an interactive
  ** human session, so assume the request comes from a robot.
  */
  robot.resultCache = MIGHT_BE_ROBOT;
  return 1;
}

/*
** Rewrite the current page with content that attempts
** to prove that the client is not a robot.
*/
static void ask_for_proof_that_client_is_not_robot(void){
  unsigned p1, p2, p3, p4, p5, k2, k3;
  int k;

  /* Ask the client to present proof-of-work */
  cgi_reset_content();
  cgi_set_content_type("text/html");
  style_header("Browser Verification");
  @ <h1 id="x1">Checking to see if you are a robot<span id="x2"></span></h1>
  @ <form method="GET" id="x6"><p>
  @ <span id="x3" style="visibility:hidden;">\
  @ Press <input type="submit" id="x5" value="Ok" focus> to continue</span>
  @ <span id="x7" style="visibility:hidden;">You appear to be a robot.</span>\
  @ </p>
  if( g.zExtra && g.zExtra[0] ) cgi_tag_query_parameter("name");
  cgi_query_parameters_to_hidden();
  @ <input id="x4" type="hidden" name="proof" value="0">
  @ </form>
  @ <script nonce='%s(style_nonce())'>
  @ function aaa(x){return document.getElementById(x);}\
  @ function bbb(h,a){\
  @ aaa("x4").value=h;\
  @ if((a%%75)==0){\
  @ aaa("x2").textContent=aaa("x2").textContent+".";\
  @ }var z;\
  @ if(a>0){\
  @ setTimeout(bbb,1,h+a,a-1);\
  @ }else if((z=window.getComputedStyle(document.body).zIndex)==='0'||z===0){\
  @ aaa("x3").style.visibility="visible";\
  @ aaa("x2").textContent="";\
  @ aaa("x1").textContent="All clear";\
  @ aaa("x6").onsubmit=function(){aaa("x3").style.visibility="hidden";};\
  @ aaa("x5").focus();\
  @ }else{\
  @ aaa("x7").style.visibility="visible";\
  @ aaa("x2").textContent="";\
  @ aaa("x3").style.display="none";\
  @ aaa("x1").textContent="Access Denied";\
  @ }\
  @ }\
  robot_pow_hash();
  k = 400 + robot.h2%299;
  k2 = (robot.h2/299)%99 + 973;
  k3 = (robot.h2/(299*99))%99 + 811;
  p1 = (k*k + k)/2;
  p2 = robot.h1-p1;
  p3 = p2%k2;
  p4 = (p2/k2)%k3;
  p5 = p2/(k2*k3);
  @ function ccc(a,b,c){return (a*%u(k3)+b)*%u(k2)+c;}\
  @ window.addEventListener('load',function(){\
  @ bbb(ccc(%u(p5),%u(p4),%u(p3)),%u(k));},false);
  /* Prevent successfully completed robot checks from reappearing and force
  ** incomplete checks to start over when navigating back and forward. More
  ** information: <https://stackoverflow.com/a/43043658>. */
  @ window.addEventListener('pageshow',function(e){if(e.persisted)\
    @ window.location.reload();});
  @ </script>
  style_finish_page();
}

/*
** SETTING: robot-restrict                width=40 block-text
** The VALUE of this setting is a list of GLOB patterns that match
** pages for which complex HTTP requests from unauthenticated clients
** should be disallowed.  "Unauthenticated" means the user is "nobody".
** The recommended value for this setting is:
**
**   timelineX,diff,annotate,fileage,file,finfo,reports,tree,hexdump,download
**
** Usually the tag should exactly match the page name.  The "diff" tag
** covers all diffing pages such as /vdiff, /fdiff, and /vpatch.  The
** "annotate" tag also covers /blame and /praise.  "zip" also covers
** /tarball and /sqlar.  If a tag has an "X" character appended then it
** only applies if query parameters are such that the page is particularly
** difficult to compute.  Useful "X" tags include "timelineX" and "zipX".
** The "ext" tag matches all extension, but a tag of the form "ext/PATH"
** only matches the extension at PATH.
**
** See the [[robot-zip-leaf]] and [[robot-zip-tag]] settings
** for additional controls associated with the "zipX" restriction.
**
** Change this setting "off" to disable all robot restrictions.
*/
/*
** SETTING: robot-exception              width=40 block-text
**
** The value of this setting should be a regular expression.
** If it matches the REQUEST_URI without the SCRIPT_NAME prefix
** matches this regular expression, then the request is an exception
** to anti-robot defenses and should be allowed through.  For
** example, to allow robots to download tarballs or ZIP archives
** for named versions and releases, you could use an expression like
** this:
**
**     ^/tarball/(version-[0-9.]+|release)/
**
** This setting can hold multiple regular expressions, one
** regular expression per line.  The input URL is exempted from
** anti-robot defenses if any of the multiple regular expressions
** matches.
*/
/*
** SETTING: robot-zip-leaf               boolean
**
** If this setting is true, the robots are allowed to download tarballs,
** ZIP-archives, and SQL-archives even though "zipX" is found in
** the [[robot-restrict]] setting as long as the specific check-in being
** downloaded is a leaf check-in.
*/
/*
** SETTING: robot-zip-tag                width=40 block-text
**
** If this setting is a list of GLOB patterns matching tags,
** then robots are allowed to download tarballs, ZIP-archives, and
** SQL-archives even though "zipX" appears in [[robot-restrict]], as long as
** the specific check-in being downloaded has a tags that matches
** the GLOB list of this setting.  Recommended value:  
** "release,robot-access".
*/

/*
** Return the default restriction GLOB
*/
const char *robot_restrict_default(void){
  /* NOTE: The default value is also mentioned in the online help screen of
  ** the "robot-restrict" setting, and in the www/antibot.wiki document. */
  return "timelineX,diff,annotate,fileage,file,finfo,reports,"
         "tree,hexdump,download";
}

/*
** Return true if zTag matches one of the tags in the robot-restrict
** setting.
**
** A zTag of "*" matches anything.
*/
static int robot_restrict_has_tag(const char *zTag){
  static const char *zGlob = 0;
  if( zGlob==0 ){
    zGlob = db_get("robot-restrict",robot_restrict_default());
    if( zGlob==0 ) zGlob = "";
  }
  if( zGlob[0]==0 || fossil_strcmp(zGlob, "off")==0 ){
    return 0;
  }
  if( zTag==0 || (zTag[0]=='*' && zTag[1]==0) ){
    return 1;
  }
  return glob_multi_match(zGlob,zTag);
}

/*
** Check the request URI to see if it matches one of the URI
** exceptions listed in the robot-exception setting.  Return true
** if it does.  Return false if it does not.
**
** For the purposes of this routine, the "request URI" means
** the REQUEST_URI value with the SCRIPT_NAME prefix removed and
** with QUERY_STRING appended with a "?" separator if QUERY_STRING
** is not empty.
**
** If the robot-exception setting does not exist or is an empty
** string, then return false.
*/
int robot_exception(void){
  const char *zRE = db_get("robot-exception",0);
  const char *zQS;    /* QUERY_STRING */
  const char *zURI;   /* REQUEST_URI */
  const char *zSN;    /* SCRIPT_NAME */
  const char *zNL;    /* Next newline character */
  char *zRequest;     /* REQUEST_URL w/o SCRIPT_NAME prefix + QUERY_STRING */
  int nRequest;       /* Length of zRequest in bytes */
  size_t nURI, nSN;   /* Length of zURI and zSN */
  int bMatch = 0;     /* True if there is a match */

  if( zRE==0 ) return 0;
  if( zRE[0]==0 ) return 0;
  zURI = PD("REQUEST_URI","");
  nURI = strlen(zURI);
  zSN = PD("SCRIPT_NAME","");
  nSN = strlen(zSN);
  if( nSN<=nURI ) zURI += nSN;
  zQS = P("QUERY_STRING");
  if( zQS && zQS[0] ){
    zRequest = mprintf("%s?%s", zURI, zQS);
  }else{
    zRequest = fossil_strdup(zURI);
  }
  nRequest = (int)strlen(zRequest);
  while( zRE[0] && bMatch==0 ){
    char *z;
    const char *zErr;
    size_t n;
    ReCompiled *pRe;
    zNL = strchr(zRE,'\n');
    if( zNL ){
      n = (size_t)(zNL - zRE)+1;
      while( zNL>zRE && fossil_isspace(zNL[0]) ) zNL--;
      if( zNL==zRE ){
        zRE += n;
        continue;
      }
    }else{
      n = strlen(zRE);
    }
    z = mprintf("%.*s", (int)(zNL - zRE)+1, zRE);
    zRE += n;
    zErr = fossil_re_compile(&pRe, z, 0);
    if( zErr ){
      fossil_warning("robot-exception error \"%s\" in expression \"%s\"\n",
                     zErr, z);
      fossil_free(z);
      continue;
    }
    fossil_free(z);
    bMatch = re_match(pRe, (const unsigned char*)zRequest, nRequest);
    re_free(pRe);
  }
  fossil_free(zRequest);
  return bMatch;
}

/*
** Return true if one or more of the conditions below are true.
** Return false if all of the following are false:
**
**   *  The zTag is on the robot-restrict list
**
**   *  The client that submitted the HTTP request might be
**      a robot
**
**   *  The Request URI does not match any of the exceptions
**      in the robot-exception setting.
**
** In other words, return true if a call to robot_restrict() would
** return true and false if a call to robot_restrict() would return
** false.
**
** The difference between this routine an robot_restrict() is that
** this routine does not generate a proof-of-work captcha.  This
** routine does not change the HTTP reply in any way.  It simply
** returns true or false.
*/
int robot_would_be_restricted(const char *zTag){
  if( robot.resultCache==KNOWN_NOT_ROBOT ) return 0;
  if( !robot_restrict_has_tag(zTag) ) return 0;
  if( !client_might_be_a_robot() ) return 0;
  if( robot_exception() ){
    robot.resultCache = KNOWN_NOT_ROBOT;
    return 0;
  }
  return 1;
}

/*
** Check to see if the page named in the argument is on the
** robot-restrict list.  If it is on the list and if the user
** is might be a robot, then bring up a captcha to test to make
** sure that client is not a robot.
**
** This routine returns true if a captcha was rendered and if subsequent
** page generation should be aborted.  It returns false if the page
** should not be restricted and should be rendered normally.
*/
int robot_restrict(const char *zTag){
  if( robot_would_be_restricted(zTag) ){
    /* Generate the proof-of-work captcha */
    ask_for_proof_that_client_is_not_robot();
    return 1;
  }else{
    return 0;
  }
}

/*
** Check to see if a robot is allowed to download a tarball, ZIP archive,
** or SQL Archive for a particular check-in identified by the "rid" 
** argument.  Return true to block the download.  Return false to
** continue.  Prior to returning true, a captcha is presented to the user.
** No output is generated when returning false.
**
** The rules:
**
** (1) If "zipX" is missing from the robot-restrict setting, then robots
**     are allowed to download any archive.  None of the remaining rules
**     below are consulted unless "zipX" is on the robot-restrict setting.
**
** (2) If the robot-zip-leaf setting is true, then robots are allowed
**     to download archives for any leaf check-in.  This allows URL like
**     /tarball/trunk/archive.tar.gz to work since branch labels like "trunk"
**     always resolve to a leaf.
**
** (3) If the robot-zip-tag setting is a comma-separated tags, then any
**     check-in that contains one of the tags on that list is allowed to
**     be downloaded.  This allows check-ins with tags like "release" or
**     "robot-access" to be downloaded by robots.
*/
int robot_restrict_zip(int rid){
  const char *zTag;
  if( !robot_restrict_has_tag("zipX") || !client_might_be_a_robot() ){
    return 0;        /* Rule (1) */
  }

  if( db_get_boolean("robot-zip-leaf",0) && is_a_leaf(rid) ){
    return 0;        /* Rule (2) */
  }

  zTag = db_get("robot-zip-tag",0);
  if( zTag && zTag[0] && fossil_strcmp(zTag,"off")!=0 ){
    int ok = 0;
    Stmt q;
    db_prepare(&q,
      "SELECT substr(tagname,5) FROM tagxref, tag"
      " WHERE tagxref.rid=%d"
      "   AND tag.tagid=tagxref.tagid"
      "   AND tagxref.tagtype=1"
      "   AND tag.tagname GLOB 'sym-*'",
      rid
    );
    while( !ok && db_step(&q)==SQLITE_ROW ){
      if( glob_multi_match(zTag, db_column_text(&q,0)) ) ok = 1;
    }
    db_finalize(&q);
    if( ok ) return 0; /* Rule (3) */
  }

  /* Generate the proof-of-work captcha */
  ask_for_proof_that_client_is_not_robot();
  return 1;
}

/*
** WEBPAGE: test-robotck
**
** Run the robot_restrict() function using the value of the "name="
** query parameter as an argument.  Used for testing the robot_restrict()
** logic.
**
** Whenever this page is successfully rendered (when it doesn't go to
** the captcha) it deletes the proof-of-work cookie.  So reloading the
** page will reset the cookie and restart the verification.
**
** If the zip=CHECKIN query parameter is provided, then also invoke
** robot_restrict_archive() on the RID of CHECKIN.
*/
void robot_restrict_test_page(void){
  const char *zName = P("name");
  const char *zZip = P("zip");
  const char *zP1 = P("proof");
  const char *zP2 = P(ROBOT_COOKIE);
  const char *z;
  int rid = 0;
  if( zName==0 || zName[0]==0 ) zName = g.zPath;
  login_check_credentials();
  if( g.zLogin==0 ){ login_needed(1); return; }
  g.zLogin = 0;
  if( robot_restrict(zName) ) return;
  if( zZip && zZip[0] ){
    rid = symbolic_name_to_rid(zZip, "ci");
    if( rid && robot_restrict_zip(rid) ) return;
  }
  style_set_current_feature("test");
  style_header("robot_restrict() test");
  @ <h1>Captcha passed</h1>
  @
  @ <p>
  if( zP1 && zP1[0] ){
     @ proof=%h(zP1)<br>
  }
  if( zP2 && zP2[0] ){
    @ %h(ROBOT_COOKIE)=%h(zP2)<br>
    cgi_set_cookie(ROBOT_COOKIE,"",0,-1);
  }
  if( zZip && zZip[0] ){
    @ zip=%h(zZip)<br>
    @ rid=%d(rid)<br>
  }
  if( g.perm.Admin ){
    z = db_get("robot-restrict",robot_restrict_default());
    if( z && z[0] ){
      @ robot-restrict=%h(z)</br>
    }
    @ robot.h1=%u(robot.h1)<br>
    @ robot.h2=%u(robot.h2)<br>
    switch( robot.resultCache ){
      case MIGHT_BE_ROBOT: {
        @ robot.resultCache=MIGHT_BE_ROBOT<br>
        break;
      }
      case KNOWN_NOT_ROBOT: {
        @ robot.resultCache=KNOWN_NOT_ROBOT<br>
        break;
      }
      default: {
        @ robot.resultCache=OTHER (%d(robot.resultCache))<br>
        break;
      }
    }
  }
  @ </p>
  @ <p><a href="%R/test-robotck/%h(zName)">Retry</a>
  style_finish_page();
}

/*
** WEBPAGE: tokens
**
** Allow users to create, delete, and view their access token.
**
** The access token is a string TOKEN which if included in a query
** parameter like "token=TOKEN" authenticates a request as coming
** from an authorized agent.  This can be used, for example, by
** script to access content without running into problems with
** robot defenses.
*/
void tokens_page(void){
  char *zMyToken;

  login_check_credentials();
  style_set_current_feature("tokens");
  style_header("Access Tokens");
  if( g.zLogin==0 || fossil_strcmp(g.zLogin,"anonymous")==0 ){
    @ User "%h(g.zLogin?g.zLogin:"anonymous")" is not allowed to
    @ own or use access tokens.
    style_finish_page();
    return;
  }
  if( g.perm.Admin && P("del")!=0 ){
    const char *zDel = P("del");
    db_unprotect(PROTECT_CONFIG);
    db_multi_exec(
      "DELETE FROM config WHERE name='token-%q'",
      zDel);
    db_protect_pop();
  }
  zMyToken = db_text(0,
    "SELECT substr(name,7) FROM config"
    " WHERE name GLOB 'token-*'"
    "   AND json_valid(value,6)"
    "   AND value->>'user' = %Q",
    g.zLogin
  );
  if( zMyToken==0 && P("new") ){
    sqlite3_uint64 r;
    sqlite3_randomness(sizeof(r),&r);
    zMyToken = mprintf("%016llx", r);
    db_unprotect(PROTECT_CONFIG);
    db_multi_exec(
      "INSERT INTO config(name,value,mtime)"
      "VALUES('token-%q','{user:%!j}',now())",
      zMyToken, g.zLogin
    );
    db_protect_pop();
  }else if( zMyToken!=0 && P("selfdel")
         && fossil_strcmp(zMyToken,P("selfdel"))==0 ){
    db_unprotect(PROTECT_CONFIG);
    db_multi_exec(
      "DELETE FROM config WHERE name='token-%q'",
      zMyToken);
    db_protect_pop();
    zMyToken = 0;
  }
  if( zMyToken==0 ){
    @ <p>You do not currently have an access token.
    @ <a href="%R/tokens?new=true">Create one</a>
  }else{
    @ <p>Your access token is "%h(zMyToken)". 
    @ <p>Use this token as the value of the token= query parameter
    @ to bypass robot defenses on unauthenticated queries to this
    @ server (%R).  Do not misuse your token.  Keep it confidential.
    @ If you misuse your token, or if somebody else steals your token
    @ and misuses, that can result in loss of access privileges to this
    @ server.
    @ <p><a href="%R/tokens?selfdel=%h(zMyToken)">Delete my token</a>
  }
  if( g.perm.Admin ){
    int nTok = 0;
    Stmt s;
    db_prepare(&s, 
      "SELECT substr(name,7), value->>'user', datetime(mtime,'unixepoch')"
      "  FROM config"
      " WHERE name GLOB 'token-*'"
      "   AND json_valid(value,6)"
    );
    while( db_step(&s)==SQLITE_ROW ){
      if( nTok==0 ){
        @ <hr>
        @ <p>All tokens</p>
        @ <table border="1" cellpadding="5" cellspacing="0">
        @ <tr><th>User <th>Token  <th>Date <th> &nbsp;</tr>
      }
      nTok++;
      @ <tr><td>%h(db_column_text(&s,1))
      @ <td>%h(db_column_text(&s,0))
      @ <td>%h(db_column_text(&s,2))
      @ <td><a href="%R/tokens?del=%h(db_column_text(&s,0))">delete</a>
      @ </tr>
    }
    db_finalize(&s);
    if( nTok==0 ){
      @ <hr>
      @ <p>There are access tokens defined for this repository.
    }else{
      @ </table>
    }
  }
  style_finish_page();
}
Changes to src/schema.c.
26
27
28
29
30
31
32
33

34
35
36
37
38
39
40
26
27
28
29
30
31
32

33
34
35
36
37
38
39
40







-
+







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
@ );
@ ) WITHOUT ROWID;
@
@ -- Identifier for this file type.
@ -- The integer is the same as 'FSLG'.
@ PRAGMA application_id=252006675;
;

#if INTERFACE
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
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







-
+












-
+


-
+







@ -- in the form of name-value pairs.
@ --
@ CREATE TABLE config(
@   name TEXT PRIMARY KEY NOT NULL,  -- Primary name of the entry
@   value CLOB,                      -- Content of the named parameter
@   mtime DATE,                      -- last modified.  seconds since 1970
@   CHECK( typeof(name)='text' AND length(name)>=1 )
@ );
@ ) WITHOUT ROWID;
@
@ -- Artifacts that should not be processed are identified in the
@ -- "shun" table.  Artifacts that are control-file forgeries or
@ -- spam or artifacts whose contents violate administrative policy
@ -- can be shunned in order to prevent them from contaminating
@ -- the repository.
@ --
@ -- Shunned artifacts do not exist in the blob table.  Hence they
@ -- have not artifact ID (rid) and we thus must store their full
@ -- UUID.
@ --
@ CREATE TABLE shun(
@   uuid UNIQUE,          -- UUID of artifact to be shunned. Canonical form
@   uuid TEXT PRIMARY KEY,-- UUID of artifact to be shunned. Canonical form
@   mtime DATE,           -- When added.  seconds since 1970
@   scom TEXT             -- Optional text explaining why the shun occurred
@ );
@ ) WITHOUT ROWID;
@
@ -- Artifacts that should not be pushed are stored in the "private"
@ -- table.  Private artifacts are omitted from the "unclustered" and
@ -- "unsent" tables.
@ --
@ -- A phantom artifact (that is, an artifact with BLOB.SIZE<0 - an artifact
@ -- for which we do not know the content) might also be marked as private.
191
192
193
194
195
196
197
198

199
200
201
202
203
204
205
191
192
193
194
195
196
197

198
199
200
201
202
203
204
205







-
+







@ -- 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
@ );
@ ) WITHOUT ROWID;
@
@ -- The application ID helps the unix "file" command to identify the
@ -- database as a fossil repository.
@ PRAGMA application_id=252006673;
;

/*
257
258
259
260
261
262
263
264

265
266
267
268
269
270
271
257
258
259
260
261
262
263

264
265
266
267
268
269
270
271







-
+







@ --    pid = Parent file ID.
@ --    fnid = File Name ID.
@ --    pfnid = Parent File Name ID.
@ --    isaux = pmid IS AUXiliary parent, not primary parent
@ --
@ -- pid==0    if the file is added by check-in mid.
@ -- pid==(-1) if the file exists in a merge parents but not in the primary
@  --          parent.  In other words, if the file file was added by merge.
@  --          parent.  In other words, if the file was added by merge.
@ -- fid==0    if the file is removed by check-in mid.
@ --
@ CREATE TABLE mlink(
@   mid INTEGER,                       -- Check-in that contains fid
@   fid INTEGER,                       -- New file content. 0 if deleted
@   pmid INTEGER,                      -- Check-in that contains pid
@   pid INTEGER,                       -- Prev file content. 0 if new. -1 merge
363
364
365
366
367
368
369
370

371
372
373
374
375
376
377
363
364
365
366
367
368
369

370
371
372
373
374
375
376
377







-
+







@ );
@
@ -- Each artifact 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-HASH" where
@ -- HASH is the indentifier of the ticket.  Tags used to assign symbolic
@ -- HASH is the identifier 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.
@ );
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
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







-
+










-
+
















-
+













-
+

















-
+
+
+








/*
** Allowed values for MIMEtype codes
*/
#if INTERFACE
# define MT_NONE       0   /* unspecified */
# define MT_WIKI       1   /* Wiki */
# define MT_MARKDOWN   2   /* Markdonw */
# define MT_MARKDOWN   2   /* Markdown */
# define MT_UNKNOWN    3   /* unknown  */
# define ValidMTC(X)  ((X)>=0 && (X)<=3)  /* True if MIMEtype code is valid */
#endif

/*
** Predefined tagid values
*/
#if INTERFACE
# define TAG_BGCOLOR    1     /* Set the background color for display */
# define TAG_COMMENT    2     /* The check-in comment */
# define TAG_USER       3     /* User who made a checking */
# define TAG_USER       3     /* User who made a check-in */
# define TAG_DATE       4     /* The date of a check-in */
# define TAG_HIDDEN     5     /* Do not display in timeline */
# define TAG_PRIVATE    6     /* Do not sync */
# define TAG_CLUSTER    7     /* A cluster */
# define TAG_BRANCH     8     /* Value is name of the current branch */
# define TAG_CLOSED     9     /* Do not display this check-in as a leaf */
# define TAG_PARENT     10    /* Change to parentage on a check-in */
# define TAG_NOTE       11    /* Extra text appended to a check-in comment */
#endif

/*
** The schema for the local FOSSIL database file found at the root
** of every check-out.  This database contains the complete state of
** the check-out.  See also the addendum in zLocalSchemaVmerge[].
*/
const char zLocalSchema[] =
@ -- The VVAR table holds miscellanous information about the local database
@ -- The VVAR table holds miscellanous information about the local checkout
@ -- in the form of name-value pairs.  This is similar to the VAR table
@ -- table in the repository except that this table holds information that
@ -- is specific to the local check-out.
@ --
@ -- Important Variables:
@ --
@ --     repository        Full pathname of the repository database
@ --     user-id           Userid to use
@ --
@ CREATE TABLE vvar(
@   name TEXT PRIMARY KEY NOT NULL,  -- Primary name of the entry
@   value CLOB,                      -- Content of the named parameter
@   CHECK( typeof(name)='text' AND length(name)>=1 )
@ );
@ ) WITHOUT ROWID;
@
@ -- Each entry in the vfile table represents a single file in the
@ -- current check-out.
@ --
@ -- The file.rid field is 0 for files or folders that have been
@ -- added but not yet committed.
@ --
@ -- Vfile.chnged meaning:
@ --    0       File is unmodified
@ --    1       Manually edited and/or modified as part of a merge command
@ --    2       Replaced by a merge command
@ --    3       Added by a merge command
@ --    4,5     Same as 2,3 except merge using --integrate
@ --
@ CREATE TABLE vfile(
@   id INTEGER PRIMARY KEY,           -- ID of the checked-out file
@   vid INTEGER REFERENCES blob,      -- The check-in this file is part of.
@   chnged INT DEFAULT 0,  -- 0:unchng 1:edit 2:m-chng 3:m-add 4:i-chng 5:i-add
@   chnged INT DEFAULT 0,
@   -- 0:unchng 1:edit 2:m-chng 3:m-add 4:i-chng
@   -- 5:i-add 6:+exec 7:+symlink 8:-exec 9:unlink
@   deleted BOOLEAN DEFAULT 0,        -- True if deleted
@   isexe BOOLEAN,                    -- True if file should be executable
@   islink BOOLEAN,                   -- True if file should be symlink
@   rid INTEGER,                      -- Originally from this repository record
@   mrid INTEGER,                     -- Based on this record due to a merge
@   mtime INTEGER,                    -- Mtime of file on disk. sec since 1970
@   pathname TEXT,                    -- Full pathname relative to root
Changes to src/search.c.
16
17
18
19
20
21
22
23

24
25
26
27
28
29
30
16
17
18
19
20
21
22

23
24
25
26
27
28
29
30







-
+







*******************************************************************************
**
** This file contains code to implement a search functions
** against timeline comments, check-in content, wiki pages, tickets,
** and/or forum posts.
**
** The search can be either a per-query "grep"-like search that scans
** the entire corpus.  Or it can use the FTS4 search engine of SQLite.
** the entire corpus.  Or it can use the FTS5 search engine of SQLite.
** The choice is an administrator configuration option.
**
** The first option is referred to as "full-scan search".  The second
** option is called "indexed search".
**
** The code in this file is ordered approximately as follows:
**
128
129
130
131
132
133
134
135
136
137
138




139
140
141
142
143
144
145
128
129
130
131
132
133
134




135
136
137
138
139
140
141
142
143
144
145







-
-
-
-
+
+
+
+







  if( fSrchFlg & SRCHFLG_STATIC ){
    p = &gSearch;
    search_end(p);
  }else{
    p = fossil_malloc(sizeof(*p));
    memset(p, 0, sizeof(*p));
  }
  p->zPattern = z = mprintf("%s", zPattern);
  p->zMarkBegin = mprintf("%s", zMarkBegin);
  p->zMarkEnd = mprintf("%s", zMarkEnd);
  p->zMarkGap = mprintf("%s", zMarkGap);
  p->zPattern = z = mprintf("%s",zPattern);
  p->zMarkBegin = mprintf("%s",zMarkBegin);
  p->zMarkEnd = mprintf("%s",zMarkEnd);
  p->zMarkGap = mprintf("%s",zMarkGap);
  p->fSrchFlg = fSrchFlg;
  blob_init(&p->snip, 0, 0);
  while( *z && p->nTerm<SEARCH_MAX_TERM ){
    while( *z && !ISALNUM(*z) ){ z++; }
    if( *z==0 ) break;
    p->a[p->nTerm].z = z;
    for(i=1; ISALNUM(z[i]); i++){}
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
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
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
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







-
+

-
-
-
+
+
+

-
-
-
-
+
+
+
+
+
+
+
+
+
+
+

-
+
-
-
-
-
+
+
+


-
-
-
+
+
+
+
+
+

-
-
+
+

-
+
+







+
+


+

-
-
+


+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+









+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+









-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-










+
+
+






-
+






+

-
-
+
+
-
-
+
-
-
+







}

/*
** Testing the search function.
**
** COMMAND: search*
**
** Usage: %fossil search [-a|-all] [-n|-limit #] [-W|-width #] pattern...
** Usage: %fossil search [OPTIONS] PATTERN...
**
** Search for timeline entries matching all words provided on the
** command line. Whole-word matches scope more highly than partial
** matches.
** Search the repository for PATTERN and show matches.  Depending on
** options and how the administrator has search configured for the
** repository, the search can cover:
**
** Note:  The command only search the EVENT table.  So it will only
** display check-in comments or other comments that appear on an
** unaugmented timeline.  It does not search document text or forum
** messages.
**    *   check-in comments (-c)
**    *   embedded documentation (--docs)
**    *   forum posts (--forum)
**    *   tickets (--tickets)
**    *   tech notes (--technotes)
**    *   wiki pages (--wiki)
**    *   built-in fossil help text (-h)
**    *   all of the above (-a)
**
** Use options below to select the scope of the search.  The
** default is check-in comments only (-c).
**
** Outputs, by default, some top-N fraction of the results. The -all
** Output is colorized if writing to a TTY and if the NO_COLOR environment
** option can be used to output all matches, regardless of their search
** score.  The -limit option can be used to limit the number of entries
** returned.  The -width option can be used to set the output width used
** when printing matches.
** variable is not set.  Use the "--highlight 0" option to disable colorization
** or use "--highlight 91" to force it on.  Change the argument to --highlight
** to change the color.
**
** Options:
**     -a|--all          Output all matches, not just best matches
**     --debug           Show additional debug content on --fts search
**     --fts             Use the full-text search mechanism (testing only)
**     -a|--all          Search everything
**     -c|--checkins     Search check-in comments
**     --docs            Search embedded documentation
**     --forum           Search forum posts
**     -h|--bi-help      Search built-in help
**     --highlight N     Used VT100 color N for matching text.  0 means "off".
**     -n|--limit N      Limit output to N matches
**     --scope SCOPE     Scope of search.  Valid for --fts only.  One or
**                       more of: all, c, d, e, f, t, w.  Defaults to all.
**     --technotes       Search tech notes
**     --tickets         Search tickets
**     -W|--width WIDTH  Set display width to WIDTH columns, 0 for
**                       unlimited. Defaults the terminal's width.
**                       unlimited. Defaults to the terminal's width.
**     --wiki            Search wiki
*/
void search_cmd(void){
  Blob pattern;
  int i;
  Blob sql = empty_blob;
  Stmt q;
  int iBest;
  int srchFlags = 0;
  int bFts = 1;          /* Use FTS search by default now */
  char fAll = NULL != find_option("all", "a", 0);
  const char *zLimit = find_option("limit","n",1);
  const char *zScope = 0;
  const char *zWidth = find_option("width","W",1);
  const char *zScope = find_option("scope",0,1);
  int bDebug = find_option("debug",0,0)!=0;
  int bDebug = find_option("debug",0,0)!=0;     /* Undocumented */
  int nLimit = zLimit ? atoi(zLimit) : -1000;
  int width;
  int nTty = 0;          /* VT100 highlight color for matching text */
  const char *zHighlight = 0;
  int bFlags = 0;        /* DB open flags */

  nTty =  terminal_is_vt100();

  /* Undocumented option to change highlight color */
  zHighlight = find_option("highlight",0,1);
  if( zHighlight ) nTty = atoi(zHighlight);

  /* Undocumented option (legacy) */
  zScope = find_option("scope",0,1);

  int bFts = find_option("fts",0,0)!=0;
  if( find_option("fts",0,0)!=0 ) bFts = 1;      /* Undocumented legacy */
  if( find_option("legacy",0,0)!=0 ) bFts = 0;   /* Undocumented */

  if( zWidth ){
    width = atoi(zWidth);
    if( (width!=0) && (width<=20) ){
      fossil_fatal("-W|--width value must be >20 or 0");
    }
  }else{
    width = -1;
  }
  if( zScope ){
    for(i=0; zScope[i]; i++){
      switch( zScope[i] ){
        case 'a':  srchFlags = SRCH_ALL;       break;
        case 'c':  srchFlags |= SRCH_CKIN;     break;
        case 'd':  srchFlags |= SRCH_DOC;      break;
        case 'e':  srchFlags |= SRCH_TECHNOTE; break;
        case 'f':  srchFlags |= SRCH_FORUM;    break;
        case 'h':  srchFlags |= SRCH_HELP;     break;
        case 't':  srchFlags |= SRCH_TKT;      break;
        case 'w':  srchFlags |= SRCH_WIKI;     break;

  db_find_and_open_repository(0, 0);
      }
    }
    bFts = 1;
  }
  if( find_option("all","a",0) ){      srchFlags |= SRCH_ALL;      bFts = 1; }
  if( find_option("bi-help","h",0) ){  srchFlags |= SRCH_HELP;     bFts = 1; }
  if( find_option("checkins","c",0) ){ srchFlags |= SRCH_CKIN;     bFts = 1; }
  if( find_option("docs",0,0) ){       srchFlags |= SRCH_DOC;      bFts = 1; }
  if( find_option("forum",0,0) ){      srchFlags |= SRCH_FORUM;    bFts = 1; }
  if( find_option("technotes",0,0) ){  srchFlags |= SRCH_TECHNOTE; bFts = 1; }
  if( find_option("tickets",0,0) ){    srchFlags |= SRCH_TKT;      bFts = 1; }
  if( find_option("wiki",0,0) ){       srchFlags |= SRCH_WIKI;     bFts = 1; }

  /* If no search objects are specified, default to "check-in comments" */
  if( srchFlags==0 ) srchFlags = SRCH_CKIN;

  if( srchFlags==SRCH_HELP ) bFlags = OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE;
  db_find_and_open_repository(bFlags, 0);
  verify_all_options();
  if( g.argc<3 ) return;
  login_set_capabilities("s", 0);
  if( search_restrict(srchFlags)==0 && (srchFlags & SRCH_HELP)==0 ){
    const char *zC1 = 0, *zPlural = "s";
    if( srchFlags & SRCH_TECHNOTE ){  zC1 = "technote"; }
    if( srchFlags & SRCH_TKT ){       zC1 = "ticket";   }
    if( srchFlags & SRCH_FORUM ){     zC1 = "forum";    zPlural = ""; }
    if( srchFlags & SRCH_DOC ){       zC1 = "document"; }
    if( srchFlags & SRCH_WIKI ){      zC1 = "wiki";     zPlural = ""; }
    if( srchFlags & SRCH_CKIN ){      zC1 = "check-in"; }
    fossil_print(
      "Search of %s%s is disabled on this repository.\n"
      "Enable using \"fossil fts-config enable %s\".\n",
      zC1, zPlural, zC1
    );
    return;
  }

  blob_init(&pattern, g.argv[2], -1);
  for(i=3; i<g.argc; i++){
    blob_appendf(&pattern, " %s", g.argv[i]);
  }
  if( bFts ){
    /* Search using FTS */
    Blob com;
    Blob snip;
    const char *zPattern = blob_str(&pattern);
    int srchFlags;
    unsigned int j;
    if( zScope==0 ){
      srchFlags = SRCH_ALL;
    }else{
      srchFlags = 0;
      for(i=0; zScope[i]; i++){
        switch( zScope[i] ){
          case 'a':  srchFlags = SRCH_ALL;  break;
          case 'c':  srchFlags |= SRCH_CKIN;     break;
          case 'd':  srchFlags |= SRCH_DOC;      break;
          case 'e':  srchFlags |= SRCH_TECHNOTE; break;
          case 'f':  srchFlags |= SRCH_FORUM;    break;
          case 't':  srchFlags |= SRCH_TKT;      break;
          case 'w':  srchFlags |= SRCH_WIKI;     break;
        }
      }
    }
    search_sql_setup(g.db);
    add_content_sql_commands(g.db);
    db_multi_exec(
      "CREATE TEMP TABLE x(label,url,score,id,date,snip);"
    );
    if( !search_index_exists() ){
      search_fullscan(zPattern, srchFlags);  /* Full-scan search */
    }else{
      search_update_index(srchFlags);        /* Update the index */
      search_indexed(zPattern, srchFlags);   /* Indexed search */
      if( srchFlags & SRCH_HELP ){
        search_fullscan(zPattern, SRCH_HELP);
      }
    }
    db_prepare(&q, "SELECT snip, label, score, id, date"
                   "  FROM x"
                   " ORDER BY score DESC, date DESC;");
    blob_init(&com, 0, 0);
    blob_init(&snip, 0, 0);
    if( width<0 ) width = 80;
    if( width<0 ) width = terminal_get_width(80);
    while( db_step(&q)==SQLITE_ROW ){
      const char *zSnippet = db_column_text(&q, 0);
      const char *zLabel = db_column_text(&q, 1);
      const char *zDate = db_column_text(&q, 4);
      const char *zScore = db_column_text(&q, 2);
      const char *zId = db_column_text(&q, 3);
      char *zOrig;
      blob_appendf(&snip, "%s", zSnippet);
      for(j=0; j<snip.nUsed; j++){
        if( snip.aData[j]=='\n' ){
      zOrig = blob_materialize(&snip);
      blob_init(&snip, 0, 0);
          if( j>0 && snip.aData[j-1]=='\r' ) snip.aData[j-1] = ' ';
          snip.aData[j] = ' ';
      html_to_plaintext(zOrig, &snip, (nTty?HTOT_VT100:0)|HTOT_FLOW|HTOT_TRIM);
        }
      }
      fossil_free(zOrig);
      blob_appendf(&com, "%s\n%s\n%s", zLabel, blob_str(&snip), zDate);
      if( bDebug ){
        blob_appendf(&com," score: %s id: %s", zScore, zId);
      }
      comment_print(blob_str(&com), 0, 5, width,
            COMMENT_PRINT_TRIM_CRLF |
            COMMENT_PRINT_WORD_BREAK |
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
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







+
-
+






+
+
+





-
-
-
-
+
+
+
+

-
+
+







/* What to search for */
#define SRCH_CKIN     0x0001    /* Search over check-in comments */
#define SRCH_DOC      0x0002    /* Search over embedded documents */
#define SRCH_TKT      0x0004    /* Search over tickets */
#define SRCH_WIKI     0x0008    /* Search over wiki */
#define SRCH_TECHNOTE 0x0010    /* Search over tech notes */
#define SRCH_FORUM    0x0020    /* Search over forum messages */
#define SRCH_HELP     0x0040    /* Search built-in help (full-scan only) */
#define SRCH_ALL      0x003f    /* Search over everything */
#define SRCH_ALL      0x007f    /* Search over everything */
#endif

/*
** Remove bits from srchFlags which are disallowed by either the
** current server configuration or by user permissions.  Return
** the revised search flags mask.
**
** If bFlex is true, that means allow through the SRCH_HELP option
** even if it is not explicitly enabled.
*/
unsigned int search_restrict(unsigned int srchFlags){
  static unsigned int knownGood = 0;
  static unsigned int knownBad = 0;
  static const struct { unsigned m; const char *zKey; } aSetng[] = {
     { SRCH_CKIN,     "search-ci"   },
     { SRCH_DOC,      "search-doc"  },
     { SRCH_TKT,      "search-tkt"  },
     { SRCH_WIKI,     "search-wiki" },
     { SRCH_CKIN,     "search-ci"       },
     { SRCH_DOC,      "search-doc"      },
     { SRCH_TKT,      "search-tkt"      },
     { SRCH_WIKI,     "search-wiki"     },
     { SRCH_TECHNOTE, "search-technote" },
     { SRCH_FORUM,    "search-forum" },
     { SRCH_FORUM,    "search-forum"    },
     { SRCH_HELP,     "search-help"     },
  };
  int i;
  if( g.perm.Read==0 )   srchFlags &= ~(SRCH_CKIN|SRCH_DOC|SRCH_TECHNOTE);
  if( g.perm.RdTkt==0 )  srchFlags &= ~(SRCH_TKT);
  if( g.perm.RdWiki==0 ) srchFlags &= ~(SRCH_WIKI);
  if( g.perm.RdForum==0) srchFlags &= ~(SRCH_FORUM);
  for(i=0; i<count(aSetng); i++){
790
791
792
793
794
795
796

797

798
799
800
801
802
803
804
850
851
852
853
854
855
856
857

858
859
860
861
862
863
864
865







+
-
+







  const char *zPattern,       /* The query pattern */
  unsigned int srchFlags      /* What to search over */
){
  search_init(zPattern, "<mark>", "</mark>", " ... ",
          SRCHFLG_STATIC|SRCHFLG_HTML);
  if( (srchFlags & SRCH_DOC)!=0 ){
    char *zDocGlob = db_get("doc-glob","");
    const char *zMainBranch = db_main_branch();
    char *zDocBr = db_get("doc-branch","trunk");
    char *zDocBr = db_get("doc-branch", zMainBranch);
    if( zDocGlob && zDocGlob[0] && zDocBr && zDocBr[0] ){
      Glob * pGlob = glob_create(zDocBr)
        /* We're misusing a Glob as a list of comma-/space-delimited
        ** tokens. We're not actually doing glob matches here. */;
      int i;
      db_multi_exec(
        "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;"
909
910
911
912
913
914
915





















916
917
918
919
920
921
922
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







      "         search_score(),"
      "         'f'||rid,"
      "         datetime(event.mtime),"
      "         search_snippet()"
      "    FROM event JOIN blob on event.objid=blob.rid"
      "   WHERE search_match('',body('f',rid,NULL));"
    );
  }
  if( (srchFlags & SRCH_HELP)!=0 ){
    const char *zPrefix;
    helptext_vtab_register(g.db);
    if( srchFlags==SRCH_HELP ){
      zPrefix = "The";
    }else{
      zPrefix = "Built-in help for the";
    }
    db_multi_exec(
      "INSERT INTO x(label,url,score,id,snip)"
      "  SELECT format('%q \"%%s\" %%s',name,type),"
      "         '/help/'||name,"
      "         search_score(),"
      "         'h'||rowid,"
      "         search_snippet()"
      "    FROM helptext"
      "   WHERE search_match(format('the \"%%s\" %%s',name,type),"
      "                      helptext.helptext);",
      zPrefix
    );
  }
}

/*
** Number of significant bits in a u32
*/
static int nbits(u32 x){
1036
1037
1038
1039
1040
1041
1042
1043

1044
1045
1046
1047
1048
1049
1050
1118
1119
1120
1121
1122
1123
1124

1125
1126
1127
1128
1129
1130
1131
1132







-
+







  static const char *zSnippetCall;
  if( srchFlags==0 ) return;
  sqlite3_create_function(g.db, "rank", 1, SQLITE_UTF8|SQLITE_INNOCUOUS, 0,
     search_rank_sqlfunc, 0, 0);
  zPat = search_simplify_pattern(zPattern);
  blob_init(&sql, 0, 0);
  if( search_index_type(0)==4 ){
    /* If this repo is still using the legacy FTS4 search index, then
    /* If this repo is still using the legacy FTS5 search index, then
    ** the snippet() function is slightly different */
    zSnippetCall = "snippet(ftsidx,'<mark>','</mark>',' ... ',-1,35)";
  }else{
    /* This is the common case - Using newer FTS5 search index */
    zSnippetCall = "snippet(ftsidx,-1,'<mark>','</mark>',' ... ',35)";
  }
  blob_appendf(&sql,
1066
1067
1068
1069
1070
1071
1072

1073
1074
1075
1076
1077
1078
1079
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162







+







    static const struct { unsigned m; char c; } aMask[] = {
       { SRCH_CKIN,     'c' },
       { SRCH_DOC,      'd' },
       { SRCH_TKT,      't' },
       { SRCH_WIKI,     'w' },
       { SRCH_TECHNOTE, 'e' },
       { SRCH_FORUM,    'f' },
       { SRCH_HELP,     'h' },
    };
    int i;
    for(i=0; i<count(aMask); i++){
      if( srchFlags & aMask[i].m ){
        blob_appendf(&sql, "%sftsdocs.type='%c'", zSep, aMask[i].c);
        zSep = " OR ";
      }
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
1238
1239
1240
1241
1242
1243
1244

1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266







-
+











+
+
+







  Stmt q;
  int nRow = 0;
  int nLimit = db_get_int("search-limit", 100);

  if( P("searchlimit")!=0 ){
    nLimit = atoi(P("searchlimit"));
  }
  srchFlags = search_restrict(srchFlags);
  srchFlags = search_restrict(srchFlags) | (srchFlags & SRCH_HELP);
  if( srchFlags==0 ) return 0;
  search_sql_setup(g.db);
  add_content_sql_commands(g.db);
  db_multi_exec(
    "CREATE TEMP TABLE x(label,url,score,id,date,snip);"
  );
  if( !search_index_exists() ){
    search_fullscan(zPattern, srchFlags);  /* Full-scan search */
  }else{
    search_update_index(srchFlags);        /* Update the index, if necessary */
    search_indexed(zPattern, srchFlags);   /* Indexed search */
    if( srchFlags & SRCH_HELP ){
      search_fullscan(zPattern, SRCH_HELP);
    }
  }
  db_prepare(&q, "SELECT url, snip, label, score, id, substr(date,1,10)"
                 "  FROM x"
                 " ORDER BY score DESC, date DESC;");
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUrl = db_column_text(&q, 0);
    const char *zSnippet = db_column_text(&q, 1);
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
1256
1257
1258
1259
1260
1261

1262
1263






1264
1265
1266
1267
1268
1269
1270

1271
1272
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
1320
1321
1322
1323
1324
1325
1326








1327
1328
1329
1330
1331
1332
1333
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
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
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
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
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432







-
+







+
+
+
+
+
-
-
+
+
+






+

-
+
















+
-
-
+
+
+
+
+
+







+

-
-



-
+
+
+


-
+





-





-
+










-
+
















-
-
-
-
-
-
-
+
+
+
+
+
+
+
+







**     0x01    If the y= query parameter is present, use it as an addition
**             restriction what to search.
**
**     0x02    Show nothing if search is disabled.
**
** Return true if there are search results.
*/
int search_screen(unsigned srchFlags, int mFlags){
int search_screen(unsigned srchAllowed, int mFlags){
  const char *zType = 0;
  const char *zClass = 0;
  const char *zDisable1;
  const char *zDisable2;
  const char *zPattern;
  int fDebug = PB("debug");
  int haveResult = 0;
  int srchThisTime;
  const char *zY = PD("y","all");
  if( zY[0]=='h' && zY[1]==0 ){
    srchAllowed = search_restrict(srchAllowed) | (srchAllowed & SRCH_HELP);
  }else{
  srchFlags = search_restrict(srchFlags);
  switch( srchFlags ){
    srchAllowed = search_restrict(srchAllowed);
  }
  switch( srchAllowed ){
    case SRCH_CKIN:     zType = " Check-ins";  zClass = "Ckin"; break;
    case SRCH_DOC:      zType = " Docs";       zClass = "Doc";  break;
    case SRCH_TKT:      zType = " Tickets";    zClass = "Tkt";  break;
    case SRCH_WIKI:     zType = " Wiki";       zClass = "Wiki"; break;
    case SRCH_TECHNOTE: zType = " Tech Notes"; zClass = "Note"; break;
    case SRCH_FORUM:    zType = " Forum";      zClass = "Frm";  break;
    case SRCH_HELP:     zType = " Help";       zClass = "Hlp";  break;
  }
  if( srchFlags==0 ){
  if( srchAllowed==0 ){
    if( mFlags & 0x02 ) return 0;
    zDisable1 = " disabled";
    zDisable2 = " disabled";
    zPattern = "";
  }else{
    zDisable1 = ""; /* Was: " autofocus" */
    zDisable2 = "";
    zPattern = PD("s","");
  }
  @ <form method='GET' action='%R/%T(g.zPath)'>
  if( zClass ){
    @ <div class='searchForm searchForm%s(zClass)'>
  }else{
    @ <div class='searchForm'>
  }
  @ <input type="text" name="s" size="40" value="%h(zPattern)"%s(zDisable1)>
  srchThisTime = srchAllowed;
  if( (mFlags & 0x01)!=0 && (srchFlags & (srchFlags-1))!=0 ){
    static const struct { const char *z; const char *zNm; unsigned m; } aY[] = {
  if( (mFlags & 0x01)!=0 && (srchAllowed & (srchAllowed-1))!=0 ){
    static const struct {
      const char *z;
      const char *zNm;
      unsigned m;
    } aY[] = {
       { "all",  "All",        SRCH_ALL      },
       { "c",    "Check-ins",  SRCH_CKIN     },
       { "d",    "Docs",       SRCH_DOC      },
       { "t",    "Tickets",    SRCH_TKT      },
       { "w",    "Wiki",       SRCH_WIKI     },
       { "e",    "Tech Notes", SRCH_TECHNOTE },
       { "f",    "Forum",      SRCH_FORUM    },
       { "h",    "Help",       SRCH_HELP     },
    };
    const char *zY = PD("y","all");
    unsigned newFlags = srchFlags;
    int i;
    @ <select size='1' name='y'>
    for(i=0; i<count(aY); i++){
      if( (aY[i].m & srchFlags)==0 ) continue;
      if( (aY[i].m & srchAllowed)==0 ) continue;
      if( aY[i].m==SRCH_HELP && fossil_strcmp(zY,"h")!=0
       && search_restrict(SRCH_HELP)==0 ) continue;
      cgi_printf("<option value='%s'", aY[i].z);
      if( fossil_strcmp(zY,aY[i].z)==0 ){
        newFlags &= aY[i].m;
        srchThisTime &= aY[i].m;
        cgi_printf(" selected");
      }
      cgi_printf(">%s</option>\n", aY[i].zNm);
    }
    @ </select>
    srchFlags = newFlags;
  }
  if( fDebug ){
    @ <input type="hidden" name="debug" value="1">
  }
  @ <input type="submit" value="Search%s(zType)"%s(zDisable2)>
  if( srchFlags==0 ){
  if( srchAllowed==0 && srchThisTime==0 ){
    @ <p class="generalError">Search is disabled</p>
  }
  @ </div></form>
  while( fossil_isspace(zPattern[0]) ) zPattern++;
  if( zPattern[0] ){
    if( zClass ){
      @ <div class='searchResult searchResult%s(zClass)'>
    }else{
      @ <div class='searchResult'>
    }
    if( search_run_and_output(zPattern, srchFlags, fDebug)==0 ){
    if( search_run_and_output(zPattern, srchThisTime, fDebug)==0 ){
      @ <p class='searchEmpty'>No matches for: <span>%h(zPattern)</span></p>
    }
    @ </div>
    haveResult = 1;
  }
  return haveResult;
}

/*
** WEBPAGE: search
**
** Search for check-in comments, documents, tickets, or wiki that
** match a user-supplied pattern.
**
**    s=PATTERN       Specify the full-text pattern to search for
**    y=TYPE          What to search.
**                      c -> check-ins
**                      d -> documentation
**                      t -> tickets
**                      w -> wiki
**                      e -> tech notes
**                      f -> forum
**                    all -> everything
**                      c -> check-ins,
**                      d -> documentation,
**                      t -> tickets,
**                      w -> wiki,
**                      e -> tech notes,
**                      f -> forum,
**                      h -> built-in help,
**                    all -> everything.
*/
void search_page(void){
  const int isSearch = P("s")!=0;
  login_check_credentials();
  style_header("Search%s", isSearch ? " Results" : "");
  cgi_check_for_malice();
  search_screen(SRCH_ALL, 1);
1372
1373
1374
1375
1376
1377
1378
1379

1380
1381
1382
1383
1384
1385
1386
1387
1388

1389
1390
1391
1392
1393
1394
1395
1471
1472
1473
1474
1475
1476
1477

1478
1479
1480
1481
1482
1483
1484
1485
1486

1487
1488
1489
1490
1491
1492
1493
1494







-
+








-
+







        wiki_convert(&tail, &html, 0);
        blob_reset(&tail);
      }else{
        blob_append(pOut, "\n", 1);
        wiki_convert(pIn, &html, 0);
      }
    }
    html_to_plaintext(blob_str(&html), pOut);
    html_to_plaintext(blob_str(&html), pOut, 0);
  }else if( fossil_strcmp(zMimetype,"text/x-markdown")==0 ){
    markdown_to_html(pIn, blob_size(&title) ? NULL : &title, &html);
  }else if( fossil_strcmp(zMimetype,"text/html")==0 ){
    if( blob_size(&title)==0 ) doc_is_embedded_html(pIn, &title);
    pHtml = pIn;
  }
  blob_appendf(pOut, "%s\n", blob_str(&title));
  if( blob_size(pHtml) ){
    html_to_plaintext(blob_str(pHtml), pOut);
    html_to_plaintext(blob_str(pHtml), pOut, 0);
  }else{
    blob_append(pOut, blob_buffer(pIn), blob_size(pIn));
  }
  blob_reset(&html);
  blob_reset(&title);
}

1886
1887
1888
1889
1890
1891
1892

1893

1894
1895
1896
1897
1898
1899
1900
1985
1986
1987
1988
1989
1990
1991
1992

1993
1994
1995
1996
1997
1998
1999
2000







+
-
+







/*
** If the doc-glob and doc-br settings are valid for document search
** and if the latest check-in on doc-br is in the unindexed set of
** check-ins, then update all 'd' entries in FTSDOCS that have
** changed.
*/
static void search_update_doc_index(void){
  const char *zMainBranch = db_main_branch();
  const char *zDocBranches = db_get("doc-branch","trunk");
  const char *zDocBranches = db_get("doc-branch", zMainBranch);
  int i;
  Glob * pGlob = glob_create(zDocBranches)
    /* We're misusing a Glob as a list of comma-/space-delimited
    ** tokens. We're not actually doing glob matches here. */;
  if( !pGlob ) return;
  db_multi_exec(
    "CREATE TEMP TABLE current_docs(rid INTEGER PRIMARY KEY, name);"
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
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
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







-
+






-
-
+
+

-
+

-
-
-
+
+
+

-
+

-
-
-
-
+
+
+
+




















-
-
-
-
-
-
+
+
+
+
+
+
+







  if( db_table_exists("repository","chat") ){
    chat_rebuild_index(1);
  }
  fossil_print(" done\n");
}

/*
** COMMAND: fts-config*
** COMMAND: fts-config*                    abbrv-subcom
**
** Usage: fossil fts-config ?SUBCOMMAND? ?ARGUMENT?
**
** The "fossil fts-config" command configures the full-text search capabilities
** of the repository.  Subcommands:
**
**     reindex            Rebuild the search index.  This is a no-op if
**                        index search is disabled
**    reindex            Rebuild the search index.  This is a no-op if
**                       index search is disabled
**
**     index (on|off)     Turn the search index on or off
**    index (on|off)     Turn the search index on or off
**
**     enable cdtwef      Enable various kinds of search. c=Check-ins,
**                        d=Documents, t=Tickets, w=Wiki, e=Tech Notes,
**                        f=Forum.
**    enable TYPE ..     Enable search for TYPE.  TYPE is one of:
**                       check-in, document, ticket, wiki, technote, 
**                       forum, help, or all
**
**     disable cdtwef     Disable various kinds of search
**    disable TYPE ...   Disable search for TYPE
**
**     tokenizer VALUE    Select a tokenizer for indexed search. VALUE
**                        may be one of (porter, on, off, trigram, unicode61),
**                        and "on" is equivalent to "porter". Unindexed
**                        search never uses tokenization or stemming.
**    tokenizer VALUE    Select a tokenizer for indexed search. VALUE
**                       may be one of (porter, on, off, trigram, unicode61),
**                       and "on" is equivalent to "porter". Unindexed
**                       search never uses tokenization or stemming.
**
** The current search settings are displayed after any changes are applied.
** Run this command with no arguments to simply see the settings.
*/
void fts_config_cmd(void){
  static const struct {
    int iCmd;
    const char *z;
  } aCmd[] = {
     { 1,  "reindex"  },
     { 2,  "index"    },
     { 3,  "disable"  },
     { 4,  "enable"   },
     { 5,  "tokenizer"},
  };
  static const struct {
    const char *zSetting;
    const char *zName;
    const char *zSw;
  } aSetng[] = {
     { "search-ci",       "check-in search:",  "c" },
     { "search-doc",      "document search:",  "d" },
     { "search-tkt",      "ticket search:",    "t" },
     { "search-wiki",     "wiki search:",      "w" },
     { "search-technote", "tech note search:", "e" },
     { "search-forum",    "forum search:",     "f" },
     { "search-ci",       "check-in search:",       "c" },
     { "search-doc",      "document search:",       "d" },
     { "search-tkt",      "ticket search:",         "t" },
     { "search-wiki",     "wiki search:",           "w" },
     { "search-technote", "technote search:",       "e" },
     { "search-forum",    "forum search:",          "f" },
     { "search-help",     "built-in help search:",  "h" },
  };
  char *zSubCmd = 0;
  int i, j, n;
  int iCmd = 0;
  int iAction = 0;
  db_find_and_open_repository(0, 0);
  if( g.argc>2 ){
2190
2191
2192
2193
2194
2195
2196

2197


2198
2199
2200
2201
2202
































2203
2204
2205
2206
2207
2208
2209
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
2337
2338
2339
2340







+

+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    if( g.argc<3 ) usage("index (on|off)");
    iAction = 1 + is_truth(g.argv[3]);
  }
  db_begin_transaction();

  /* Adjust search settings */
  if( iCmd==3 || iCmd==4 ){
    int k;
    const char *zCtrl;
    for(k=2; k<g.argc; k++){
      if( k==2 ){
    if( g.argc<4 ) usage(mprintf("%s STRING",zSubCmd));
    zCtrl = g.argv[3];
    for(j=0; j<count(aSetng); j++){
      if( strchr(zCtrl, aSetng[j].zSw[0])!=0 ){
        db_set_int(aSetng[j].zSetting/*works-like:"x"*/, iCmd-3, 0);
        if( g.argc<4 ){
          zCtrl = "all";
        }else{
          zCtrl = g.argv[3];
          k++;
        }
      }else{
        zCtrl = g.argv[k];
      }
      if( fossil_strcmp(zCtrl,"all")==0 ){
        zCtrl = "cdtwefh";
      }
      if( strlen(zCtrl)>=4 ){
        /* If the argument to "enable" or "disable" is a string of at least
        ** 4 characters which matches part of any aSetng.zName, then use that
        ** one aSetng value only. */
        char *zGlob = mprintf("*%s*", zCtrl);
        for(j=0; j<count(aSetng); j++){
          if( sqlite3_strglob(zGlob, aSetng[j].zName)==0 ){
            db_set_int(aSetng[j].zSetting/*works-like:"x"*/, iCmd-3, 0);
            zCtrl = 0;
            break;
          }
        }
        fossil_free(zGlob);
      }
      if( zCtrl ){
        for(j=0; j<count(aSetng); j++){
          if( strchr(zCtrl, aSetng[j].zSw[0])!=0 ){
            db_set_int(aSetng[j].zSetting/*works-like:"x"*/, iCmd-3, 0);
          }
        }
      }
    }
  }else if( iCmd==5 ){
    int iOldTokenizer, iNewTokenizer;
    if( g.argc<4 ) usage("tokenizer porter|on|off|trigram|unicode61");
    iOldTokenizer = search_tokenizer_type(0);
    db_set("search-tokenizer",
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
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







-
+


-
+








-
-
+
+


-
+


-
+







  }
  if( iAction>=2 ){
    search_rebuild_index();
  }

  /* Always show the status before ending */
  for(i=0; i<count(aSetng); i++){
    fossil_print("%-17s %s\n", aSetng[i].zName,
    fossil_print("%-21s %s\n", aSetng[i].zName,
       db_get_boolean(aSetng[i].zSetting,0) ? "on" : "off");
  }
  fossil_print("%-17s %s\n", "tokenizer:",
  fossil_print("%-21s %s\n", "tokenizer:",
       search_tokenizer_for_string(0));
  if( search_index_exists() ){
    int pgsz = db_int64(0, "PRAGMA repository.page_size;");
    i64 nTotal = db_int64(0, "PRAGMA repository.page_count;")*pgsz;
    i64 nFts = db_int64(0, "SELECT count(*) FROM dbstat"
                               " WHERE schema='repository'"
                               " AND name LIKE 'fts%%'")*pgsz;
    char zSize[50];
    fossil_print("%-17s FTS%d\n", "full-text index:", search_index_type(1));
    fossil_print("%-17s %d\n", "documents:",
    fossil_print("%-21s FTS%d\n", "full-text index:", search_index_type(1));
    fossil_print("%-21s %d\n", "documents:",
       db_int(0, "SELECT count(*) FROM ftsdocs"));
    approxSizeName(sizeof(zSize), zSize, nFts);
    fossil_print("%-17s %s (%.1f%% of repository)\n", "space used",
    fossil_print("%-21s %s (%.1f%% of repository)\n", "space used",
       zSize, 100.0*((double)nFts/(double)nTotal));
  }else{
    fossil_print("%-17s disabled\n", "full-text index:");
    fossil_print("%-21s disabled\n", "full-text index:");
  }
  db_end_transaction(0);
}

/*
** WEBPAGE: test-ftsdocs
**
2732
2733
2734
2735
2736
2737
2738
2739

2740
2741
2742
2743
2744
2745
2746
2863
2864
2865
2866
2867
2868
2869

2870
2871
2872
2873
2874
2875
2876
2877







-
+








  if( rc==SQLITE_OK ){
    rc = fts5MatchinfoIter(pApi, pFts, p, fts5MatchinfoLocalCb);
  }
  if( rc!=SQLITE_OK ){
    sqlite3_result_error_code(pCtx, rc);
  }else{
    /* No errors has occured, so return a copy of the array of integers. */
    /* No error has occurred, so return a copy of the array of integers. */
    int nByte = p->nRet * sizeof(u32);
    sqlite3_result_blob(pCtx, (void*)p->aRet, nByte, SQLITE_TRANSIENT);
  }
}

int db_register_fts5(sqlite3 *db){
  int rc;                         /* Return code */
Changes to src/security_audit.c.
98
99
100
101
102
103
104

105
106
107
108
109
110
111
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112







+







  const char *zAnonCap;      /* Capabilities of user "anonymous" and "nobody" */
  const char *zDevCap;       /* Capabilities of user group "developer" */
  const char *zReadCap;      /* Capabilities of user group "reader" */
  const char *zPubPages;     /* GLOB pattern for public pages */
  const char *zSelfCap;      /* Capabilities of self-registered users */
  int hasSelfReg = 0;        /* True if able to self-register */
  const char *zPublicUrl;    /* Canonical access URL */
  const char *zVulnReport;   /* The vuln-report setting */
  Blob cmd;
  char *z;
  int n, i;
  CapabilityString *pCap;
  char **azCSP;              /* Parsed content security policy */

  login_check_credentials();
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
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







-
+











+
+
+
+
+
+
+
+
+
+
+
+














-
+








  /* Check to see if any TH1 scripts are configured to run on a sync
  */
  if( db_exists("SELECT 1 FROM config WHERE name GLOB 'xfer-*-script'"
                " AND length(value)>0") ){
    @ <li><p><b>WARNING:</b>
    @ TH1 scripts might be configured to run on any sync, push, pull, or
    @ clone operation.  See the the <a href="%R/xfersetup">/xfersetup</a>
    @ clone operation.  See the <a href="%R/xfersetup">/xfersetup</a>
    @ page for more information.  These TH1 scripts are a potential
    @ security concern and so should be carefully audited by a human.
  }

  /* The strict-manifest-syntax setting should be on. */
  if( db_get_boolean("strict-manifest-syntax",1)==0 ){
    @ <li><p><b>WARNING:</b>
    @ The "strict-manifest-syntax"  flag is off.  This is a security
    @ risk.  Turn this setting on (its default) to protect the users
    @ of this repository.
  }

  zVulnReport = db_get("vuln-report","log");
  if( fossil_strcmp(zVulnReport,"block")!=0
   && fossil_strcmp(zVulnReport,"fatal")!=0
  ){
    @ <li><p><b>WARNING:</b>
    @ The <a href="%R/help/vuln-report">vuln-report setting</a>
    @ has a value of "%h(zVulnReport)". This disables defenses against
    @ XSS or SQL-injection vulnerabilities caused by coding errors in
    @ custom TH1 scripts.  For the best security, change
    @ the value of the vuln-report setting to "block" or "fatal".
  }

  /* Obsolete:  */
  if( hasAnyCap(zAnonCap, "d") ||
      hasAnyCap(zDevCap,  "d") ||
      hasAnyCap(zReadCap, "d") ){
    @ <li><p><b>WARNING:</b>
    @ One or more users has the <a
    @ href="https://fossil-scm.org/forum/forumpost/43c78f4bef">obsolete</a>
    @ "d" capability. You should remove it using the
    @ <a href="setup_ulist">User Configuration</a> page in case we
    @ ever reuse the letter for another purpose.
  }

  /* If anonymous users are allowed to create new Wiki, then
  ** wiki moderation should be activated to pervent spam.
  ** wiki moderation should be activated to prevent spam.
  */
  if( hasAnyCap(zAnonCap, "fk") ){
    if( db_get_boolean("modreq-wiki",0)==0 ){
      @ <li><p><b>WARNING:</b>
      @ Anonymous users can create or edit wiki without moderation.
      @ This can result in robots inserting lots of wiki spam into
      @ repository.
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
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







-
+









-
+





-
+







    @ up by the webserver contains the name of an authenticated user.
    @ Fossil's built-in authentication mechanism is bypassed.
    @ Fix this by deactivating the "Allow REMOTE_USER authentication"
    @ checkbox on the <a href="setup_access">Access Control</a> page.
  }
  if( db_get_boolean("http_authentication_ok", 0) ){
    @ <li><p><b>Caution:</b>
    @ This repository trusts that the HTTP_AUTHENITICATION environment
    @ This repository trusts that the HTTP_AUTHENTICATION environment
    @ variable set up by the webserver contains the name of an
    @ authenticated user.
    @ Fossil's built-in authentication mechanism is bypassed.
    @ Fix this by deactivating the "Allow HTTP_AUTHENTICATION authentication"
    @ checkbox on the <a href="setup_access">Access Control</a> page.
  }

  /* Logging should be turned on
  */
  if( db_get_boolean("access-log",0)==0 ){
  if( db_get_boolean("access-log",1)==0 ){
    @ <li><p>
    @ The <a href="access_log">User Log</a> is disabled.  The user log
    @ keeps a record of successful and unsuccessful login attempts and is
    @ useful for security monitoring.
  }
  if( db_get_boolean("admin-log",0)==0 ){
  if( db_get_boolean("admin-log",1)==0 ){
    @ <li><p>
    @ The <a href="admin_log">Administrative Log</a> is disabled.
    @ The administrative log provides a record of configuration changes
    @ and is useful for security monitoring.
  }

#if !defined(_WIN32) && !defined(FOSSIL_OMIT_LOAD_AVERAGE)
662
663
664
665
666
667
668

669





670
671
672
673
674
675
676
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695







+

+
+
+
+
+







      @ <li>%h(azCSP[ii])
    }
    @ </ol>
  }
  fossil_free(azCSP);

  if( alert_enabled() ){
    char * zListId = db_get("email-listid", 0);
    @ <li><p> Email alert configuration summary:
    if( !zListId || !zListId[0] ){
      @ <br><strong>WARNING:</strong> <code>email-listid</code> is not set,
      @ so notifications will not include unsubscribe links.
    }
    fossil_free(zListId);
    @ <table class="label-value">
    stats_for_email();
    @ </table>
  }else{
    @ <li><p> Email alerts are disabled
  }

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
838
839
840
841
842
821
822
823
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
877
878
879
880
881
882
883
884
885
886

887

888
889
890
891
892
893
894
895







-
-
-
-
-





+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+





+
+
+



+
+

+
-
+
-
+







    @ <blockquote><pre>
    @ errorlog: <i>FILENAME</i>
    @ </pre></blockquote>
    blob_reset(&fullname);
  }
}

/*
** The maximum number of bytes of the error log to show by default.
*/
#define MXSHOWLOG 500000

/*
** WEBPAGE: errorlog
**
** Show the content of the error log.  Only the administrator can view
** this page.
**
**    y=0x001          Show only hack attempts
**    y=0x002          Show only panics and assertion faults
**    y=0x004          Show hung backoffice processes
**    y=0x008          Show POST requests from a different origin
**    y=0x010          Show SQLITE_AUTH and similar
**    y=0x020          Show SMTP error reports
**    y=0x040          Show TH1 vulnerability reports
**    y=0x080          Show SQL errors
**    y=0x100          Show timeouts
**    y=0x200          Show WAL recoveries
**    y=0x8000         Show other uncategorized messages
**
** If y is omitted or is zero, a count of the various message types is
** shown.
*/
void errorlog_page(void){
  i64 szFile;
  FILE *in;
  char *zLog;
  const char *zType = P("y");
  static const int eAllTypes = 0x83ff;
  long eType = 0;
  int bOutput = 0;
  int prevWasTime = 0;
  int nHack = 0;
  int nPanic = 0;
  int nOther = 0;
  int nHang = 0;
  int nXPost = 0;
  int nAuth = 0;
  int nSmtp = 0;
  int nVuln = 0;
  int nSqlErr = 0;
  int nTimeout = 0;
  int nRecover = 0;
  char z[10000];
  char zTime[10000];

  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
  }
  if( zType ){
    eType = strtol(zType,0,0) & eAllTypes;
  }
  style_header("Server Error Log");
  style_submenu_element("Test", "%R/test-warning");
  style_submenu_element("Refresh", "%R/errorlog");
  style_submenu_element("Download", "%R/errorlog?download");
  style_submenu_element("Truncate", "%R/errorlog?truncate");
  style_submenu_element("Log-Menu", "%R/setup-logmenu");
  if( eType ){
  style_submenu_element("Panics", "%R/paniclog");
    style_submenu_element("Summary", "%R/errorlog");
  style_submenu_element("Non-Hacks", "%R/hacklog?not");
  }

  if( g.zErrlog==0 || fossil_strcmp(g.zErrlog,"-")==0 ){
    no_error_log_available();
    style_finish_page();
    return;
  }
  if( P("truncate1") && cgi_csrf_safe(2) ){
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
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
1006


1007
1008
1009
1010
1011



1012
1013
1014

1015
1016
1017
1018


1019
1020
1021
1022
1023
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
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







-
-






-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
-
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
-
-
-
+
-
+









-
+




+
-
+
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
+
-
-
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
-
-
+
-
-
-
-
-
-
+
+
+
+
+
-
-
+
-
-
-
-
+
+
+
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+

-
-
-
-
+
+
+
-

-
+

-
-
-
+
+
-
-
-


    @ </form>
    style_finish_page();
    return;
  }
  zLog = file_canonical_name_dup(g.zErrlog);
  @ <p>The server error log at "%h(zLog)" is %,lld(szFile) bytes in size.
  fossil_free(zLog);
  style_submenu_element("Download", "%R/errorlog?download");
  style_submenu_element("Truncate", "%R/errorlog?truncate");
  in = fossil_fopen(g.zErrlog, "rb");
  if( in==0 ){
    @ <p class='generalError'>Unable to open that file for reading!</p>
    style_finish_page();
    return;
  }
  if( szFile>MXSHOWLOG && P("all")==0 ){
    @ <form action="%R/errorlog" method="POST">
    @ <p>Only the last %,d(MXSHOWLOG) bytes are shown.
  if( eType==0 ){
    /* will do a summary */
  }else if( (eType&eAllTypes)!=eAllTypes ){
    @ Only the following types of messages displayed:
    @ <input type="submit" name="all" value="Show All">
    @ </form>
    fseek(in, -MXSHOWLOG, SEEK_END);
  }
  @ <hr>
  @ <pre>
    @ <ul>
    if( eType & 0x01 ){
      @ <li>Hack attempts
    }
    if( eType & 0x02 ){
      @ <li>Panics and assertion faults
    }
    if( eType & 0x04 ){
      @ <li>Hung backoffice processes
  while( fgets(z, sizeof(z), in) ){
    @ %h(z)\
  }
    }
    if( eType & 0x08 ){
      @ <li>POST requests from different origin
    }
    if( eType & 0x10 ){
      @ <li>SQLITE_AUTH and similar errors
    }
    if( eType & 0x20 ){
      @ <li>SMTP malfunctions
    }
  fclose(in);
  @ </pre>
    if( eType & 0x40 ){
      @ <li>TH1 vulnerabilities
  style_finish_page();
}

    }
    if( eType & 0x80 ){
      @ <li>SQL errors
    }
/*
** WEBPAGE: paniclog
**
** Scan the error log for panics.  Show all panic messages, ignoring all
** other error log entries.
*/
    if( eType & 0x100 ){
      @ <li>Timeouts
    }
    if( eType & 0x200 ){
      @ <li>WAL recoveries
    }
    if( eType & 0x8000 ){
      @ <li>Other uncategorized messages
    }
    @ </ul>
void paniclog_page(void){
  i64 szFile;
  char *zLog;
  FILE *in;
  }
  @ <hr>
  if( eType ){
    @ <pre>
  }
  while( fgets(z, sizeof(z), in) ){
  int bOutput = 0;
  int prevWasTime = 0;
    if( prevWasTime ){
  char z[10000];
  char zTime[10000];

  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
  }
  style_header("Server Panic Log");
      if( strncmp(z,"possible hack attempt - 418 ", 27)==0 ){
        bOutput = (eType & 0x01)!=0;
        nHack++;
      }else
      if( strncmp(z,"panic: ", 7)==0 ){
        if( strncmp(z+7,"Timeout",7)==0 ){
          bOutput = (eType & 0x100)!=0;
          nTimeout++;
        }else{
          bOutput = (eType & 0x02)!=0;
          nPanic++;
        }
      }else
      if( strstr(z,"assertion fault")!=0 ){
        bOutput = (eType & 0x02)!=0;
        nPanic++;
      }else
      if( strncmp(z,"SMTP:", 5)==0 ){
        bOutput = (eType & 0x20)!=0;
        nSmtp++;
      }else
      if( sqlite3_strglob("warning: SQLITE_NOTICE(283):*",z)==0 ){
        bOutput = (eType & 0x200)!=0;
        nRecover++;
  style_submenu_element("Log-Menu", "%R/setup-logmenu");

  if( g.zErrlog==0 || fossil_strcmp(g.zErrlog,"-")==0 ){
      }else
      if( sqlite3_strglob("warning: backoffice process * still *",z)==0 ){
        bOutput = (eType & 0x04)!=0;
        nHang++;
      }else
      if( sqlite3_strglob("warning: POST from different origin*",z)==0 ){
        bOutput = (eType & 0x08)!=0;
    no_error_log_available();
    style_finish_page();
    return;
  }
  in = fossil_fopen(g.zErrlog, "rb");
  if( in==0 ){
    @ <p class='generalError'>Unable to open that file for reading!</p>
        nXPost++;
      }else
      if( sqlite3_strglob("SECURITY: authorizer blocks*",z)==0
       || sqlite3_strglob("warning: SQLITE_AUTH*",z)==0
      ){
        bOutput = (eType & 0x10)!=0;
        nAuth++;
      }else
      if( strncmp(z,"possible", 8)==0 && strstr(z,"tainted")!=0 ){
        bOutput = (eType & 0x40)!=0;
        nVuln++;
      }else
      if( strstr(z,"statement aborts at ") ){
        bOutput = (eType & 0x80)!=0;
        nSqlErr++;
    style_finish_page();
    return;
  }
      }else
      {
        bOutput = (eType & 0x8000)!=0;
        nOther++;
      }
  szFile = file_size(g.zErrlog, ExtFILE);
  zLog = file_canonical_name_dup(g.zErrlog);
  @ Panic messages contained within the %lld(szFile)-byte 
  @ <a href="%R/errorlog?all">error log</a> found at
  @ "%h(zLog)".
  fossil_free(zLog);
  @ <hr>
  @ <pre>
  while( fgets(z, sizeof(z), in) ){
    if( prevWasTime
      if( bOutput ){
     && (strncmp(z,"panic: ", 7)==0 || strstr(z," assertion fault ")!=0)
    ){
      @ %h(zTime)\
        @ %h(zTime)\
      bOutput = 1;
      }
    }
    if( strncmp(z, "--------", 8)==0 ){
      size_t n = strlen(z);
      memcpy(zTime, z, n+1);
      prevWasTime = 1;
      bOutput = 0;
    }else{
      prevWasTime = 0;
    }
    if( bOutput ){
    if( bOutput && eType ){
      @ %h(z)\
    }
  }
  fclose(in);
  if( eType ){
  @ </pre>
    @ </pre>
  style_finish_page();
}

  }
  if( eType==0 ){
    int nNonHack = nPanic + nHang + nAuth + nSmtp + nVuln + nOther + nSqlErr;
    int nTotal = nNonHack + nHack + nXPost;
    @ <p><table border="a" cellspacing="0" cellpadding="5">
    if( nPanic>0 ){
      @ <tr><td align="right">%d(nPanic)</td>
      @     <td><a href="./errorlog?y=2">Panics</a></td>
    }
/*
** WEBPAGE: hacklog
**
** Scan the error log for "possible hack attempt" entries  Show hack
** attempt messages only, omitting all others.  Or if the "not" query
** parameter is present, show only messages that are not hack attempts.
*/
void hacklog_page(void){
  i64 szFile;
  char *zLog;
    if( nVuln>0 ){
      @ <tr><td align="right">%d(nVuln)</td>
      @     <td><a href="./errorlog?y=64">TH1 Vulnerabilities</a></td>
  FILE *in;
  int bOutput = 0;
  int prevWasTime = 0;
  int isNot = P("not")!=0;
  char z[10000];
  char zTime[10000];

    }
  login_check_credentials();
  if( !g.perm.Admin ){
    if( nHack>0 ){
    login_needed(0);
    return;
  }
      @ <tr><td align="right">%d(nHack)</td>
      @     <td><a href="./errorlog?y=1">Hack Attempts</a></td>
    }
  style_header("Server Hack Log");
  style_submenu_element("Log-Menu", "%R/setup-logmenu");

  if( g.zErrlog==0 || fossil_strcmp(g.zErrlog,"-")==0 ){
    no_error_log_available();
    if( nSqlErr>0 ){
      @ <tr><td align="right">%d(nSqlErr)</td>
      @     <td><a href="./errorlog?y=128">SQL Errors</a></td>
    }
    if( nTimeout>0 ){
      @ <tr><td align="right">%d(nTimeout)</td>
      @     <td><a href="./errorlog?y=256">Timeouts</a></td>
    style_finish_page();
    return;
  }
    }
    if( nRecover>0 ){
      @ <tr><td align="right">%d(nRecover)</td>
      @     <td><a href="./errorlog?y=512">WAL recoveries</a></td>
    }
  in = fossil_fopen(g.zErrlog, "rb");
  if( in==0 ){
    if( nHang>0 ){
    @ <p class='generalError'>Unable to open that file for reading!</p>
    style_finish_page();
    return;
  }
  szFile = file_size(g.zErrlog, ExtFILE);
  zLog = file_canonical_name_dup(g.zErrlog);
      @ <tr><td align="right">%d(nHang)</td>
      @     <td><a href="./errorlog?y=4">Hung Backoffice</a></td>
    }
    if( nXPost>0 ){
      @ <tr><td align="right">%d(nXPost)</td>
  @ %s(isNot?"Non-hack":"Hack") messages contained within the %lld(szFile)-byte 
  @ <a href="%R/errorlog?all">error log</a> found at
      @     <td><a href="./errorlog?y=8">POSTs from different origin</a></td>
  @ "%h(zLog)".
  fossil_free(zLog);
  @ <hr>
  @ <pre>
    }
    if( nAuth>0 ){
      @ <tr><td align="right">%d(nAuth)</td>
  while( fgets(z, sizeof(z), in) ){
    if( prevWasTime 
     && ((strncmp(z,"possible hack attempt - 418 ", 27)==0) ^ isNot)
    ){
      @     <td><a href="./errorlog?y=16">SQLITE_AUTH and similar</a></td>
    }
    if( nSmtp>0 ){
      @ <tr><td align="right">%d(nSmtp)</td>
      @     <td><a href="./errorlog?y=32">SMTP faults</a></td>
    }
    if( nOther>0 ){
      @ %h(zTime)\
      bOutput = 1;
      @ <tr><td align="right">%d(nOther)</td>
      @     <td><a href="./errorlog?y=32768">Other</a></td>
    }
    if( strncmp(z, "--------", 8)==0 ){
      size_t n = strlen(z);
      memcpy(zTime, z, n+1);
      prevWasTime = 1;
    @ <tr><td align="right">%d(nTotal)</td>
    if( nTotal>0 ){
      @     <td><a href="./errorlog?y=4095">All Messages</a></td>
      bOutput = 0;
    }else{
      prevWasTime = 0;
      @     <td>All Messages</td>
    }
    if( bOutput ){
      @ %h(z)\
    }
    @ </table>
  }
  }
  fclose(in);
  @ </pre>
  style_finish_page();
}
Changes to src/setup.c.
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
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







-
+






-
+





-
+

-
+







    }
    db_protect_pop();
  }
}

/*
** Output a single entry for a menu generated using an HTML table.
** If zLink is not NULL or an empty string, then it is the page that
** If zLink is neither NULL nor an empty string, then it is the page that
** the menu entry will hyperlink to.  If zLink is NULL or "", then
** the menu entry has no hyperlink - it is disabled.
*/
void setup_menu_entry(
  const char *zTitle,
  const char *zLink,
  const char *zDesc
  const char *zDesc  /* Caution!  Rendered using %s.  May contain raw HTML. */
){
  @ <tr><td valign="top" align="right">
  if( zLink && zLink[0] ){
    @ <a href="%s(zLink)"><nobr>%h(zTitle)</nobr></a>
  }else{
    @ %h(zTitle)
    @ <nobr>%h(zTitle)</nobr>
  }
  @ </td><td width="5"></td><td valign="top">%h(zDesc)</td></tr>
  @ </td><td width="5"></td><td valign="top">%s(zDesc)</td></tr>
}



/*
** WEBPAGE: setup
**
116
117
118
119
120
121
122


123
124
125
126
127
128
129
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131







+
+







    setup_menu_entry("Robot-Defense", "setup_robot",
      "Settings for configure defense against robots");
    setup_menu_entry("Settings", "setup_settings",
      "Web interface to the \"fossil settings\" command");
  }
  setup_menu_entry("Timeline", "setup_timeline",
    "Timeline display preferences");
  setup_menu_entry("Tarballs and ZIPs", "setup_download",
    "Preferences for auto-generated tarballs and ZIP files");
  if( setup_user ){
    setup_menu_entry("Login-Group", "setup_login_group",
      "Manage single sign-on between this repository and others"
      " on the same server");
    setup_menu_entry("Tickets", "tktsetup",
      "Configure the trouble-ticketing system for this repository");
    setup_menu_entry("Wiki", "setup_wiki",
138
139
140
141
142
143
144

145
146

147
148
149
150
151
152
153
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157







+


+







  setup_menu_entry("Search","srchsetup",
    "Configure the built-in search engine");
  setup_menu_entry("URL Aliases", "waliassetup",
    "Configure URL aliases");
  if( setup_user ){
    setup_menu_entry("Notification", "setup_notification",
      "Automatic notifications of changes via outbound email");
#if 0  /* Disabled for now.  Does this even work? */
    setup_menu_entry("Transfers", "xfersetup",
      "Configure the transfer system for this repository");
#endif
  }
  setup_menu_entry("Skins", "setup_skin_admin",
    "Select and/or modify the web interface \"skins\"");
  setup_menu_entry("Moderation", "setup_modreq",
    "Enable/Disable requiring moderator approval of Wiki and/or Ticket"
    " changes and attachments.");
  setup_menu_entry("Ad-Unit", "setup_adunit",
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
252
253
254
255
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
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







-
+






+










+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
+




-
-
-
+
+
+
+
-
-
-
+
+
-
-
+
-
-
-
-
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
-
-
-
+







  style_finish_page();
}


/*
** WEBPAGE: setup-logmenu
**
** Show a menu of available log renderings accessible to an administrator, 
** Show a menu of available log renderings accessible to an administrator,
** together with a succinct explanation of each.
**
** This page is only accessible by administrators.
*/
void setup_logmenu_page(void){
  Blob desc;
  int bErrLog;                 /* True if Error Log enabled */
  blob_init(&desc, 0, 0);

  /* Administrator access only */
  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
  }
  style_header("Log Menu");
  @ <table border="0" cellspacing="3">

  if( db_get_boolean("admin-log",1)==0 ){
    blob_appendf(&desc,
      "The admin log records configuration changes to the repository.\n"
      "<b>Disabled</b>:  Turn on the "
      " <a href='%R/setup_settings'>admin-log setting</a> to enable."
    );
    setup_menu_entry("Admin Log", 0, blob_str(&desc));
    blob_reset(&desc);
  }else{
  setup_menu_entry("Admin Log", "admin_log",
    "The admin log records configuration changes to the repository.\n"
    "The admin log is stored in the \"admin_log\" table of the repository.\n"
  );
  setup_menu_entry("Artifact Log", "rcvfromlist",
    "The artifact log records when new content is added to the repository.\n"
    setup_menu_entry("Admin Log", "admin_log",
      "The admin log records configuration changes to the repository\n"
      "in the \"admin_log\" table.\n"
    );
  }
  setup_menu_entry("Xfer Log", "rcvfromlist",
    "The artifact log records when new content is added in the\n"
    "The time and date and origin of the new content is entered into the\n"
    "Log.  The artifact log is always on and is stored in the \"rcvfrom\"\n"
    "table of the repository.\n"
  );

    "\"rcvfrom\" table.\n"
  );
  if( db_get_boolean("access-log",1) ){
    setup_menu_entry("User Log", "user_log",
      "Login attempts recorded in the \"accesslog\" table."
    );
  }else{
    blob_appendf(&desc,
      "Login attempts recorded in the \"accesslog\" table.\n"
      "<b>Disabled</b>:  Turn on the "
      "<a href='%R/setup_settings'>access-log setting</a> to enable."
    );
    setup_menu_entry("User Log", 0, blob_str(&desc));
    blob_reset(&desc);
  }

  blob_appendf(&desc,
    "The error log is a separate text file to which warning and error\n"
    "A separate text file to which warning and error\n"
    "messages are appended.  A single error log can and often is shared\n"
    "across multiple repositories.\n"
  );
  if( g.zErrlog==0 || fossil_strcmp(g.zErrlog,"-")==0 ){
    blob_appendf(&desc,"The error log is disabled for this repository.");
  }else{
    blob_appendf(&desc,"In this repository, the error log is in the file"
    blob_appendf(&desc,"<b>Disabled</b>: "
                       "To enable the error log ");
    if( fossil_strcmp(g.zCmdName, "cgi")==0 ){
      blob_appendf(&desc,
       "named \"%s\".", g.zErrlog);
  }
  setup_menu_entry("Error Log", "errorlog", blob_str(&desc));
        "make an entry like \"errorlog: <i>FILENAME</i>\""
        " in the CGI script at %h",
  blob_reset(&desc);

        P("SCRIPT_FILENAME")
  setup_menu_entry("Panic Log", "paniclog",
    "The panic log is a filtering of the Error Log that shows only the\n"
    "most important messages - assertion faults, segmentation faults, and\n"
    "similar malfunctions."
  );

      );
    }else{
  setup_menu_entry("User Log", "user_log",
    "The user log is a record of login attempts.  The user log is stored\n"
    "in the \"accesslog\" table of the respository.\n"
  );

  setup_menu_entry("Hack Log", "hacklog",
      blob_appendf(&desc,
        " add the \"--errorlog <i>FILENAME</i>\" option to the\n"
        "\"%h %h\" command that launched the server.",
        g.argv[0], g.zCmdName
      );
    }
    bErrLog = 0;
    "All 418 hack attempts"
  );

  setup_menu_entry("Non-Hack Log", "hacklog?not",
  }else{
    blob_appendf(&desc,"In this repository, the error log is the file "
       "named \"%s\".", g.zErrlog);
    bErrLog = 1;
  }
  setup_menu_entry("Error Log", bErrLog ? "errorlog" : 0, blob_str(&desc));
    "All log messages that are not hack attempts"
  );

  blob_reset(&desc);

  @ </table>
  style_finish_page();
}

/*
** Generate a checkbox for an attribute.
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
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
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







+
+
+
+
+
+

-
+
-
-
+
+
+



-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-

-
-
-
-
-
-
-
-



-
+
+


-
-
+
+

-
-







    "2", "UserAgent only",
    "1", "UserAgent and Javascript",
  };
  multiple_choice_attribute(
     "Enable hyperlinks base on User-Agent and/or Javascript",
     "auto-hyperlink", "autohyperlink", "1",
     count(azDefenseOpts)/2, azDefenseOpts);
  @ <br>
  entry_attribute("Delay in milliseconds before enabling hyperlinks", 5,
                  "auto-hyperlink-delay", "ah-delay", "50", 0);
  @ <br>
  onoff_attribute("Also require a mouse event before enabling hyperlinks",
                  "auto-hyperlink-mouseover", "ahmo", 0, 0);
  @ <p>Enable hyperlinks (the equivalent of the "h" permission) for all users,
  @ including user "nobody", as long as the User-Agent string in the
  @ including user "nobody" if the request appears to be from a human.
  @ HTTP header indicates that the request is coming from an actual human
  @ being.  If this setting is "UserAgent only" (2) then the
  @ Disabling hyperlinks helps prevent robots from walking your site and
  @ soaking up all your CPU and bandwidth.
  @ If this setting is "UserAgent only" (2) then the
  @ UserAgent string is the only factor considered.  If the value of this
  @ setting is "UserAgent And Javascript" (1) then Javascript is added that
  @ runs after the page loads and fills in the href= values of &lt;a&gt;
  @ elements.  In either case, &lt;a&gt; tags are only generated if the
  @ UserAgent string indicates that the request is coming from a human and
  @ elements.  In either case, &lt;a&gt; tags are not generated if the
  @ UserAgent string indicates that the client is a robot.
  @ not a robot.
  @
  @ <p>This setting is designed to give easy access to humans while
  @ keeping out robots.
  @ You do not normally want a robot 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>The "UserAgent and Javascript" value for this setting provides
  @ superior protection from robots.  However, that setting also prevents
  @ the visited/unvisited colors on hyperlinks from displaying correctly
  @ (Property: "auto-hyperlink")</p>
  @ on Safari-derived browsers.  (Chrome and Firefox work fine.)  Since
  @ Safari is the underlying rendering engine on all iPhones and iPads,
  @ this means that hyperlink visited/unvisited colors will not operate
  @ on those platforms when "UserAgent and Javascript" is selected.</p>
  @
  @ <p>Additional parameters that control the behavior of Javascript:</p>
  @ <blockquote>
  entry_attribute("Delay in milliseconds before enabling hyperlinks", 5,
                  "auto-hyperlink-delay", "ah-delay", "50", 0);
  @ <br>
  onoff_attribute("Also require a mouse event before enabling hyperlinks",
                  "auto-hyperlink-mouseover", "ahmo", 0, 0);
  @ </blockquote>
  @ <p>For maximum robot defense, "Delay" should be at least 50 milliseconds
  @ and "require a mouse event" should be turned on.  These values only come
  @ into play when the main auto-hyperlink settings is 2 ("UserAgent and
  @ Javascript").</p>
  @ Javascript").
  @ (Properties: "auto-hyperlink-delay" and "auto-hyperlink-mouseover")</p>
  @
  @ <p>To see if Javascript-base hyperlink enabling mechanism is working,
  @ visit the <a href="%R/test_env">/test_env</a> page (from a separate
  @ web browser that is not logged in, even as "anonymous") and verify
  @ visit the <a href="%R/test-env">/test-env</a> page from a separate
  @ web browser that is not logged in, even as "anonymous" and verify
  @ that the "g.jsHref" value is "1".</p>
  @ <p>(Properties: "auto-hyperlink", "auto-hyperlink-delay", and
  @ "auto-hyperlink-mouseover"")</p>
}

/*
** WEBPAGE: setup_robot
**
** Settings associated with defense against robots.  Requires setup privilege.
*/
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
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







-
-
+
+





+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


+
+
+
+
+
+
+











-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







  style_header("Robot Defense Settings");
  db_begin_transaction();
  @ <p>A Fossil website can have billions of pages in its tree, even for a
  @ modest project.  Many of those pages (examples: diffs and tarballs)
  @ might be expensive to compute. A robot that tries to walk the entire
  @ website can present a crippling CPU and bandwidth load.
  @
  @ <p>The settings on this page are intended to help site administrators
  @ defend the site against robots.
  @ <p>The settings on this page are intended to help administrators
  @ defend against abusive robots.
  @
  @ <form action="%R/setup_robot" method="post"><div>
  login_insert_csrf_secret();
  @ <input type="submit"  name="submit" value="Apply Changes"></p>
  @ <hr>
  @ <p><b>Do not allow robots access to these pages.</b><br>
  @ If the page name matches the GLOB pattern of this setting, and the
  @ users is "nobody", and the client has not previously passed a captcha
  @ test to show that it is not a robot, then the page is not displayed.
  @ A captcha test is rendered instead.
  @ The default value for this setting is:
  @ <p>
  @ &emsp;&emsp;&emsp;<tt>%h(robot_restrict_default())</tt>
  @ <p>
  @ Usually the tag should exactly match the page name. Exceptions:
  @ <ul>
  @ <li>  The "diff" tag covers all diffing pages such as /vdiff,
  @       /fdiff, and /vpatch.
  @ <li>  The "annotate" tag covers /annotate and also /blame and
  @       /praise.
  @ <li>  The "zip" covers itself and also /tarball and /sqlar.
  @ <li>  If a tag has an "X" character appended (ex: "timelineX")
  @       then it only applies if query parameters are such that
  @       the page is expensive and/or unusual.
  @ <li>  The "ext" tag covers all extensions, but a tag like
  @       "ext/PATH" only covers the specific extension at PATH.
  @ </ul>
  @ To disable robot restrictions, change this setting to "off".
  @ (Property: <a href="%R/help/robot-restrict">robot-restrict</a>)
  @ <br>
  textarea_attribute("", 2, 80,
      "robot-restrict", "rbrestrict", robot_restrict_default(), 0);

  @ <p><b>Exception #1</b><br>
  @ If "zipX" appears in the robot-restrict list above, then tarballs,
  @ ZIP-archives, and SQL-archives may be downloaded by robots if
  @ the check-in is a leaf (robot-zip-leaf):<br>
  onoff_attribute("Allow tarballs for leaf check-ins",
        "robot-zip-leaf", "rzleaf", 0, 0);

  @ <p><b>Exception #2</b><br>
  @ If "zipX" appears in the robot-restrict list above, then tarballs,
  @ ZIP-archives, and SQL-archives may be downloaded by robots if
  @ the check-in has one or more tags that match the following
  @ list of GLOB patterns:  (robot-zip-tag)<br>
  textarea_attribute("", 2, 80,
      "robot-zip-tag", "rztag", "", 0);

  @ <p><b>Exception #3</b><br>
  @ If the request URI matches any of the following
  @ <a href="%R/re_rules">regular expressions</a> (one per line), then the
  @ request is exempt from anti-robot defenses.
  @ The regular expression is matched against the REQUEST_URI with the
  @ SCRIPT_NAME prefix removed, and with QUERY_STRING appended following
  @ a "?" if QUERY_STRING exists.  (Property: robot-exception)<br>
  textarea_attribute("", 3, 80,
      "robot-exception", "rbexcept", "", 0);
  @ <hr>
  addAutoHyperlinkSettings();

  @ <hr>
  entry_attribute("Anonymous Login Validity", 11, "anon-cookie-lifespan",
                  "anoncookls", "840", 0);
  @ <p>The number of minutes for which an anonymous login cookie is valid.
  @ Set to zero to disable anonymous login.
  @ (property: anon-cookie-lifespan)

  @ <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.
  @ (Property: "max-loadavg")</p>

  @
  @ <hr>
  @ <p><b>Do not allow robots to make complex requests
  @ against the following pages.</b>
  @ <p> A "complex request" is an HTTP request that has one or more query
  @ parameters. Some robots will spend hours juggling around query parameters
  @ or even forging fake query parameters in an effort to discover new
  @ behavior or to find an SQL injection opportunity or similar.  This can
  @ waste hours of CPU time and gigabytes of bandwidth on the server.  A
  @ suggested value for this setting is:
  @ "<tt>timeline,*diff,vpatch,annotate,blame,praise,dir,tree</tt>".
  @ (Property: robot-restrict)
  @ <p>
  textarea_attribute("", 2, 80,
      "robot-restrict", "rbrestrict", "", 0);

  @ <hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ </div></form>
  db_end_transaction(0);
  style_finish_page();
}

572
573
574
575
576
577
578
579

580
581
582
583
584
585
586
622
623
624
625
626
627
628

629
630
631
632
633
634
635
636







-
+







  @ without the "--localauth" option.
  @ <li> The server is started from CGI without the "localauth" keyword
  @ in the CGI script.
  @ </ol>
  @ (Property: "localauth")
  @
  @ <hr>
  onoff_attribute("Enable /test_env",
  onoff_attribute("Enable /test-env",
     "test_env_enable", "test_env_enable", 0, 0);
  @ <p>When enabled, the %h(g.zBaseURL)/test_env URL is available to all
  @ users.  When disabled (the default) only users Admin and Setup can visit
  @ the /test_env page.
  @ (Property: "test_env_enable")
  @ </p>
  @
742
743
744
745
746
747
748







749
750
751
752
753
754
755
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812







+
+
+
+
+
+
+







                  "auto-captcha", "autocaptcha", 0, 0);
  @ <p>When enabled, a button appears on the login screen for user
  @ "anonymous" that will automatically fill in the CAPTCHA password.
  @ This is less secure than forcing the user to do it manually, but is
  @ probably secure enough and it is certainly more convenient for
  @ anonymous users.  (Property: "auto-captcha")</p>

  @ <hr>
  entry_attribute("Anonymous Login Validity", 11, "anon-cookie-lifespan",
                  "anoncookls", "840", 0);
  @ <p>The number of minutes for which an anonymous login cookie is valid.
  @ Set to zero to disable anonymous logins.
  @ (property: anon-cookie-lifespan)

  @ <hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ </div></form>
  db_end_transaction(0);
  style_finish_page();
}

903
904
905
906
907
908
909
910

911
912
913
914
915
916
917
960
961
962
963
964
965
966

967
968
969
970
971
972
973
974







-
+







  @ <li><p><b>project-description</b> &rarr;
  @ A description of project in this repository.  This is a verbose form
  @ of project-name.  This description can be edited in the second entry
  @ box on the <a href="./setup_config">Setup/Configuration page</a>.
  @
  @ <li><p><b>project-name</b> &rarr;
  @ The human-readable name for the project.  The project-name can be
  @ modified in the first entry on the 
  @ modified in the first entry on the
  @ <a href="./setup_config">Setup/Configuration page</a>.
  @
  @ <li><p><b>peer-repo-<i>CODE</i></b> &rarr;
  @ <i>CODE</i> is 16-character prefix of the project-code for another
  @ repository that is part of the same login-group.  The value is the
  @ filename for the peer repository.
  @
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
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







+
+
+
+
+













-
-
-
-
-
-
-







  static const char *const azTimeFormats[] = {
      "0", "HH:MM",
      "1", "HH:MM:SS",
      "2", "YYYY-MM-DD HH:MM",
      "3", "YYMMDD HH:MM",
      "4", "(off)"
  };
  static const char *const azLeafMark[] = {
      "0", "No",
      "1", "Yes",
      "2", "Yes - with emphasis",
  };
  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
  }

  style_set_current_feature("setup");
  style_header("Timeline Display Preferences");
  db_begin_transaction();
  @ <form action="%R/setup_timeline" method="post"><div>
  login_insert_csrf_secret();
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>

  @ <hr>
  onoff_attribute("Allow block-markup in timeline",
                  "timeline-block-markup", "tbm", 0, 0);
  @ <p>In timeline displays, check-in comments can be displayed with or
  @ without block markup such as paragraphs, tables, etc.
  @ (Property: "timeline-block-markup")</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.  Use CSS to change
  @ display formatting features such as fonts and line-wrapping behavior.
  @ (Property: "timeline-plaintext")</p>
978
979
980
981
982
983
984










985
986
987
988
989
990
991
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056







+
+
+
+
+
+
+
+
+
+







  @ <hr>
  onoff_attribute("Break comments at newline characters",
                  "timeline-hard-newlines", "thnl", 0, 0);
  @ <p>In timeline displays, newline characters in check-in comments force
  @ a line break on the display.
  @ (Property: "timeline-hard-newlines")</p>

  @ <hr>
  onoff_attribute("Do not adjust user-selected background colors",
                  "raw-bgcolor", "rbgc", 0, 0);
  @ <p>Fossil normally attempts to adjust the saturation and intensity of
  @ user-specified background colors on check-ins and branches so that the
  @ foreground text is easily readable on all skins.  Enable this setting
  @ to omit that adjustment and use exactly the background color specified
  @ by users.
  @ (Property: "raw-bgcolor")</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')");
  tmDiff = db_double(0.0,
1021
1022
1023
1024
1025
1026
1027






1028
1029
1030
1031
1032
1033
1034
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105







+
+
+
+
+
+







            "tdf", "0", count(azTimeFormats)/2, azTimeFormats);
  @ <p>If the "HH:MM" or "HH:MM:SS" format is selected, then the date is shown
  @ in a separate box (using CSS class "timelineDate") whenever the date
  @ changes.  With the "YYYY-MM-DD&nbsp;HH:MM" and "YYMMDD ..." formats,
  @ the complete date and time is shown on every timeline entry using the
  @ CSS class "timelineTime". (Property: "timeline-date-format")</p>

  @ <hr>
  multiple_choice_attribute("Leaf Markings", "timeline-mark-leaves",
            "tml", "1", count(azLeafMark)/2, azLeafMark);
  @ <p>Should timeline entries for leaf check-ins be identified in the
  @ detail section.  (Property: "timeline-mark-leaves")</p>

  @ <hr>
  entry_attribute("Max timeline comment length", 6,
                  "timeline-max-comment", "tmc", "0", 0);
  @ <p>The maximum length of a comment to be displayed in a timeline.
  @ "0" there is no length limit.
  @ (Property: "timeline-max-comment")</p>

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
1142
1143
1144
1145
1146
1147
1148
1149
1150





1151
1152
1153
1154
1155
1156
1157
1148
1149
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
1182
1183
1184
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
1210
1211
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







+
















+
+
+
+







+
+
+
+
+
+





-
+
+
+
+



-
+













-
+
+
+
+

-
+
















-
-
+
+
+
+
+







** Change or view miscellaneous settings.  Part of the
** /setup pages requiring Setup privileges.
*/
void setup_settings(void){
  int nSetting;
  int i;
  Setting const *pSet;
  int bIfChng = P("all")==0;
  const Setting *aSetting = setting_info(&nSetting);

  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }

  style_set_current_feature("setup");
  style_header("Settings");
  if(!g.repositoryOpen){
    /* Provide read-only access to versioned settings,
       but only if no repo file was explicitly provided. */
    db_open_local(0);
  }
  db_begin_transaction();
  if( bIfChng ){
    @ <p>Only settings whose value is different from the default are shown.
    @ Click the "All" button above to set all settings.
  }
  @ <p>Settings marked with (v) are "versionable" and will be overridden
  @ by the contents of managed files named
  @ "<tt>.fossil-settings/</tt><i>SETTING-NAME</i>".
  @ If the file for a versionable setting exists, the value cannot be
  @ changed on this screen.</p><hr><p>
  @
  @ <form action="%R/setup_settings" method="post"><div>
  if( bIfChng ){
    style_submenu_element("All", "%R/setup_settings?all");
  }else{
    @ <input type="hidden" name="all" value="1">
    style_submenu_element("Changes-Only", "%R/setup_settings");
  }
  @ <table border="0"><tr><td valign="top">
  login_insert_csrf_secret();
  for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
    if( pSet->width==0 ){
      int hasVersionableValue = pSet->versionable &&
          (db_get_versioned(pSet->name, NULL)!=0);
          (db_get_versioned(pSet->name, NULL, NULL)!=0);
      if( bIfChng && setting_has_default_value(pSet, db_get(pSet->name,0)) ){
        continue;
      }
      onoff_attribute("", pSet->name,
                      pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/,
                      is_truth(pSet->def), hasVersionableValue);
      @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
      @ <a href='%R/help/%s(pSet->name)'>%h(pSet->name)</a>
      if( pSet->versionable ){
        @  (v)<br>
      } else {
        @ <br>
      }
    }
  }
  @ <br><input type="submit"  name="submit" value="Apply Changes">
  @ </td><td style="width:50px;"></td><td valign="top">
  @ <table>
  for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
    if( pSet->width>0 && !pSet->forceTextArea ){
      int hasVersionableValue = pSet->versionable &&
          (db_get_versioned(pSet->name, NULL)!=0);
          (db_get_versioned(pSet->name, NULL, NULL)!=0);
      if( bIfChng && setting_has_default_value(pSet, db_get(pSet->name,0)) ){
        continue;
      }
      @ <tr><td>
      @ <a href='%R/help?cmd=%s(pSet->name)'>%h(pSet->name)</a>
      @ <a href='%R/help/%s(pSet->name)'>%h(pSet->name)</a>
      if( pSet->versionable ){
        @  (v)
      } else {
        @
      }
      @</td><td>
      entry_attribute("", /*pSet->width*/ 25, pSet->name,
                      pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/,
                      (char*)pSet->def, hasVersionableValue);
      @</td></tr>
    }
  }
  @</table>
  @ </td><td style="width:50px;"></td><td valign="top">
  for(i=0, pSet=aSetting; i<nSetting; i++, pSet++){
    if( pSet->width>0 && pSet->forceTextArea ){
      int hasVersionableValue = db_get_versioned(pSet->name, NULL)!=0;
      @ <a href='%R/help?cmd=%s(pSet->name)'>%s(pSet->name)</a>
      int hasVersionableValue = db_get_versioned(pSet->name, NULL, NULL)!=0;
      if( bIfChng && setting_has_default_value(pSet, db_get(pSet->name,0)) ){
        continue;
      }
      @ <a href='%R/help/%s(pSet->name)'>%s(pSet->name)</a>
      if( pSet->versionable ){
        @  (v)<br>
      } else {
        @ <br>
      }
      textarea_attribute("", /*rows*/ 2, /*cols*/ 35, pSet->name,
                      pSet->var!=0 ? pSet->var : pSet->name /*works-like:"x"*/,
1241
1242
1243
1244
1245
1246
1247
1248

1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
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







-
+












-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







  @ The project name will also be used as the RSS feed title.
  @ (Property: "project-name")
  @ </p>
  @ <hr>
  textarea_attribute("Project Description", 3, 80,
                     "project-description", "pd", "", 0);
  @ <p>Describe your project. This will be used in page headers for search
  @ engines as well as a short RSS description.
  @ engines, the repository listing and a short RSS description.
  @ (Property: "project-description")</p>
  @ <hr>
  entry_attribute("Canonical Server URL", 40, "email-url",
                   "eurl", "", 0);
  @ <p>This is the URL used to access this repository as a server.
  @ Other repositories use this URL to clone or sync against this repository.
  @ This is also the basename for hyperlinks included in email alert text.
  @ Omit the trailing "/".
  @ If this repo will not be set up as a persistent server and will not
  @ be sending email alerts, then leave this entry blank.
  @ Suggested value: "%h(g.zBaseURL)"
  @ (Property: "email-url")</p>
  @ <hr>
  entry_attribute("Tarball and ZIP-archive Prefix", 20, "short-project-name",
                  "spn", "", 0);
  @ <p>This is used as a prefix on the names of generated tarballs and
  @ ZIP archive. For best results, keep this prefix brief and avoid special
  @ characters such as "/" and "\".
  @ If no tarball prefix is specified, then the full Project Name above is used.
  @ (Property: "short-project-name")
  @ </p>
  @ <hr>
  entry_attribute("Download Tag", 20, "download-tag", "dlt", "trunk", 0);
  @ <p>The <a href='%R/download'>/download</a> page is designed to provide
  @ a convenient place for newbies
  @ to download a ZIP archive or a tarball of the project.  By default,
  @ the latest trunk check-in is downloaded.  Change this tag to something
  @ else (ex: release) to alter the behavior of the /download page.
  @ (Property: "download-tag")
  @ </p>
  @ <hr>
  entry_attribute("Index Page", 60, "index-page", "idxpg", "/home", 0);
  @ <p>Enter the pathname of the page to display when the "Home" menu
  @ option is selected and when no pathname is
  @ specified in the URL.  For example, if you visit the url:</p>
  @
  @ <blockquote><p>%h(g.zBaseURL)</p></blockquote>
1359
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
1398
1399
1400
1401
1402
1403
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
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
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
1517




1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




















-
+


-
-
-
-
+
+
+
+




+







      "sitemap-extra", "smextra", "", 0);
  @ <hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ </div></form>
  db_end_transaction(0);
  style_finish_page();
}

/*
** WEBPAGE: setup_download
**
** The "Admin/Download" page.  Requires Setup privilege.
*/
void setup_download(void){
  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }

  style_set_current_feature("setup");
  style_header("Tarball and ZIP Downloads");
  db_begin_transaction();
  @ <form action="%R/setup_download" method="post"><div>
  login_insert_csrf_secret();
  @ <input type="submit"  name="submit" value="Apply Changes"></p>
  @ <hr>
  entry_attribute("Tarball and ZIP Name Prefix", 20, "short-project-name",
                  "spn", "", 0);
  @ <p>This is used as a prefix for the names of generated tarballs and
  @ ZIP archive. Keep this prefix brief and use only lower-case ASCII
  @ characters, digits, "_", "-" in the name. If this setting is blank,
  @ then the full <a href='%R/help/project-name'>project-name</a> setting
  @ is used instead.
  @ (Property: "short-project-name")
  @ </p>
  @ <hr>
  @ <p><b>Configuration for the <a href="%R/download">/download</a> page.</b>
  @ <p>The value is a TCL list divided into groups of four tokens:
  @ <ol>
  @ <li> Maximum number of matches (COUNT).
  @ <li> Tag to match using glob (TAG).
  @ <li> Maximum age of check-ins to match (MAX_AGE).
  @ <li> Comment to apply to matches (COMMENT).
  @ </ol>
  @ Each 4-tuple will match zero or more check-ins.  The /download page
  @ displays the union of matches from all 4-tuples.
  @ See the <a href="%R/help/suggested-downloads">suggested-downloads</a>
  @ setting documentation for further detail.
  @ <p>
  @ The /download page is omitted from the <a href="%R/sitemap">/sitemap</a>
  @ if the first token is "0" or "off" or "no".  The default value 
  @ for this setting is "off".
  @ (Property: <a href="%R/help/suggested-downloads">suggested-downloads</a>)
  @ <p>
  textarea_attribute("", 4, 80,
      "suggested-downloads", "sgtrlst", "off", 0);
  @ <hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ </div></form>
  db_end_transaction(0);
  style_finish_page();
}

/*
** WEBPAGE: setup_wiki
**
** The "Admin/Wiki" page.  Requires Setup privilege.
*/
void setup_wiki(void){
  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }

  style_set_current_feature("setup");
  style_header("Wiki Configuration");
  db_begin_transaction();
  @ <form action="%R/setup_wiki" method="post"><div>
  login_insert_csrf_secret();
  @ <input type="submit"  name="submit" value="Apply Changes"></p>
  @ <hr>
  onoff_attribute("Associate Wiki Pages With Branches, Tags, or Checkins",
  onoff_attribute("Associate Wiki Pages With Branches, Tags, Tickets, or Checkins",
                  "wiki-about", "wiki-about", 1, 0);
  @ <p>
  @ Associate wiki pages with branches, tags, or checkins, based on
  @ the wiki page name.  Wiki pages that begin with "branch/", "checkin/"
  @ or "tag/" and which continue with the name of an existing branch, check-in
  @ or tag are treated specially when this feature is enabled.
  @ Associate wiki pages with branches, tags, tickets, or checkins, based on
  @ the wiki page name.  Wiki pages that begin with "branch/", "checkin/",
  @ "tag/" or "ticket" and which continue with the name of an existing branch,
  @ check-in, tag or ticket are treated specially when this feature is enabled.
  @ <ul>
  @ <li> <b>branch/</b><i>branch-name</i>
  @ <li> <b>checkin/</b><i>full-check-in-hash</i>
  @ <li> <b>tag/</b><i>tag-name</i>
  @ <li> <b>ticket/</b><i>full-ticket-hash</i>
  @ </ul>
  @ (Property: "wiki-about")</p>
  @ <hr>
  entry_attribute("Allow Unsafe HTML In Markdown", 6,
                  "safe-html", "safe-html", "", 0);
  @ <p>Allow "unsafe" HTML (ex: &lt;script&gt;, &lt;form&gt;, etc) to be
  @ generated by <a href="%R/md_rules">Markdown-formatted</a> documents.
1459
1460
1461
1462
1463
1464
1465



1466
1467
1468
1469
1470
1471
1472
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605







+
+
+







    login_needed(0);
    return;
  }

  style_set_current_feature("setup");
  style_header("Chat Configuration");
  db_begin_transaction();
  if( P("rbldchatidx") && cgi_csrf_safe(2) ){
    chat_rebuild_index(1);
  }
  @ <form action="%R/setup_chat" method="post"><div>
  login_insert_csrf_secret();
  @ <input type="submit"  name="submit" value="Apply Changes"></p>
  @ <hr>
  entry_attribute("Initial Chat History Size", 10,
                  "chat-initial-history", "chatih", "50", 0);
  @ <p>When /chat first starts up, it preloads up to this many historical
1511
1512
1513
1514
1515
1516
1517

1518
1519



1520

















1521
1522
1523
1524
1525
1526
1527
1644
1645
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







+

-
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








  multiple_choice_attribute("Alert sound",
     "chat-alert-sound", "snd", azAlerts[0],
     count(azAlerts)/2, azAlerts);
  @ <p>The sound used in the client-side chat to indicate that a new
  @ chat message has arrived.
  @ (Property: "chat-alert-sound")</p>

  @ <hr/>
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ <p><input type="submit"  name="submit" value="Apply Changes">
  @ <input type="submit" name="rbldchatidx"\
  @  value="Rebuild Full-Text Search Index"></p>
  @ </div></form>

  /* Validate the chat FTS search index */
  if( db_table_exists("repository","chatfts1") ){
    char *zMissing;
    zMissing = db_text(0,
      "SELECT group_concat(rowid,', ') FROM chat"
      " WHERE rowid NOT IN (SELECT rowid FROM chatfts1_docsize)"
    );
    if( zMissing && zMissing[0] ){
      @ <p><b>WARNING:</b> The following chat messages are missing
      @ from the full-text index.  Press the "Rebuild Full-Text Search Index"
      @ button above to fix this.</p>
      @ <p>%h(zMissing)</p>
    }
    fossil_free(zMissing);
  }

  db_end_transaction(0);
  @ <script nonce="%h(style_nonce())">
  @ (function(){
  @   var w = document.getElementById('idsnd');
  @   w.onchange = function(){
  @     var audio = new Audio('%s(g.zBaseURL)/builtin/' + w.value);
  @     audio.currentTime = 0;
2088
2089
2090
2091
2092
2093
2094
2095

2096
2097
2098
2099
2100
2101
2102
2241
2242
2243
2244
2245
2246
2247

2248
2249
2250
2251
2252
2253
2254
2255







-
+







  }
  style_set_current_feature("setup");
  style_header("Admin Log");
  style_submenu_element("Log-Menu", "setup-logmenu");
  create_admin_log_table();
  limit = atoi(PD("n","200"));
  ofst = atoi(PD("x","0"));
  fLogEnabled = db_get_boolean("admin-log", 0);
  fLogEnabled = db_get_boolean("admin-log", 1);
  @ <div>Admin logging is %s(fLogEnabled?"on":"off").
  @ (Change this on the <a href="setup_settings">settings</a> page.)</div>

  if( ofst>0 ){
    int prevx = ofst - limit;
    if( prevx<0 ) prevx = 0;
    @ <p><a href="admin_log?n=%d(limit)&x=%d(prevx)">[Newer]</a></p>
2155
2156
2157
2158
2159
2160
2161

2162
2163
2164
2165
2166
2167
2168
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322







+








/*
** WEBPAGE: srchsetup
**
** Configure the search engine.  Requires Admin privilege.
*/
void page_srchsetup(){
  const char *zMainBranch;
  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
  }
  style_set_current_feature("setup");
  style_header("Search Configuration");
2185
2186
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
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







+
-
+

-
+
+














+
+



+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
















+
+
+
+







  @ <td>Search all Markdown files in the doc/ subfolder and all README.txt
  @ files.</tr>
  @ <tr><td>*<td><td>Search all checked-in files</tr>
  @ <tr><td><i>(blank)</i><td>
  @ <td>Search nothing. (Disables document search).</tr>
  @ </table>
  @ <hr>
  zMainBranch = db_main_branch();
  entry_attribute("Document Branches", 20, "doc-branch", "db", "trunk", 0);
  entry_attribute("Document Branches", 20, "doc-branch", "db", zMainBranch, 0);
  @ <p>When searching documents, use the versions of the files found at the
  @ type of the "Document Branches" branch.  Recommended value: "trunk".
  @ type of the "Document Branches" branch.  Recommended value: the name of
  @ the main branch (usually "trunk").
  @ Document search is disabled if blank. It may be a list of branch names
  @ separated by spaces and/or commas.
  @ <hr>
  onoff_attribute("Search Check-in Comments", "search-ci", "sc", 0, 0);
  @ <br>
  onoff_attribute("Search Documents", "search-doc", "sd", 0, 0);
  @ <br>
  onoff_attribute("Search Tickets", "search-tkt", "st", 0, 0);
  @ <br>
  onoff_attribute("Search Wiki", "search-wiki", "sw", 0, 0);
  @ <br>
  onoff_attribute("Search Tech Notes", "search-technote", "se", 0, 0);
  @ <br>
  onoff_attribute("Search Forum", "search-forum", "sf", 0, 0);
  @ <br>
  onoff_attribute("Search Built-in Help Text", "search-help", "sh", 0, 0);
  @ <hr>
  @ <p><input type="submit"  name="submit" value="Apply Changes"></p>
  @ <hr>
  if( cgi_csrf_safe(2) ){
  if( P("fts0") ){
    search_drop_index();
  }else if( P("fts1") ){
    const char *zTokenizer = PD("ftstok","off");
    search_set_tokenizer(zTokenizer);
    search_drop_index();
    search_create_index();
    search_fill_index();
    search_update_index(search_restrict(SRCH_ALL));
    if( P("fts0") ){
      search_drop_index();
    }else if( P("fts1") ){
      const char *zTokenizer = PD("ftstok","off");
      search_set_tokenizer(zTokenizer);
      search_drop_index();
      search_create_index();
      search_fill_index();
      search_update_index(search_restrict(SRCH_ALL));
    }
    if( P("rbldchatidx") ){
      chat_rebuild_index(1);
    }
  }
  if( search_index_exists() ){
    int pgsz = db_int64(0, "PRAGMA repository.page_size;");
    i64 nTotal = db_int64(0, "PRAGMA repository.page_count;")*pgsz;
    i64 nFts = db_int64(0, "SELECT count(*) FROM dbstat"
                               " WHERE schema='repository'"
                               " AND name LIKE 'fts%%'")*pgsz;
    char zSize[30];
    approxSizeName(sizeof(zSize),zSize,nFts);
    @ <p>Currently using an SQLite FTS%d(search_index_type(0)) search index.
    @ The index helps search run faster, especially on large repositories,
    @ but takes up space.  The index is currently using about %s(zSize)
    @ or %.1f(100.0*(double)nFts/(double)nTotal)%% of the repository.</p>
    select_fts_tokenizer();
    @ <p><input type="submit" name="fts0" value="Delete The Full-Text Index">
    @ <input type="submit" name="fts1" value="Rebuild The Full-Text Index">
    if( db_table_exists("repository","chat") ){
      @ <input type="submit" name="rbldchatidx" \
      @ value="Rebuild The Chat FTS Index">
    }
    style_submenu_element("FTS Index Debugging","%R/test-ftsdocs");
  }else{
    @ <p>The SQLite search index is disabled.  All searching will be
    @ a full-text scan.  This usually works fine, but can be slow for
    @ larger repositories.</p>
    select_fts_tokenizer();
    @ <p><input type="submit" name="fts1" value="Create A Full-Text Index">
Changes to src/setupuser.c.
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
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







+






-
+



-
+







*/
void setup_ulist(void){
  Stmt s;
  double rNow;
  const char *zWith = P("with");
  int bUnusedOnly = P("unused")!=0;
  int bUbg = P("ubg")!=0;
  int bHaveAlerts;

  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
  }

  bHaveAlerts = alert_tables_exist();
  style_submenu_element("Add", "setup_uedit");
  style_submenu_element("Log", "access_log");
  style_submenu_element("Help", "setup_ulist_notes");
  if( alert_tables_exist() ){
  if( bHaveAlerts ){
    style_submenu_element("Subscribers", "subscribers");
  }
  style_set_current_feature("setup");
  style_header("User List");
  if( (zWith==0 || zWith[0]==0) && !bUnusedOnly ){
    @ <table border=1 cellpadding=2 cellspacing=0 class='userTable'>
    @ <thead><tr>
113
114
115
116
117
118
119
120

121
122
123
124
125
126
127
114
115
116
117
118
119
120

121
122
123
124
125
126
127
128







-
+







      }
    }
  }
  if( !bUnusedOnly ){
    style_submenu_element("Unused", "setup_ulist?unused");
  }
  @ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \
  @  data-column-types='ktxTTKt' data-init-sort='2'>
  @  data-column-types='ktxKTKt' data-init-sort='4'>
  @ <thead><tr>
  @ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login\
  @ <th>Alerts</tr></thead>
  @ <tbody>
  db_multi_exec(
    "CREATE TEMP TABLE lastAccess(uname TEXT PRIMARY KEY, atime REAL)"
    "WITHOUT ROWID;"
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


205

206
207
208
209
210
211
212
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
205
206

207
208

209
210
211
212
213

214
215
216
217
218
219
220
221







-
+







-
-
-
+
+
+


-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+













-
+
+












-
+


-
+

-
+


+
+
-
+







  }
  if( bUnusedOnly ){
    zWith = mprintf(
        " AND login NOT IN ("
        "SELECT user FROM event WHERE user NOT NULL "
        "UNION ALL SELECT euser FROM event WHERE euser NOT NULL%s)"
        " AND uid NOT IN (SELECT uid FROM rcvfrom)",
        alert_tables_exist() ?
        bHaveAlerts ?
          " UNION ALL SELECT suname FROM subscriber WHERE suname NOT NULL":"");
  }else if( zWith && zWith[0] ){
    zWith = mprintf(" AND fullcap(cap) GLOB '*[%q]*'", zWith);
  }else{
    zWith = "";
  }
  db_prepare(&s,
     "SELECT uid, login, cap, info, date(user.mtime,'unixepoch'),"
     "       lower(login) AS sortkey, "
     "       CASE WHEN info LIKE '%%expires 20%%'"
      /*0-4*/"SELECT uid, login, cap, info, date(user.mtime,'unixepoch'),"
      /* 5 */"lower(login) AS sortkey, "
      /* 6 */"CASE WHEN info LIKE '%%expires 20%%'"
             "    THEN substr(info,instr(lower(info),'expires')+8,10)"
             "    END AS exp,"
             "atime,"
     "       subscriber.ssub, subscriber.subscriberId"
     "  FROM user LEFT JOIN lastAccess ON login=uname"
     "            LEFT JOIN subscriber ON login=suname"
     " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s"
     " ORDER BY sortkey", zWith/*safe-for-%s*/
      /* 7 */"atime,"
      /* 8 */"user.mtime AS sorttime,"
      /*9-11*/"%s"
             " FROM user LEFT JOIN lastAccess ON login=uname"
             "            LEFT JOIN subscriber ON login=suname"
             " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s"
             " ORDER BY sorttime DESC",
             bHaveAlerts
             ? "subscriber.ssub, subscriber.subscriberId, subscriber.semail"
             : "null, null, null",
             zWith/*safe-for-%s*/
  );
  rNow = db_double(0.0, "SELECT julianday('now');");
  while( db_step(&s)==SQLITE_ROW ){
    int uid = db_column_int(&s, 0);
    const char *zLogin = db_column_text(&s, 1);
    const char *zCap = db_column_text(&s, 2);
    const char *zInfo = db_column_text(&s, 3);
    const char *zDate = db_column_text(&s, 4);
    const char *zSortKey = db_column_text(&s,5);
    const char *zExp = db_column_text(&s,6);
    double rATime = db_column_double(&s,7);
    char *zAge = 0;
    const char *zSub;
    int sid = db_column_int(&s,9);
    int sid = db_column_int(&s,10);
    sqlite3_int64 sorttime = db_column_int64(&s, 8);
    if( rATime>0.0 ){
      zAge = human_readable_age(rNow - rATime);
    }
    if( bUbg ){
      @ <tr style='background-color: %h(user_color(zLogin));'>
    }else{
      @ <tr>
    }
    @ <td data-sortkey='%h(zSortKey)'>\
    @ <a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a>
    @ <td>%h(zCap)
    @ <td>%h(zInfo)
    @ <td>%h(zDate?zDate:"")
    @ <td data-sortkey='%09llx(sorttime)'>%h(zDate?zDate:"")
    @ <td>%h(zExp?zExp:"")
    @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"")
    if( db_column_type(&s,8)==SQLITE_NULL ){
    if( db_column_type(&s,9)==SQLITE_NULL ){
      @ <td>
    }else if( (zSub = db_column_text(&s,8))==0 || zSub[0]==0 ){
    }else if( (zSub = db_column_text(&s,9))==0 || zSub[0]==0 ){
      @ <td><a href="%R/alerts?sid=%d(sid)"><i>off</i></a>
    }else{
      const char *zEmail = db_column_text(&s, 11);
      char * zAt = zEmail ? mprintf(" &rarr; %h", zEmail) : mprintf("");
      @ <td><a href="%R/alerts?sid=%d(sid)">%h(zSub)</a>
      @ <td><a href="%R/alerts?sid=%d(sid)">%h(zSub)</a>  %z(zAt)
    }

    @ </tr>
    fossil_free(zAge);
  }
  @ </tbody></table>
  db_finalize(&s);
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
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
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
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
480
481
482
483







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+












+

















-
+
-
-
-
+
+
+
+







*/
static int isValidPwString(const char *zPw){
  if( zPw==0 ) return 0;
  if( zPw[0]==0 ) return 1;
  while( zPw[0]=='*' ){ zPw++; }
  return zPw[0]!=0;
}

/*
** Return true if user capability strings zOrig and zNew materially
** differ, taking into account that they may be sorted in an arbitary
** order. This does not take inherited permissions into
** account. Either argument may be NULL. A NULL and an empty string
** are considered equivalent here. e.g. "abc" and "cab" are equivalent
** for this purpose, but "aCb" and "acb" are not.
*/
static int userCapsChanged(const char *zOrig, const char *zNew){
  if( !zOrig ){
    return zNew ? (0!=*zNew) : 0;
  }else if( !zNew ){
    return 0!=*zOrig;
  }else if( 0==fossil_strcmp(zOrig, zNew) ){
    return 0;
  }else{
    /* We don't know that zOrig and zNew are sorted equivalently.  The
    ** following steps will compare strings which contain all the same
    ** capabilities letters as equivalent, regardless of the letters'
    ** order in their strings. */
    char aOrig[128]; /* table of zOrig bytes */
    int nOrig = 0, nNew = 0;

    memset( &aOrig[0], 0, sizeof(aOrig) );
    for( ; *zOrig; ++zOrig, ++nOrig ){
      if( 0==(*zOrig & 0x80) ){
        aOrig[(int)*zOrig] = 1;
      }
    }
    for( ; *zNew; ++zNew, ++nNew ){
      if( 0==(*zNew & 0x80) && !aOrig[(int)*zNew] ){
        return 1;
      }
    }
    return nOrig!=nNew;
  }
}

/*
** COMMAND: test-user-caps-changed
**
** Usage: %fossil test-user-caps-changed caps1 caps2
**
*/
void test_user_caps_changed(void){

  char const * zOld = g.argc>2 ? g.argv[2] : NULL;
  char const * zNew = g.argc>3 ? g.argv[3] : NULL;
  fossil_print("Has changes? = %d\n",
               userCapsChanged( zOld, zNew ));
}

/*
** Sends notification of user permission elevation changes to all
** subscribers with a "u" subscription. This is a no-op if alerts are
** not enabled.
**
** These subscriptions differ from most, in that:
**
** - They currently lack an "unsubscribe" link.
**
** - Only an admin can assign this subscription, but if a non-admin
**   edits their subscriptions after an admin assigns them this one,
**   this particular one will be lost.  "Feature or bug?" is unclear,
**   but it would be odd for a non-admin to be assigned this
**   capability.
*/
static void alert_user_cap_change(const char *zLogin,   /*Affected user*/
                                 int uid,              /*[user].uid*/
                                 int bIsNew,           /*true if new user*/
                                 const char *zOrigCaps,/*Old caps*/
                                 const char *zNewCaps  /*New caps*/){
  Blob hdr, body;
  Stmt q;
  int nBody;
  AlertSender *pSender;
  char *zSubname;
  char *zURL;
  char * zSubject;

  if( !alert_enabled() ) return;
  zSubject = bIsNew
    ? mprintf("New user created: [%q]", zLogin)
    : mprintf("User [%q] capabilities changed", zLogin);
  zURL = db_get("email-url",0);
  zSubname = db_get("email-subname", "[Fossil Repo]");
  blob_init(&body, 0, 0);
  blob_init(&hdr, 0, 0);
  if( bIsNew ){
    blob_appendf(&body, "User [%q] was created with "
                 "permissions [%q] by user [%q].\n",
                 zLogin, zNewCaps, g.zLogin);
  } else {
    blob_appendf(&body, "Permissions for user [%q] where changed "
                 "from [%q] to [%q] by user [%q].\n",
                 zLogin, zOrigCaps, zNewCaps, g.zLogin);
  }
  if( zURL ){
    blob_appendf(&body, "\nUser editor: %s/setup_uedit?id=%d\n", zURL, uid);
  }
  nBody = blob_size(&body);
  pSender = alert_sender_new(0, 0);
  db_prepare(&q,
        "SELECT semail, hex(subscriberCode)"
        "  FROM subscriber, user "
        " WHERE sverified AND NOT sdonotcall"
        "   AND suname=login"
        "   AND ssub GLOB '*u*'");
  while( !pSender->zErr && db_step(&q)==SQLITE_ROW ){
    const char *zTo = db_column_text(&q, 0);
    blob_truncate(&hdr, 0);
    blob_appendf(&hdr, "To: <%s>\r\nSubject: %s %s\r\n",
                 zTo, zSubname, zSubject);
    if( zURL ){
      const char *zCode = db_column_text(&q, 1);
      blob_truncate(&body, nBody);
      blob_appendf(&body,"\n-- \nSubscription info: %s/alerts/%s\n",
                   zURL, zCode);
    }
    alert_send(pSender, &hdr, &body, 0);
  }
  db_finalize(&q);
  alert_sender_free(pSender);
  fossil_free(zURL);
  fossil_free(zSubname);
  fossil_free(zSubject);
}

/*
** WEBPAGE: setup_uedit
**
** Edit information about a user or create a new user.
** Requires Admin privileges.
*/
void user_edit(void){
  const char *zId, *zLogin, *zInfo, *zCap, *zPw;
  const char *zGroup;
  const char *zOldLogin;
  int uid, i;
  char *zOldCaps = 0;        /* Capabilities before edit */
  char *zDeleteVerify = 0;   /* Delete user verification text */
  int higherUser = 0;  /* True if user being edited is SETUP and the */
                       /* user doing the editing is ADMIN.  Disallow editing */
  const char *inherit[128];
  int a[128];
  const char *oa[128];

  /* Must have ADMIN privileges to access this page
  */
  login_check_credentials();
  if( !g.perm.Admin ){ login_needed(0); return; }

  /* Check to see if an ADMIN user is trying to edit a SETUP account.
  ** Don't allow that.
  */
  zId = PD("id", "0");
  uid = atoi(zId);
  if( zId && !g.perm.Setup && uid>0 ){
  if( uid>0 ){
    char *zOldCaps;
    zOldCaps = db_text(0, "SELECT cap FROM user WHERE uid=%d",uid);
    higherUser = zOldCaps && strchr(zOldCaps,'s');
    zOldCaps = db_text("", "SELECT cap FROM user WHERE uid=%d",uid);
    if( zId && !g.perm.Setup ){
      higherUser = zOldCaps && strchr(zOldCaps,'s');
    }
  }

  if( P("can") ){
    /* User pressed the cancel button */
    cgi_redirect(cgi_referer("setup_ulist"));
    return;
  }
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
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







+
+
-
+





-
+




-
+




-
+


-
+
+







  }else if( zDeleteVerify!=0 ){
    /* Need to verify a delete request */
  }else if( !cgi_csrf_safe(2) ){
    /* This might be a cross-site request forgery, so ignore it */
  }else{
    /* We have all the information we need to make the change to the user */
    char c;
    int bCapsChanged = 0 /* 1 if user's permissions changed */;
    const int bIsNew = uid<=0;
    char zCap[70], zNm[4];
    char aCap[70], zNm[4];
    zNm[0] = 'a';
    zNm[2] = 0;
    for(i=0, c='a'; c<='z'; c++){
      zNm[1] = c;
      a[c&0x7f] = ((c!='s' && c!='y') || g.perm.Setup) && P(zNm)!=0;
      if( a[c&0x7f] ) zCap[i++] = c;
      if( a[c&0x7f] ) aCap[i++] = c;
    }
    for(c='0'; c<='9'; c++){
      zNm[1] = c;
      a[c&0x7f] = P(zNm)!=0;
      if( a[c&0x7f] ) zCap[i++] = c;
      if( a[c&0x7f] ) aCap[i++] = c;
    }
    for(c='A'; c<='Z'; c++){
      zNm[1] = c;
      a[c&0x7f] = P(zNm)!=0;
      if( a[c&0x7f] ) zCap[i++] = c;
      if( a[c&0x7f] ) aCap[i++] = c;
    }

    zCap[i] = 0;
    aCap[i] = 0;
    bCapsChanged = bIsNew || userCapsChanged(zOldCaps, &aCap[0]);
    zPw = P("pw");
    zLogin = P("login");
    if( strlen(zLogin)==0 ){
      const char *zRef = cgi_referer("setup_ulist");
      style_header("User Creation Error");
      @ <span class="loginError">Empty login not allowed.</span>
      @
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
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







-
-
-
-
-
+
+
+
+
+
+











-
-
+
+
+













-
+




















-
+








-
+









+
+
+
+
+



+
+
+








-
+





+


-







      @ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)">
      @ [Bummer]</a></p>
      style_finish_page();
      return;
    }
    cgi_csrf_verify();
    db_unprotect(PROTECT_USER);
    db_multi_exec(
       "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
       "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now())",
      uid, zLogin, P("info"), zPw, zCap
    );
    uid = db_int(0,
                 "REPLACE INTO user(uid,login,info,pw,cap,mtime) "
                 "VALUES(nullif(%d,0),%Q,%Q,%Q,%Q,now()) "
                 "RETURNING uid",
                 uid, zLogin, P("info"), zPw, &aCap[0]);
    assert( uid>0 );
    if( zOldLogin && fossil_strcmp(zLogin, zOldLogin)!=0 ){
      if( alert_tables_exist() ){
        /* Rename matching subscriber entry, else the user cannot
           re-subscribe with their same email address. */
        db_multi_exec("UPDATE subscriber SET suname=%Q WHERE suname=%Q",
                      zLogin, zOldLogin);
      }
      admin_log( "Renamed user [%q] to [%q].", zOldLogin, zLogin );
    }
    db_protect_pop();
    setup_incr_cfgcnt();
    admin_log( "Updated user [%q] with capabilities [%q].",
               zLogin, zCap );
    admin_log( "%s user [%q] with capabilities [%q].",
               bIsNew ? "Added" : "Updated",
               zLogin, &aCap[0] );
    if( atoi(PD("all","0"))>0 ){
      Blob sql;
      char *zErr = 0;
      blob_zero(&sql);
      if( zOldLogin==0 ){
        blob_appendf(&sql,
          "INSERT INTO user(login)"
          "  SELECT %Q WHERE NOT EXISTS(SELECT 1 FROM user WHERE login=%Q);",
          zLogin, zLogin
        );
        zOldLogin = zLogin;
      }
#if 0
      /* Problem: when renaming a user we need to update the subcriber
      /* Problem: when renaming a user we need to update the subscriber
      ** names to match but we cannot know from here if each member of
      ** the login group has the subscriber tables, so we cannot blindly
      ** include this SQL. */
      else if( fossil_strcmp(zLogin, zOldLogin)!=0
               && alert_tables_exist() ){
        /* Rename matching subscriber entry, else the user cannot
           re-subscribe with their same email address. */
        blob_appendf(&sql,
                     "UPDATE subscriber SET suname=%Q WHERE suname=%Q;",
                     zLogin, zOldLogin);
      }
#endif
      blob_appendf(&sql,
        "UPDATE user SET login=%Q,"
        "  pw=coalesce(shared_secret(%Q,%Q,"
                "(SELECT value FROM config WHERE name='project-code')),pw),"
        "  info=%Q,"
        "  cap=%Q,"
        "  mtime=now()"
        " WHERE login=%Q;",
        zLogin, P("pw"), zLogin, P("info"), zCap,
        zLogin, P("pw"), zLogin, P("info"), &aCap[0],
        zOldLogin
      );
      db_unprotect(PROTECT_USER);
      login_group_sql(blob_str(&sql), "<li> ", " </li>\n", &zErr);
      db_protect_pop();
      blob_reset(&sql);
      admin_log( "Updated user [%q] in all login groups "
                 "with capabilities [%q].",
                 zLogin, zCap );
                 zLogin, &aCap[0] );
      if( zErr ){
        const char *zRef = cgi_referer("setup_ulist");
        style_header("User Change Error");
        admin_log( "Error updating user '%q': %s'.", zLogin, zErr );
        @ <span class="loginError">%h(zErr)</span>
        @
        @ <p><a href="setup_uedit?id=%d(uid)&referer=%T(zRef)">
        @ [Bummer]</a></p>
        style_finish_page();
        if( bCapsChanged ){
          /* It's possible that caps were updated locally even if
          ** login group updates failed. */
          alert_user_cap_change(zLogin, uid, bIsNew, zOldCaps, &aCap[0]);
        }
        return;
      }
    }
    if( bCapsChanged ){
      alert_user_cap_change(zLogin, uid, bIsNew, zOldCaps, &aCap[0]);
    }
    cgi_redirect(cgi_referer("setup_ulist"));
    return;
  }

  /* Load the existing information about the user, if any
  */
  zLogin = "";
  zInfo = "";
  zCap = "";
  zCap = zOldCaps;
  zPw = "";
  for(i='a'; i<='z'; i++) oa[i] = "";
  for(i='0'; i<='9'; i++) oa[i] = "";
  for(i='A'; i<='Z'; i++) oa[i] = "";
  if( uid ){
    assert( zCap );
    zLogin = db_text("", "SELECT login FROM user WHERE uid=%d", uid);
    zInfo = db_text("", "SELECT info FROM user WHERE uid=%d", uid);
    zCap = db_text("", "SELECT cap FROM user WHERE uid=%d", uid);
    zPw = db_text("", "SELECT pw FROM user WHERE uid=%d", uid);
    for(i=0; zCap[i]; i++){
      char c = zCap[i];
      if( (c>='a' && c<='z') || (c>='0' && c<='9') || (c>='A' && c<='Z') ){
        oa[c&0x7f] = " checked=\"checked\"";
      }
    }
1014
1015
1016
1017
1018
1019
1020
1021

1022
1023
1024
1025
1026
1027
1028
1166
1167
1168
1169
1170
1171
1172

1173
1174
1175
1176
1177
1178
1179
1180







-
+







    return;
  }
  style_header("User %h", db_column_text(&q,1));
  @ <table class="label-value">
  @ <tr><th>uid:</th><td>%d(db_column_int(&q,0))
  @  (<a href="%R/setup_uedit?id=%d(db_column_int(&q,0))">edit</a>)</td></tr>
  @ <tr><th>login:</th><td>%h(db_column_text(&q,1))</td></tr>
  @ <tr><th>capabilities:</th><td>%h(db_column_text(&q,2))</th></tr>
  @ <tr><th>capabilities:</th><td>%h(db_column_text(&q,2))</td></tr>
  @ <tr><th valign="top">info:</th>
  @ <td valign="top"><span style='white-space:pre-line;'>\
  @ %h(db_column_text(&q,5))</span></td></tr>
  @ <tr><th>user.mtime:</th><td>%h(db_column_text(&q,6))</td></tr>
  if( db_column_type(&q,7)!=SQLITE_NULL ){
    @ <tr><th>subscriberId:</th><td>%d(db_column_int(&q,7))
    @  (<a href="%R/alerts?sid=%d(db_column_int(&q,7))">edit</a>)</td></tr>
Changes to src/sha1.c.
255
256
257
258
259
260
261
262

263
264
265
266
267
268
269
255
256
257
258
259
260
261

262
263
264
265
266
267
268
269







-
+








    if (digest) {
        for (i = 0; i < 20; i++)
            digest[i] = (unsigned char)
                ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255);
    }
}
#endif /* Built-in SHA1 implemenation */
#endif /* Built-in SHA1 implementation */

/*
** Convert a digest into base-16.  digest should be declared as
** "unsigned char digest[20]" in the calling function.  The SHA1
** digest is stored in the first 20 bytes.  zBuf should
** be "char zBuf[41]".
*/
418
419
420
421
422
423
424
425

426
427
428
429
430
431
432
418
419
420
421
422
423
424

425
426
427
428
429
430
431
432







-
+







  unsigned char zResult[20];
  char zDigest[41];

  SHA1Init(&ctx);
  SHA1Update(&ctx, (unsigned const char*)zIn, strlen(zIn));
  SHA1Final(zResult, &ctx);
  DigestToBase16(zResult, zDigest);
  return mprintf("%s", zDigest);
  return fossil_strdup(zDigest);
}

/*
** Convert a cleartext password for a specific user into a SHA1 hash.
**
** The algorithm here is:
**
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
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







-
+











-
+







    if( zProjectId==0 ){
      zProjectId = db_get("project-code", 0);

      /* On the first xfer request of a clone, the project-code is not yet
      ** known.  Use the cleartext password, since that is all we have.
      */
      if( zProjectId==0 ){
        return mprintf("%s", zPw);
        return fossil_strdup(zPw);
      }
    }
    zProjCode = zProjectId;
  }
  SHA1Update(&ctx, (unsigned char*)zProjCode, strlen(zProjCode));
  SHA1Update(&ctx, (unsigned char*)"/", 1);
  SHA1Update(&ctx, (unsigned char*)zLogin, strlen(zLogin));
  SHA1Update(&ctx, (unsigned char*)"/", 1);
  SHA1Update(&ctx, (unsigned const char*)zPw, strlen(zPw));
  SHA1Final(zResult, &ctx);
  DigestToBase16(zResult, zDigest);
  return mprintf("%s", zDigest);
  return fossil_strdup(zDigest);
}

/*
** Implement the shared_secret() SQL function.  shared_secret() takes two or
** three arguments; the third argument is optional.
**
** (1) The cleartext password
Changes to src/sha3.c.
610
611
612
613
614
615
616
617

618
619
620
621
622
623
624
610
611
612
613
614
615
616

617
618
619
620
621
622
623
624







-
+







char *sha3sum(const char *zIn, int iSize){
  SHA3Context ctx;
  char zDigest[132];

  SHA3Init(&ctx, iSize);
  SHA3Update(&ctx, (unsigned const char*)zIn, strlen(zIn));
  DigestToBase16(SHA3Final(&ctx), zDigest, iSize/8);
  return mprintf("%s", zDigest);
  return fossil_strdup(zDigest);
}
#endif

/*
** COMMAND: sha3sum*
**
** Usage: %fossil sha3sum FILE...
Changes to src/shun.c.
371
372
373
374
375
376
377
378

379
380
381
382
383
384
385
371
372
373
374
375
376
377

378
379
380
381
382
383
384
385







-
+







  const int perScreen = 500;   /* RCVIDs per page */

  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
    return;
  }
  style_header("Artifact Receipts");
  style_header("Xfer Log");
  style_submenu_element("Log-Menu", "setup-logmenu");
  if( showAll ){
    ofst = 0;
  }else{
    style_submenu_element("All", "rcvfromlist?all=1");
  }
  if( ofst>0 ){
413
414
415
416
417
418
419

420
421


422
423
424
425
426
427
428
413
414
415
416
417
418
419
420


421
422
423
424
425
426
427
428
429







+
-
-
+
+







    "       EXISTS(SELECT 1 FROM rcvidSha1 WHERE x=rcvfrom.rcvid),"
    "       EXISTS(SELECT 1 FROM rcvidSha3 WHERE x=rcvfrom.rcvid)"
    "  FROM rcvfrom LEFT JOIN user USING(uid)"
    " ORDER BY rcvid DESC LIMIT %d OFFSET %d",
    showAll ? -1 : perScreen+1, ofst
  );
  @ <p>Whenever new artifacts are added to the repository, either by
  @ push or using the web interface or by "fossil commit" or similar,
  @ push or using the web interface, an entry is made in the RCVFROM table
  @ to record the source of that artifact.  This log facilitates
  @ an entry is made in the RCVFROM table
  @ to record the source of those artifacts.  This log facilitates
  @ finding and fixing attempts to inject illicit content into the
  @ repository.</p>
  @
  @ <p>Click on the "rcvid" to show a list of specific artifacts received
  @ by a transaction.  After identifying illicit artifacts, remove them
  @ using the "Shun" button.  If an "rcvid" is not hyperlinked, that means
  @ all artifacts associated with that rcvid have already been shunned
Changes to src/sitemap.c.
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
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







-
-
-
-
-
-
-
-
-
-
-
-



















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







  int srchFlags;
  int inSublist = 0;
  int i;
  int isPopup = 0;         /* This is an XMLHttpRequest() for /sitemap */
  int e = atoi(PD("e","0"));
  const char *zExtra;

#if 0  /* Removed 2021-01-26 */
  const struct {
    const char *zTitle;
    const char *zProperty;
  } aExtra[] = {
    { "Documentation",  "sitemap-docidx" },
    { "Download",       "sitemap-download" },
    { "License",        "sitemap-license" },
    { "Contact",        "sitemap-contact" },
  };
#endif

  login_check_credentials();
  if( P("popup")!=0 ){
    /* The "popup" query parameter
    ** then disable anti-robot defenses */
    isPopup = 1;
    g.perm.Hyperlink = 1;
    g.jsHref = 0;
  }
  srchFlags = search_restrict(SRCH_ALL);
  if( !isPopup ){
    style_header("Site Map");
    style_adunit_config(ADUNIT_RIGHT_OK);
  }

  @ <ul id="sitemap" class="columns" style="column-width:20em">
  if( (e&1)==0 ){
    @ <li>%z(href("%R/home"))Home Page</a>
  }

#if 0  /* Removed 2021-01-26  */
  for(i=0; i<sizeof(aExtra)/sizeof(aExtra[0]); i++){
    char *z = db_get(aExtra[i].zProperty,0);
    if( z==0 || z[0]==0 ) continue;
    if( !inSublist ){
      @ <ul>
      inSublist = 1;
    }
    if( z[0]=='/' ){
      @ <li>%z(href("%R%s",z))%s(aExtra[i].zTitle)</a></li>
    }else{
      @ <li>%z(href("%s",z))%s(aExtra[i].zTitle)</a></li>
    }
  }
#endif

  zExtra = db_get("sitemap-extra",0);
  if( zExtra && (e&2)==0 ){
    int rc;
    char **azExtra = 0;
    int *anExtra;
    int nExtra = 0;
    if( isPopup ) Th_FossilInit(0);
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
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







-
-
-
-
-
-
-
-
-
-
-





+
+
+













+
+
+







        }
      }
    }
    Th_Free(g.interp, azExtra);
  }
  if( (e&1)!=0 ) goto end_of_sitemap;

#if 0  /* Removed on 2021-02-11.  Make a sitemap-extra entry if you */
       /* really want this */
  if( srchFlags & SRCH_DOC ){
    if( !inSublist ){
      @ <ul>
      inSublist = 1;
    }
    @ <li>%z(href("%R/docsrch"))Documentation Search</a></li>
  }
#endif

  if( inSublist ){
    @ </ul>
    inSublist = 0;
  }
  @ </li>
  if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){
    @ <li>%z(href("%R/ckout"))Checkout Status</a></li>
  }
  if( g.perm.Read ){
    const char *zEditGlob = db_get("fileedit-glob","");
    @ <li>%z(href("%R/tree"))File Browser</a>
    @   <ul>
    @   <li>%z(href("%R/tree?type=tree&ci=trunk"))Tree-view,
    @        Trunk Check-in</a></li>
    @   <li>%z(href("%R/tree?type=flat"))Flat-view</a></li>
    @   <li>%z(href("%R/fileage?name=trunk"))File ages for Trunk</a></li>
    @   <li>%z(href("%R/uvlist"))Unversioned Files</a>
    if( g.perm.Write && zEditGlob[0]!=0 ){
      @   <li>%z(href("%R/fileedit"))On-line File Editor</li>
    }
    @ </ul>
  }
  if( g.perm.Zip && db_get_boolean("suggested-downloads",0)!=0 ){
    @ <li>%z(href("%R/download"))Tarballs and ZIPs</a>
  }
  if( g.perm.Read ){
    @ <li>%z(href("%R/timeline"))Project Timeline</a>
    @ <ul>
    @   <li>%z(href("%R/reports"))Activity Reports</a></li>
    @   <li>%z(href("%R/sitemap-timeline"))Other timelines</a></li>
    @ </ul>
190
191
192
193
194
195
196

197

198
199
200
201
202
203
204
157
158
159
160
161
162
163
164

165
166
167
168
169
170
171
172







+
-
+







  if( srchFlags ){
    @ <li>%z(href("%R/search"))Search</a></li>
  }
  if( g.perm.Chat ){
    @ <li>%z(href("%R/chat"))Chat</a></li>
  }
  if( g.perm.RdForum ){
    const char *zTitle = db_get("forum-title","Forum");
    @ <li>%z(href("%R/forum"))Forum</a>
    @ <li>%z(href("%R/forum"))%h(zTitle)</a>
    @ <ul>
    @   <li>%z(href("%R/timeline?y=f"))Recent activity</a></li>
    @ </ul>
    @ </li>
  }
  if( g.perm.RdTkt ){
    @ <li>%z(href("%R/reportlist"))Tickets/Bug Reports</a>
318
319
320
321
322
323
324
325

326
327
328
329
330
331
332

333
334
335
336
337
338
339
286
287
288
289
290
291
292

293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308







-
+







+







  }
  if( !isPopup ){
    style_header("Test Page Map");
    style_adunit_config(ADUNIT_RIGHT_OK);
  }
  @ <ul id="sitemap" class="columns" style="column-width:20em">
  if( g.perm.Admin || db_get_boolean("test_env_enable",0) ){
    @ <li>%z(href("%R/test_env"))CGI Environment Test</a></li>
    @ <li>%z(href("%R/test-env"))CGI Environment Test</a></li>
  }
  if( g.perm.Read ){
    @ <li>%z(href("%R/test-rename-list"))List of file renames</a></li>
  }
  @ <li>%z(href("%R/test-builtin-files"))List of built-in files</a></li>
  @ <li>%z(href("%R/mimetype_list"))List of MIME types</a></li>
  @ <li>%z(href("%R/hash-color-test"))Hash color test</a>
  @ <li>%z(href("%R/test-bgcolor"))Background color test</a>
  if( g.perm.Admin ){
    @ <li>%z(href("%R/test-backlinks"))List of backlinks</a></li>
    @ <li>%z(href("%R/test-backlink-timeline"))Backlink timeline</a></li>
    @ <li>%z(href("%R/phantoms"))List of phantom artifacts</a></li>
    @ <li>%z(href("%R/test-warning"))Error Log test page</a></li>
    @ <li>%z(href("%R/repo_stat1"))Repository <tt>sqlite_stat1</tt> table</a>
    @ <li>%z(href("%R/repo_schema"))Repository schema</a></li>
Changes to src/skins.c.
160
161
162
163
164
165
166
167

168
169
170
171
172
173
174
160
161
162
163
164
165
166

167
168
169
170
171
172
173
174







-
+







**    "fossil http", or  the "skin:" CGI config setting.
**
** 2) The "skin" display setting cookie or URL argument, in that
**    order. If the "skin" URL argument is provided and refers to a legal
**    skin then that will update the display cookie. If the skin name is
**    illegal it is silently ignored.
**
** 3) The built-in skin identfied by the "default-skin" setting, if such
** 3) The built-in skin identified by the "default-skin" setting, if such
**    a setting exists and matches one of the built-in skin names.
**
** 4) Skin properties (settings "css", "details", "footer", "header",
**    and "js") from the CONFIG db table
**
** 5) The built-in skin named "default"
**
1343
1344
1345
1346
1347
1348
1349
1350

1351
1352
1353
1354
1355
1356
1357
1343
1344
1345
1346
1347
1348
1349

1350
1351
1352
1353
1354
1355
1356
1357







-
+







  builtin_request_js("skin.js");
  style_finish_page();
}

/*
** WEBPAGE: skins
**
** Show a list of all of the built-in skins, plus the respository skin,
** Show a list of all of the built-in skins, plus the repository skin,
** and provide the user with an opportunity to change to any of them.
*/
void skins_page(void){
  int i;
  char *zBase = fossil_strdup(g.zTop);
  size_t nBase = strlen(zBase);
  login_check_credentials();
1371
1372
1373
1374
1375
1376
1377
1378

1379
1380
1381
1382
1383
1384
1385
1371
1372
1373
1374
1375
1376
1377

1378
1379
1380
1381
1382
1383
1384
1385







-
+







    @ <p class="warning">Warning:
    if( iDraftSkin>0 ){
      @ you are using a draft skin,
    }else{
      @ this fossil instance was started with a hard-coded skin
      @ value
    }
    @ which supercedes any option selected below. A skin selected
    @ which supersedes any option selected below. A skin selected
    @ below will be recorded in your 
    @ "%z(href("%R/fdscookie"))fossil_display_settings</a>" cookie
    @ but will not be used so long as the site has a
    @ higher-priority skin in place.
    @ </p>
  }
  @ <p>The following skins are available for this repository:</p>
Changes to src/smtp.c.
154
155
156
157
158
159
160
161


162
163

164
165
166
167
168
169
170
154
155
156
157
158
159
160

161
162
163
164
165
166
167
168
169
170
171
172







-
+
+


+







struct SmtpSession {
  const char *zFrom;        /* Domain from which we are sending */
  const char *zDest;        /* Domain that will receive the email */
  char *zHostname;          /* Hostname of SMTP server for zDest */
  u32 smtpFlags;            /* Flags changing the operation */
  FILE *logFile;            /* Write session transcript to this log file */
  Blob *pTranscript;        /* Record session transcript here */
  int atEof;                /* True after connection closes */
  int bOpen;                /* True if connection is Open */
  int bFatal;               /* Error is fatal.  Do not retry */
  char *zErr;               /* Error message */
  Blob inbuf;               /* Input buffer */
  UrlData url;              /* Address of the server */
};

/* Allowed values for SmtpSession.smtpFlags */
#define SMTP_TRACE_STDOUT   0x00001     /* Debugging info to console */
#define SMTP_TRACE_FILE     0x00002     /* Debugging info to logFile */
#define SMTP_TRACE_BLOB     0x00004     /* Record transcript */
#define SMTP_DIRECT         0x00008     /* Skip the MX lookup */
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
252
253
254
255
256
257
258
259
260

261
262
263
264
265
266
267
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

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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




-
-
-
+
+
+

-
+
-
-





-
+


-
-






-
-
+

-

-
+

-
-
-
-
-
-
-






-
+





-
-
+


-
+

-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-










-
+







void smtp_session_free(SmtpSession *pSession){
  socket_close();
  blob_reset(&pSession->inbuf);
  fossil_free(pSession->zHostname);
  fossil_free(pSession->zErr);
  fossil_free(pSession);
}

/*
** Set an error message on the SmtpSession
*/
static void smtp_set_error(
  SmtpSession *p,             /* The SMTP context */
  int bFatal,                 /* Fatal error.  Reset and retry is pointless */
  const char *zFormat,        /* Error message. */
  ...
){
  if( bFatal ) p->bFatal = 1;
  if( p->zErr==0 ){
    va_list ap;
    va_start(ap, zFormat);
    p->zErr = vmprintf(zFormat, ap);
    va_end(ap);
  }
  if( p->bOpen ){
    socket_close();
    p->bOpen = 0;
  }
}

/*
** Allocate a new SmtpSession object.
**
** Both zFrom and zDest must be specified.
**
** The ... arguments are in this order:
** Both zFrom and zDest must be specified.  smtpFlags may not contain
** either SMTP_TRACE_FILE or SMTP_TRACE_BLOB as those settings must be
** added by a subsequent call to smtp_session_config().
**
**    SMTP_PORT:            int
** The iPort option is ignored unless SMTP_PORT is set in smtpFlags
**    SMTP_TRACE_FILE:      FILE*
**    SMTP_TRACE_BLOB:      Blob*
*/
SmtpSession *smtp_session_new(
  const char *zFrom,    /* Domain for the client */
  const char *zDest,    /* Domain of the server */
  u32 smtpFlags,        /* Flags */
  ...                   /* Arguments depending on the flags */
  int iPort             /* TCP port if the SMTP_PORT flags is present */
){
  SmtpSession *p;
  va_list ap;
  UrlData url;

  p = fossil_malloc( sizeof(*p) );
  memset(p, 0, sizeof(*p));
  p->zFrom = zFrom;
  p->zDest = zDest;
  p->smtpFlags = smtpFlags;
  memset(&url, 0, sizeof(url));
  url.port = 25;
  p->url.port = 25;
  blob_init(&p->inbuf, 0, 0);
  va_start(ap, smtpFlags);
  if( smtpFlags & SMTP_PORT ){
    url.port = va_arg(ap, int);
    p->url.port = iPort;
  }
  if( smtpFlags & SMTP_TRACE_FILE ){
    p->logFile = va_arg(ap, FILE*);
  }
  if( smtpFlags & SMTP_TRACE_BLOB ){
    p->pTranscript = va_arg(ap, Blob*);
  }
  va_end(ap);
  if( (smtpFlags & SMTP_DIRECT)!=0 ){
    int i;
    p->zHostname = fossil_strdup(zDest);
    for(i=0; p->zHostname[i] && p->zHostname[i]!=':'; i++){}
    if( p->zHostname[i]==':' ){
      p->zHostname[i] = 0;
      url.port = atoi(&p->zHostname[i+1]);
      p->url.port = atoi(&p->zHostname[i+1]);
    }
  }else{
    p->zHostname = smtp_mx_host(zDest);
  }
  if( p->zHostname==0 ){
    p->atEof = 1;
    p->zErr = mprintf("cannot locate SMTP server for \"%s\"", zDest);
    smtp_set_error(p, 1, "cannot locate SMTP server for \"%s\"", zDest);
    return p;
  }
  url.name = p->zHostname;
  p->url.name = p->zHostname;
  socket_global_init();
  if( socket_open(&url) ){
    p->atEof = 1;
    p->zErr = socket_errmsg();
    socket_close();
  p->bOpen = 0;
  return p;
}

/*
** Configure debugging options on SmtpSession.  Add all bits in
** smtpFlags to the settings.  The following bits can be added:
**
**    SMTP_FLAG_FILE:     In which case pArg is the FILE* pointer to use
**
**    SMTP_FLAG_BLOB:     In which case pArg is the Blob* poitner to use.
*/
void smtp_session_config(SmtpSession *p, u32 smtpFlags, void *pArg){
  p->smtpFlags = smtpFlags;
  if( smtpFlags & SMTP_TRACE_FILE ){
    p->logFile = (FILE*)pArg;
  }else if( smtpFlags & SMTP_TRACE_BLOB ){
    p->pTranscript = (Blob*)pArg;
  }
  return p;
}

/*
** Send a single line of output the SMTP client to the server.
*/
static void smtp_send_line(SmtpSession *p, const char *zFormat, ...){
  Blob b = empty_blob;
  va_list ap;
  char *z;
  int n;
  if( p->atEof ) return;
  if( !p->bOpen ) return;
  va_start(ap, zFormat);
  blob_vappendf(&b, zFormat, ap);
  va_end(ap);
  z = blob_buffer(&b);
  n = blob_size(&b);
  assert( n>=2 );
  assert( z[n-1]=='\n' );
289
290
291
292
293
294
295
296

297
298
299
300
301
302
303
312
313
314
315
316
317
318

319
320
321
322
323
324
325
326







-
+







static void smtp_recv_line(SmtpSession *p, Blob *in){
  int n = blob_size(&p->inbuf);
  char *z = blob_buffer(&p->inbuf);
  int i = blob_tell(&p->inbuf);
  int nDelay = 0;
  if( i<n && z[n-1]=='\n' ){
    blob_line(&p->inbuf, in);
  }else if( p->atEof ){
  }else if( !p->bOpen ){
    blob_init(in, 0, 0);
  }else{
    if( n>0 && i>=n ){
      blob_truncate(&p->inbuf, 0);
      blob_rewind(&p->inbuf);
      n = 0;
    }
312
313
314
315
316
317
318
319

320
321
322
323
324
325
326
327
328
335
336
337
338
339
340
341

342


343
344
345
346
347
348
349







-
+
-
-







        z[n] = 0;
        if( n>0 && z[n-1]=='\n' ) break;
        if( got==1000 ) continue;
      }
      nDelay++;
      if( nDelay>100 ){
        blob_init(in, 0, 0);
        p->zErr = mprintf("timeout");
        smtp_set_error(p, 1, "client times out waiting on server response");
        socket_close();
        p->atEof = 1;
        return;
      }else{
        sqlite3_sleep(100);
      }
    }while( n<1 || z[n-1]!='\n' );
    blob_truncate(&p->inbuf, n);
    blob_line(&p->inbuf, in);
352
353
354
355
356
357
358

359
360
361
362
363
364
365
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387







+







  int *pbMore,      /* True if the reply is not complete */
  char **pzArg      /* Argument */
){
  int n;
  char *z;
  blob_truncate(in, 0);
  smtp_recv_line(p, in);
  blob_trim(in);
  z = blob_str(in);
  n = blob_size(in);
  if( z[0]=='#' ){
    *piCode = 0;
    *pbMore = 1;
    *pzArg = z;
  }else{
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
408
409
410
411

412
413
414


415
416
417
418
419
420
421
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
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455







+
-
-
-
-
-
-
+
+
+
+
+
+
+









-
+




+
+
+
+
+
+




+








+



+
+







** Have the client send a QUIT message.
*/
int smtp_client_quit(SmtpSession *p){
  Blob in = BLOB_INITIALIZER;
  int iCode = 0;
  int bMore = 0;
  char *zArg = 0;
  if( p->bOpen ){
  smtp_send_line(p, "QUIT\r\n");
  do{
    smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
  }while( bMore );
  p->atEof = 1;
  socket_close();
    smtp_send_line(p, "QUIT\r\n");
    do{
      smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
    }while( bMore );
    p->bOpen = 0;
    socket_close();
  }
  return 0;
}

/*
** Begin a client SMTP session.  Wait for the initial 220 then send
** the EHLO and wait for a 250.
**
** Return 0 on success and non-zero for a failure.
*/
int smtp_client_startup(SmtpSession *p){
static int smtp_client_startup(SmtpSession *p){
  Blob in = BLOB_INITIALIZER;
  int iCode = 0;
  int bMore = 0;
  char *zArg = 0;
  if( p==0 || p->bFatal ) return 1;
  if( socket_open(&p->url) ){
    smtp_set_error(p, 1, "can't open socket: %z", socket_errmsg());
    return 1;
  }
  p->bOpen = 1;
  do{
    smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
  }while( bMore );
  if( iCode!=220 ){
    smtp_set_error(p, 1, "conversation begins with: \"%d %s\"",iCode,zArg);
    smtp_client_quit(p);
    return 1;
  }
  smtp_send_line(p, "EHLO %s\r\n", p->zFrom);
  do{
    smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
  }while( bMore );
  if( iCode!=250 ){
    smtp_set_error(p, 1, "reply to EHLO with: \"%d %s\"",iCode, zArg);
    smtp_client_quit(p);
    return 1;
  }
  fossil_free(p->zErr);
  p->zErr = 0;
  return 0;
}

/*
** COMMAND: test-smtp-probe
**
** Usage: %fossil test-smtp-probe DOMAIN [ME]
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
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







-
+









+
+
+
+




-
+
+
+
+





-
+
+
+
+





-
+
+
+
+













-
+
+
+
+
+







** the message content.
**
** Return 0 on success.  Otherwise an error code.
*/
int smtp_send_msg(
  SmtpSession *p,        /* The SMTP server to which the message is sent */
  const char *zFrom,     /* Who the message is from */
  int nTo,               /* Number of receipients */
  int nTo,               /* Number of recipients */
  const char **azTo,     /* Email address of each recipient */
  const char *zMsg       /* Body of the message */
){
  int i;
  int iCode = 0;
  int bMore = 0;
  char *zArg = 0;
  Blob in;
  blob_init(&in, 0, 0);
  if( !p->bOpen ){
    if( !p->bFatal ) smtp_client_startup(p);
    if( !p->bOpen ) return 1;
  }
  smtp_send_line(p, "MAIL FROM:<%s>\r\n", zFrom);
  do{
    smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
  }while( bMore );
  if( iCode!=250 ) return 1;
  if( iCode!=250 ){
    smtp_set_error(p, 0,"reply to MAIL FROM: \"%d %s\"",iCode,zArg);
    return 1;
  }
  for(i=0; i<nTo; i++){
    smtp_send_line(p, "RCPT TO:<%s>\r\n", azTo[i]);
    do{
      smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
    }while( bMore );
    if( iCode!=250 ) return 1;
    if( iCode!=250 ){
      smtp_set_error(p, 0,"reply to RCPT TO: \"%d %s\"",iCode,zArg);
      return 1;
    }
  }
  smtp_send_line(p, "DATA\r\n");
  do{
    smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
  }while( bMore );
  if( iCode!=354 ) return 1;
  if( iCode!=354 ){
    smtp_set_error(p, 0, "reply to DATA with: \"%d %s\"",iCode,zArg);
    return 1;
  }
  smtp_send_email_body(zMsg, socket_send, 0);
  if( p->smtpFlags & SMTP_TRACE_STDOUT ){
    fossil_print("C: # message content\nC: .\n");
  }
  if( p->smtpFlags & SMTP_TRACE_FILE ){
    fprintf(p->logFile, "C: # message content\nC: .\n");
  }
  if( p->smtpFlags & SMTP_TRACE_BLOB ){
    blob_appendf(p->pTranscript, "C: # message content\nC: .\n");
  }
  do{
    smtp_get_reply_from_server(p, &in, &iCode, &bMore, &zArg);
  }while( bMore );
  if( iCode!=250 ) return 1;
  if( iCode!=250 ){
    smtp_set_error(p, 0, "reply to end-of-DATA with: \"%d %s\"",
                   iCode, zArg);
    return 1;
  }
  return 0;
}

/*
** The input is a base email address of the form "local@domain".
** Return a pointer to just the "domain" part, or 0 if the string
** contains no "@".
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
685
686
687
688
689
690
691

692
693
694
695
696
697
698
699







-








    zToDomain = domain_of_addr(azTo[0]);
  }
  p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort);
  if( p->zErr ){
    fossil_fatal("%s", p->zErr);
  }
  fossil_print("Connection to \"%s\"\n", p->zHostname);
  smtp_client_startup(p);
  smtp_send_msg(p, zFrom, nTo, azTo, blob_str(&body));
  smtp_client_quit(p);
  if( p->zErr ){
    fossil_fatal("ERROR: %s\n", p->zErr);
  }
  smtp_session_free(p);
  blob_reset(&body);
}
Changes to src/sorttable.js.
1
2
3
4
5
6
7
8
9
10
11
12
13
14

15
16
17
18
19
20
21
1
2
3
4
5
6
7
8
9
10
11
12
13

14
15
16
17
18
19
20
21













-
+







/* Javascript code that will enables sorting of the table.  This code is
** derived from
**
**     http://www.webtoolkit.info/sortable-html-table.html
**
** but with extensive modifications.
**
** All tables with class "sortable" are registered with the SortableTable()
** function.  Example:
**
**   <table class='sortable' data-column-types='tnkx' data-init-sort='2'>
**
** Column data types are determined by the data-column-types attribute of
** the table.  The value of data-column-types is a string where each 
** the table.  The value of data-column-types is a string where each
** character of the string represents a datatype for one column in the
** table.
**
**       t      Sort by text
**       n      Sort numerically
**       k      Sort by the data-sortkey property
**       x      This column is not sortable
84
85
86
87
88
89
90


91
92
93
94
95
96
97
98


99
100
101
102
103
104
105
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







+
+








+
+







      var clsName = hdrCell.className.replace(/\s*\bsort\s*\w+/, '');
      clsName += ' sort ' + sortType;
      hdrCell.className = clsName;
    }
  }
  this.sortText = function(a,b) {
    var i = thisObject.sortIndex;
    if (a.cells.length<=i) return -1; /* see ticket 59d699710b1ab5d4 */
    if (b.cells.length<=i) return 1;
    aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
    bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
    if(aa<bb) return -1;
    if(aa==bb) return a.rowIndex-b.rowIndex;
    return 1;
  }
  this.sortReverseText = function(a,b) {
    var i = thisObject.sortIndex;
    if (a.cells.length<=i) return 1; /* see ticket 59d699710b1ab5d4 */
    if (b.cells.length<=i) return -1;
    aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
    bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
    if(aa<bb) return +1;
    if(aa==bb) return a.rowIndex-b.rowIndex;
    return -1;
  }
  this.sortNumeric = function(a,b) {
Changes to src/sqlcmd.c.
166
167
168
169
170
171
172
173

174
175
176
177
178
179
180
166
167
168
169
170
171
172

173
174
175
176
177
178
179
180







-
+







** These invoke the corresponding C routines.
**
** WARNING:
** Do not instantiate these functions for any Fossil webpage or command
** method other than the "fossil sql" command.  If an attacker gains access
** to these functions, he will be able to disable other defense mechanisms.
**
** This routines are for interactiving testing only.  They are experimental
** This routines are for interactive testing only.  They are experimental
** and undocumented (apart from this comments) and might go away or change
** in future releases.
**
** 2020-11-29:  These functions are now only available if the "fossil sql"
** command is started with the --test option.
*/
static void sqlcmd_db_protect(
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
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







+
-
-
+
+
+












+




+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+







  foci_register(db);
  deltafunc_init(db);
  helptext_vtab_register(db);
  builtin_vtab_register(db);
  g.repositoryOpen = 1;
  g.db = db;
  sqlite3_busy_timeout(db, 10000);
  if( g.zRepositoryName ){
  sqlite3_db_config(db, SQLITE_DBCONFIG_MAINDBNAME, "repository");
  db_maybe_set_encryption_key(db, g.zRepositoryName);
    sqlite3_db_config(db, SQLITE_DBCONFIG_MAINDBNAME, "repository");
    db_maybe_set_encryption_key(db, g.zRepositoryName);
  }
  if( g.zLocalDbName ){
    char *zSql = sqlite3_mprintf("ATTACH %Q AS 'localdb' KEY ''",
                                 g.zLocalDbName);
    sqlite3_exec(db, zSql, 0, 0, 0);
    sqlite3_free(zSql);
  }
  if( g.zConfigDbName ){
    char *zSql = sqlite3_mprintf("ATTACH %Q AS 'configdb' KEY ''",
                                 g.zConfigDbName);
    sqlite3_exec(db, zSql, 0, 0, 0);
    sqlite3_free(zSql);
  }
  (void)timeline_query_for_tty();  /* Registers wiki_to_text() as side-effect */
  /* Arrange to trace close operations so that static prepared statements
  ** will get cleaned up when the shell closes the database connection */
  if( g.fSqlTrace ) mTrace |= SQLITE_TRACE_PROFILE;
  sqlite3_trace_v2(db, mTrace, db_sql_trace, 0);
  if( g.zRepositoryName ){
  db_protect_only(PROTECT_NONE);
  sqlite3_set_authorizer(db, db_top_authorizer, db);
  if( local_bSqlCmdTest ){
    sqlite3_create_function(db, "db_protect", 1, SQLITE_UTF8, 0,
                            sqlcmd_db_protect, 0, 0);
    sqlite3_create_function(db, "db_protect_pop", 0, SQLITE_UTF8, 0,
                            sqlcmd_db_protect_pop, 0, 0);
    sqlite3_create_function(db, "shared_secret", 2, SQLITE_UTF8, 0,
                            sha1_shared_secret_sql_function, 0, 0);
    db_protect_only(PROTECT_NONE);
    sqlite3_set_authorizer(db, db_top_authorizer, db);
    if( local_bSqlCmdTest ){
      sqlite3_create_function(db, "db_protect", 1, SQLITE_UTF8, 0,
                              sqlcmd_db_protect, 0, 0);
      sqlite3_create_function(db, "db_protect_pop", 0, SQLITE_UTF8, 0,
                              sqlcmd_db_protect_pop, 0, 0);
      sqlite3_create_function(db, "shared_secret", 2, SQLITE_UTF8, 0,
                              sha1_shared_secret_sql_function, 0, 0);
    }
  }
  return SQLITE_OK;
}

/*
** atexit() handler that cleans up global state modified by this module.
*/
418
419
420
421
422
423
424
425

426
427
428
429
423
424
425
426
427
428
429

430
431
432
433
434







-
+




  fossil_close(1, noRepository);
  sqlite3_shutdown();
#ifndef _WIN32
  linenoiseSetMultiLine(1);
#endif
  atexit(sqlcmd_atexit);
  g.zConfigDbName = zConfigDb;
  g.argv[1] = "-quote";
  g.argv[1] = "--noinit";
  sqlite3_shell(g.argc, g.argv);
  sqlite3_cancel_auto_extension((void(*)(void))sqlcmd_autoinit);
  fossil_close(0, noRepository);
}
Changes to src/stash.c.
257
258
259
260
261
262
263

264
265
266
267
268
269
270
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271







+







*/
static int stash_create(void){
  const char *zComment;              /* Comment to add to the stash */
  int stashid;                       /* ID of the new stash */
  int vid;                           /* Current check-out */

  zComment = find_option("comment", "m", 1);
  (void)fossil_text_editor();
  verify_all_options();
  if( zComment==0 ){
    Blob prompt;                       /* Prompt for stash comment */
    Blob comment;                      /* User comment reply */
#if defined(_WIN32) || defined(__CYGWIN__)
    int bomSize;
    const unsigned char *bom = get_utf8_bom(&bomSize);
506
507
508
509
510
511
512
513
514


515
516
517
518
519
520
521





522
523
524
525
526
527
528
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







-
-
+
+







+
+
+
+
+








/*
** COMMAND: stash
**
** Usage: %fossil stash SUBCOMMAND ARGS...
**
** > fossil stash
** > fossil stash save ?-m|--comment COMMENT? ?FILES...?
** > fossil stash snapshot ?-m|--comment COMMENT? ?FILES...?
** > fossil stash save ?FILES...?
** > fossil stash snapshot ?FILES...?
**
**      Save the current changes in the working tree as a new stash.
**      Then revert the changes back to the last check-in.  If FILES
**      are listed, then only stash and revert the named files.  The
**      "save" verb can be omitted if and only if there are no other
**      arguments.  The "snapshot" verb works the same as "save" but
**      omits the revert, keeping the check-out unchanged.
**
**      Options:
**         --editor NAME                  Use the NAME editor to enter comment
**         -m|--comment COMMENT           Comment text for the new stash
**
**
** > fossil stash list|ls ?-v|--verbose? ?-W|--width NUM?
**
**      List all changes sets currently stashed.  Show information about
**      individual files in each changeset if -v or --verbose is used.
**
** > fossil stash show|cat ?STASHID? ?DIFF-OPTIONS?
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
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







-
+

-
-
-
+
+
+







+
+
+
+







**
** > fossil stash goto ?STASHID?
**
**      Update to the baseline check-out for STASHID then apply the
**      changes of STASHID.  Keep STASHID so that it can be reused
**      This command is undoable.
**
** > fossil stash drop|rm ?STASHID? ?-a|--all?
** > fossil stash drop|rm ?STASHIDs...? ?-a|--all?
**
**      Forget everything about STASHID.  Forget the whole stash if the
**      -a|--all flag is used.  Individual drops are undoable but -a|--all
**      is not.
**      Forget everything about the given STASHIDs.  Forget the whole
**      stash if the -a|--all flag is used.  Individual drops are
**      undoable but -a|--all is not.
**
** > fossil stash diff ?STASHID? ?DIFF-OPTIONS?
** > fossil stash gdiff ?STASHID? ?DIFF-OPTIONS?
**
**      Show diffs of the current working directory and what that
**      directory would be if STASHID were applied. With gdiff,
**      gdiff-command is used instead of internal diff logic.
**
** > fossil stash rename STASHID NEW-NAME
**
**      Change the description of the given STASHID entry to NEW-NAME.
*/
void stash_cmd(void){
  const char *zCmd;
  int nCmd;
  int stashid = 0;
  undo_capture_command_line();
  db_must_be_within_tree();
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
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







-
+








+
+
+
+
+
+
-
+










  ){
    int fBaseline = 0;
    DiffConfig DCfg;

    if( strstr(zCmd,"show")!=0 || strstr(zCmd,"cat")!=0 ){
      fBaseline = 1;
    }
    if( find_option("tk",0,0)!=0 ){
    if( find_option("tk",0,0)!=0 || gdiff_using_tk(zCmd[0]=='g') ){
      db_close(0);
      diff_tk(fBaseline ? "stash show" : "stash diff", 3);
      return;
    }
    diff_options(&DCfg, zCmd[0]=='g', 0);
    stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
    stash_diff(stashid, fBaseline, &DCfg);
  }else
  if( strncmp(zCmd, "rename", nCmd)==0 ){
    if( g.argc!=5 ) usage("rename STASHID NAME");
    stashid = stash_get_id(g.argv[3]);
    db_multi_exec("UPDATE STASH SET COMMENT=%Q WHERE stashid=%d",
                  g.argv[4], stashid);
  }
  if( strncmp(zCmd, "help", nCmd)==0 ){
  else if( strncmp(zCmd, "help", nCmd)==0 ){
    g.argv[1] = "help";
    g.argv[2] = "stash";
    g.argc = 3;
    help_cmd();
  }else
  {
    usage("SUBCOMMAND ARGS...");
  }
  db_end_transaction(0);
}
Changes to src/stat.c.
92
93
94
95
96
97
98
99

100
101
102
103
104
105
106
92
93
94
95
96
97
98

99
100
101
102
103
104
105
106







-
+







    }
    sqlite3_close(db);
  }else
  if( fossil_strcmp(zDest,"dir")==0
   && (zDir = db_get("email-send-dir",0))!=0
  ){
    @ Written to files in "%h(zDir)"
    @ (%,d(file_directory_size(zDir,0,1)) messages)
    @ (%,d(file_directory_list(zDir,0,1,0,0)) messages)
  }else
  if( fossil_strcmp(zDest,"relay")==0
   && (zRelay = db_get("email-send-relayhost",0))!=0
  ){
    @ Relay to %h(zRelay) using SMTP
  }
  else{
164
165
166
167
168
169
170
171

172
173
174
175
176
177
178
164
165
166
167
168
169
170

171
172
173
174
175
176
177
178







-
+







  style_submenu_element("Activity Reports", "reports");
  style_submenu_element("Hash Collisions", "hash-collisions");
  style_submenu_element("Artifacts", "bloblist");
  if( sqlite3_compileoption_used("ENABLE_DBSTAT_VTAB") ){
    style_submenu_element("Table Sizes", "repo-tabsize");
  }
  if( g.perm.Admin || g.perm.Setup || db_get_boolean("test_env_enable",0) ){
    style_submenu_element("Environment", "test_env");
    style_submenu_element("Environment", "test-env");
  }
  @ <table class="label-value">
  fsize = file_size(g.zRepositoryName, ExtFILE);
  @ <tr><th>Repository&nbsp;Size:</th><td>%,lld(fsize) bytes</td>
  @ </td></tr>
  if( !brief ){
    @ <tr><th>Number&nbsp;Of&nbsp;Artifacts:</th><td>
266
267
268
269
270
271
272
273

274
275
276

277

278


279
280
281
282
283
284
285
266
267
268
269
270
271
272

273
274
275

276
277
278

279
280
281
282
283
284
285
286
287







-
+


-
+

+
-
+
+







      }
    }
  }
  @ <tr><th>Project&nbsp;Age:</th><td>
  z = db_text(0, "SELECT timediff('now',(SELECT min(mtime) FROM event));");
  sscanf(z, "+%d-%d-%d", &Y, &M, &D);
  if( Y>0 ){
    @ %d(Y) years, \
    @ %d(Y) year%s(Y==1?"":"s") \
  }
  if( M>0 ){
    @ %d(M) months, \
    @ %d(M) month%s(M==1?"":"s") \
  }
  if( D>0 || (Y==0 && M==0) ){
  @ %d(D) days
    @ %d(D) day%s(D==1?"":"s")
  }
  @ </td></tr>
  p = db_get("project-code", 0);
  if( p ){
    @ <tr><th>Project&nbsp;ID:</th>
    @     <td>%h(p) %h(db_get("project-name",""))</td></tr>
  }
  p = db_get("parent-project-code", 0);
499
500
501
502
503
504
505
506

507
508
509
510
511
512
513
501
502
503
504
505
506
507

508
509
510
511
512
513
514
515







-
+







** Return a string which is the public URL used to access this repository.
** Or return a NULL pointer if this repository does not have a public
** access URL.
**
** Algorithm:
**
** The public URL is given by the email-url property.  But it is only
** returned if there have been one more more accesses (as recorded by
** returned if there have been one or more accesses (as recorded by
** "baseurl:URL" entries in the CONFIG table).
*/
const char *public_url(void){
  const char *zUrl = db_get("email-url", 0);
  if( zUrl==0 ) return 0;
  if( !db_exists("SELECT 1 FROM config WHERE name='baseurl:%q'", zUrl) ){
    return 0;
Changes to src/statrep.c.
288
289
290
291
292
293
294
295
296


297
298
299
300
301
302
303
288
289
290
291
292
293
294


295
296
297
298
299
300
301
302
303







-
-
+
+







   rowClass = ++nRowNumber % 2;
   nEventTotal += nCount;
   nEventsPerYear += nCount;
   @<tr class='row%d(rowClass)'>
   @ <td>
    if(includeMonth){
      cgi_printf("<a href='%R/timeline?"
                 "ym=%t&n=%d&y=%s",
                 zTimeframe, nCount,
                 "ym=%t&y=%s",
                 zTimeframe,
                 statsReportTimelineYFlag );
      /* Reminder: n=nCount is not actually correct for bymonth unless
         that was the only user who caused events.
      */
      if( zUserName ){
        cgi_printf("&u=%t", zUserName);
      }
316
317
318
319
320
321
322

323
324
325
326
327

328
329
330
331
332
333
334
316
317
318
319
320
321
322
323
324
325
326
327

328
329
330
331
332
333
334
335







+




-
+







     && rNowFraction>0.05
     && nCount>0
     && nMaxEvents>0
    ){
      /* If the timespan covered by this row contains "now", then project
      ** the number of changes until the completion of the timespan and
      ** show a dashed box of that projection. */
      int nProj = (int)(((double)nCount)/rNowFraction);
      int nExtra = (int)(((double)nCount)/rNowFraction) - nCount;
      int nXSize = (100 * nExtra)/nMaxEvents;
      @ <span class='statistics-report-graph-line' \
      @  style='display:inline-block;min-width:%d(nSize)%%;'>&nbsp;</span>\
      @ <span class='statistics-report-graph-extra' \
      @ <span class='statistics-report-graph-extra' title='%d(nProj)' \
      @  style='display:inline-block;min-width:%d(nXSize)%%;'>&nbsp;</span>\
    }else{
      @ <div class='statistics-report-graph-line' \
      @  style='width:%d(nSize)%%;'>&nbsp;</div> \
    }
    @ </td>
    @ </tr>
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
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







-
-
+
+














-
+


+




-
+







    const int nCount = db_column_int(&q,1);
    int nSize = (nCount>0 && nMaxEvents>0)
      ? (int)(100 * nCount / nMaxEvents)
      : 0;
    if(!nSize) nSize = 1;
    total += nCount;
    cgi_printf("<tr class='row%d'>", ++rowCount % 2 );
    cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s",
               zYear, zWeek, nCount,
    cgi_printf("<td><a href='%R/timeline?yw=%t%s&y=%s",
               zYear, zWeek,
               statsReportTimelineYFlag);
    if( zUserName ){
      cgi_printf("&u=%t",zUserName);
    }
    cgi_printf("'>%s</a></td>",zWeek);

    cgi_printf("<td>%d</td>",nCount);
    cgi_printf("<td style='white-space: nowrap;'>");
    if( nCount ){
      if( zCurrentWeek!=0
      && strcmp(zWeek, zCurrentWeek)==0
      && rNowFraction>0.05
      && nMaxEvents>0
      ){
        /* If the covered covered by this row contains "now", then project
        /* If the timespan covered by this row contains "now", then project
        ** the number of changes until the completion of the week and
        ** show a dashed box of that projection. */
        int nProj = (int)(((double)nCount)/rNowFraction);
        int nExtra = (int)(((double)nCount)/rNowFraction) - nCount;
        int nXSize = (100 * nExtra)/nMaxEvents;
        @ <span class='statistics-report-graph-line' \
        @  style='display:inline-block;min-width:%d(nSize)%%;'>&nbsp;</span>\
        @ <span class='statistics-report-graph-extra' \
        @ <span class='statistics-report-graph-extra' title='%d(nProj)' \
        @  style='display:inline-block;min-width:%d(nXSize)%%;'>&nbsp;</span>\
      }else{
        @ <div class='statistics-report-graph-line' \
        @  style='width:%d(nSize)%%;'>&nbsp;</div> \
      }
    }
    cgi_printf("</td></tr>\n");
855
856
857
858
859
860
861



862
863
864
865
866
867
868
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873







+
+
+







**                        * m   (merge check-in),
**                        * n   (non-merge check-in)
**                        * f   (forum post)
**                        * w   (wiki page change)
**                        * t   (ticket change)
**                        * g   (tag added or removed)
**                     Defaulting to all event types.
**   from=DATETIME     Consider only events after this timestamp (requires to)
**   to=DATETIME       Consider only events before this timestamp (requires from)
**
**
** The view-specific query parameters include:
**
** view=byweek:
**
**   y=YYYY            The year to report (default is the server's
**                     current year).
Changes to src/style.c.
224
225
226
227
228
229
230
231

232
233
234
235
236
237
238
224
225
226
227
228
229
230

231
232
233
234
235
236
237
238







-
+







** Generate <form method="post" action=ARG>.  The ARG value is determined
** by the arguments.
**
** As a defense against robots, the action=ARG might instead by data-action=ARG
** and javascript (href.js) added to the page so that the data-action= is
** changed into action= after the page loads.  Whether or not this happens
** depends on if the user has the "h" privilege and whether or not the
** auto-hyperlink setting is on.  These setings determine the values of
** auto-hyperlink setting is on.  These settings determine the values of
** variables g.perm.Hyperlink and g.jsHref.
**
**    User has "h"  auto-hyperlink      g.perm.Hyperlink  g.jsHref
**    ------------  --------------      ----------------  --------
**  1:      0             0                    0             0
**  2:      1             0                    1             0
**  3:      0             1                    1             1
476
477
478
479
480
481
482
483

484
485
486
487
488
489
490
476
477
478
479
480
481
482

483
484
485
486
487
488
489
490







-
+







}

/*
** Output TEXT with a click-to-copy button next to it. Loads the copybtn.js
** Javascript module, and generates HTML elements with the following IDs:
**
**    TARGETID:       The <span> wrapper around TEXT.
**    copy-TARGETID:  The <span> for the copy button.
**    copy-TARGETID:  The <button> for the copy button.
**
** If the FLIPPED argument is non-zero, the copy button is displayed after TEXT.
**
** The COPYLENGTH argument defines the length of the substring of TEXT copied to
** clipboard:
**
**    <= 0:   No limit (default if the argument is omitted).
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
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







-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
+















-
+

-
-
-
-
-
-
+
+
+
+
+
+
+
+







  zText = vmprintf(zTextFmt/*works-like:?*/,ap);
  va_end(ap);
  if( cchLength==1 ) cchLength = hash_digits(0);
  else if( cchLength==2 ) cchLength = hash_digits(1);
  if( !bFlipped ){
    const char *zBtnFmt =
      "<span class=\"nobr\">"
      "<span "
      "class=\"copy-button\" "
      "id=\"copy-%h\" "
      "data-copytarget=\"%h\" "
      "data-copylength=\"%d\">"
      "</span>"
      "<button "
          "class=\"copy-button\" "
          "id=\"copy-%h\" "
          "data-copytarget=\"%h\" "
          "data-copylength=\"%d\">"
        "<span>"
        "</span>"
      "</button>"
      "<span id=\"%h\">"
      "%s"
        "%s"
      "</span>"
      "</span>";
    if( bOutputCGI ){
      cgi_printf(
                  zBtnFmt/*works-like:"%h%h%d%h%s"*/,
                  zTargetId,zTargetId,cchLength,zTargetId,zText);
    }else{
      zResult = mprintf(
                  zBtnFmt/*works-like:"%h%h%d%h%s"*/,
                  zTargetId,zTargetId,cchLength,zTargetId,zText);
    }
  }else{
    const char *zBtnFmt =
      "<span class=\"nobr\">"
      "<span id=\"%h\">"
      "%s"
        "%s"
      "</span>"
      "<span "
      "class=\"copy-button copy-button-flipped\" "
      "id=\"copy-%h\" "
      "data-copytarget=\"%h\" "
      "data-copylength=\"%d\">"
      "</span>"
      "<button "
          "class=\"copy-button copy-button-flipped\" "
          "id=\"copy-%h\" "
          "data-copytarget=\"%h\" "
          "data-copylength=\"%d\">"
        "<span>"
        "</span>"
      "</button>"
      "</span>";
    if( bOutputCGI ){
      cgi_printf(
                  zBtnFmt/*works-like:"%h%s%h%h%d"*/,
                  zTargetId,zText,zTargetId,zTargetId,cchLength);
    }else{
      zResult = mprintf(
742
743
744
745
746
747
748

749
750
751



752
753
754
755
756
757
758
746
747
748
749
750
751
752
753



754
755
756
757
758
759
760
761
762
763







+
-
-
-
+
+
+







  ** Do not overwrite the TH1 variable "default_csp" if it exists, as this
  ** allows it to be properly overridden via the TH1 setup script (i.e. it
  ** is evaluated before the header is rendered).
  */
  Th_MaybeStore("default_csp", zDfltCsp);
  fossil_free(zDfltCsp);
  Th_Store("nonce", zNonce);
  Th_StoreUnsafe("project_name",
  Th_Store("project_name", db_get("project-name","Unnamed Fossil Project"));
  Th_Store("project_description", db_get("project-description",""));
  if( zTitle ) Th_Store("title", zTitle);
                 db_get("project-name","Unnamed Fossil Project"));
  Th_StoreUnsafe("project_description", db_get("project-description",""));
  if( zTitle ) Th_Store("title", html_lookalike(zTitle,-1));
  Th_Store("baseurl", g.zBaseURL);
  Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
  Th_Store("home", g.zTop);
  Th_Store("index_page", db_get("index-page","/home"));
  if( local_zCurrentPage==0 ) style_set_current_page("%T", g.zPath);
  Th_Store("current_page", local_zCurrentPage);
  if( g.zPath ){                /* store the first segment of a path; */
770
771
772
773
774
775
776
777

778
779
780
781
782
783
784
775
776
777
778
779
780
781

782
783
784
785
786
787
788
789







-
+







  Th_Store("manifest_date", MANIFEST_DATE);
  Th_Store("compiler_name", COMPILER_NAME);
  Th_Store("mainmenu", style_get_mainmenu());
  stylesheet_url_var();
  image_url_var("logo");
  image_url_var("background");
  if( !login_is_nobody() ){
    Th_Store("login", g.zLogin);
    Th_Store("login", html_lookalike(g.zLogin,-1));
  }
  Th_MaybeStore("current_feature", feature_from_page_path(local_zCurrentPage) );
  if( g.ftntsIssues[0] || g.ftntsIssues[1] ||
      g.ftntsIssues[2] || g.ftntsIssues[3] ){
    char buf[80];
    sqlite3_snprintf(sizeof(buf), buf, "%i %i %i %i", g.ftntsIssues[0],
                     g.ftntsIssues[1], g.ftntsIssues[2], g.ftntsIssues[3]);
819
820
821
822
823
824
825

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







+







  headerHasBeenGenerated = 1;
  sideboxUsed = 0;
  if( g.perm.Debug && P("showqp") ){
    @ <div class="debug">
    cgi_print_all(0, 0, 0);
    @ </div>
  }
  fossil_free(zTitle);
}

#if INTERFACE
/* Allowed parameters for style_adunit() */
#define ADUNIT_OFF        0x0001       /* Do not allow ads on this page */
#define ADUNIT_RIGHT_OK   0x0002       /* Right-side vertical ads ok here */
#endif
905
906
907
908
909
910
911
912

913
914
915

916
917
918
919
920
921
922
911
912
913
914
915
916
917

918
919
920

921
922
923
924
925
926
927
928







-
+


-
+







    cgi_append_content("\n}\n", -1);
  }
  @ </script>
  builtin_fulfill_js_requests();
}

/*
** Transorm input string into a token that is safe for inclusion into
** Transform input string into a token that is safe for inclusion into
** class attribute. Digits and low-case letter are passed unchanged,
** upper-case letters are transformed to low-case, everything else is
** tranformed into hyphens; consequtive and pending hyphens are squeezed.
** transformed into hyphens; consecutive and pending hyphens are squeezed.
** If result does not fit into szOut chars then it is truncated.
** Result is always terminated with null.
*/
void style_derive_classname(const char *zIn, char *zOut, int szOut){
  assert(  zOut );
  assert( szOut>0 );
  if( zIn ){
1211
1212
1213
1214
1215
1216
1217
1218

1219
1220
1221
1222
1223
1224
1225
1217
1218
1219
1220
1221
1222
1223

1224
1225
1226
1227
1228
1229
1230
1231







-
+







  Th_Render(zScript?zScript:"");
}

/*
** Check for "name" or "page" query parameters on an /style.css
** page request.  If present, then page-specific CSS is requested,
** so add that CSS to pOut.  If the "name" and "page" query parameters
** are omitted, then pOut is unchnaged.
** are omitted, then pOut is unchanged.
*/
static void page_style_css_append_page_style(Blob *pOut){
  const char *zPage = PD("name",P("page"));
  char * zFile;
  int nFile = 0;
  const char *zBuiltin;

1244
1245
1246
1247
1248
1249
1250
1251

1252
1253
1254
1255
1256
1257
1258
1250
1251
1252
1253
1254
1255
1256

1257
1258
1259
1260
1261
1262
1263
1264







-
+







  ** default.css. */
  fossil_free(zFile);
}

/*
** WEBPAGE: style.css loadavg-exempt
**
** Return the style sheet.   The style sheet is assemblied from
** Return the style sheet.   The style sheet is assembled from
** multiple sources, in order:
**
**    (1)   The built-in "default.css" style sheet containing basic defaults.
**
**    (2)   The page-specific style sheet taken from the built-in
**          called "PAGENAME.css" where PAGENAME is the value of the name=
**          or page= query parameters.  If neither name= nor page= exist,
1298
1299
1300
1301
1302
1303
1304

1305
1306
1307
1308
1309
1310
1311
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318







+







  */
  Th_Store("baseurl", g.zBaseURL);
  Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
  Th_Store("home", g.zTop);
  image_url_var("logo");
  image_url_var("background");
  Th_Render(blob_str(&css));
  blob_reset(&css);

  /* Tell CGI that the content returned by this page is considered cacheable */
  g.isConst = 1;
}

/*
** All possible capabilities
1338
1339
1340
1341
1342
1343
1344


































1345

1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
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
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408

1409
1410
1411
1412
1413
1414
1415
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
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
1398
1399
1400
1401
1402
1403

1404
1405
1406
1407
1408
1409
1410
1411







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+








-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-









-
+







      && !login_has_capability(&c, 1, 0) ) zCap[i++] = c;
  }
  zCap[i] = 0;
  return zCap;
}

/*
** WEBPAGE: test-title
**
** Render a test page in which the page title is set by the "title"
** query parameter.  This can be used to show that HTML or Javascript
** content in the title does not leak through into generated page, resulting
** in an XSS issue.
**
** Due to the potential for abuse, this webpage is only available to
** administrators.
*/
void page_test_title(void){
  const char *zTitle;
  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed(0);
  }
  zTitle = P("title");
  if( zTitle==0 ){
    zTitle = "(No Title)";
  }
  style_header("%s", zTitle);
  @ <p>
  @ This page sets its title to the value of the "title" query parameter.
  @ The form below is a convenient way to set the title query parameter:
  @
  @ <form method="GET">
  @ Title: <input type="text" size="50" name="title" value="%h(zTitle)">
  @ <input type="submit" value="Submit">
  @ </form>
  style_finish_page();
}

/*
** WEBPAGE: test-env
** WEBPAGE: test_env
** WEBPAGE: test_env  alias
**
** Display CGI-variables and other aspects of the run-time
** environment, for debugging and trouble-shooting purposes.
*/
void page_test_env(void){
  webpage_error("");
}

/*
** WEBPAGE: honeypot
** This page is a honeypot for spiders and bots.
*/
void honeypot_page(void){
  unsigned int uSeed = captcha_seed();
  const char *zDecoded = captcha_decode(uSeed, 0);
  int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
  char *zCaptcha = captcha_render(zDecoded);
  style_header("I think you are a robot");
  @ <p>You seem like a robot.</p>
  @
  @ <p>Is that incorrect?  Are you really human?
  @ If so, please prove it by transcribing the captcha text
  @ into the entry box below and pressing "Submit".
  @ <form action="%R/login" method="post">
  @ <input type="hidden" id="u" name="u" value="anonymous">
  @ <p>
  @ Captcha: <input type="text" id="p" name="p" value="">
  @ <input type="submit" name="in" value="Submit">
  @ 
  @ <p>Alternatively, you can <a href="%R/login">log in</a> using an
  @ existing userid.
  @
  @ <p><input type="hidden" name="cs" value="%u(uSeed)">
  @ <div class="captcha"><table class="captcha"><tr><td>\
  @ <pre class="captcha">
  @ %h(zCaptcha)
  @ </pre></td></tr></table>
  if( bAutoCaptcha ) {
     @ <input type="button" value="Fill out captcha" id='autofillButton' \
     @ data-af='%s(zDecoded)'>
     builtin_request_js("login.js");
  }
  @ </div>
  free(zCaptcha);
  @
  @ <p>We regret this inconvenience. However, robots have become so
  @ prolific and so aggressive that they will soak up too much CPU time
  @ and network bandwidth on our servers if allowed to run unchecked.
  @ Your cooperation in demonstrating that you are human is
  @ appreciated.
  style_finish_page();
}

/*
** Webpages that encounter an error due to missing or incorrect
** query parameters can jump to this routine to render an error
** message screen.
**
** For administators, or if the test_env_enable setting is true, then
** details of the request environment are displayed.  Otherwise, just
** the error message is shown.
**
** If zFormat is an empty string, then this is the /test_env page.
** If zFormat is an empty string, then this is the /test-env page.
*/
void webpage_error(const char *zFormat, ...){
  int showAll = 0;
  char *zErr = 0;
  int isAuth = 0;
  char zCap[100];

1444
1445
1446
1447
1448
1449
1450





1451

1452
1453
1454
1455
1456
1457
1458
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451

1452
1453
1454
1455
1456
1457
1458
1459







+
+
+
+
+
-
+







  #endif
    @ g.zBaseURL = %h(g.zBaseURL)<br>
    @ g.zHttpsURL = %h(g.zHttpsURL)<br>
    @ g.zTop = %h(g.zTop)<br>
    @ g.zPath = %h(g.zPath)<br>
    @ g.userUid = %d(g.userUid)<br>
    @ g.zLogin = %h(g.zLogin)<br>
    if( g.eAuthMethod!=AUTH_NONE ){
      const char *zMethod[] = { "COOKIE", "LOCAL", "PW", "ENV", "HTTP" };
      @ g.eAuthMethod = %d(g.eAuthMethod) (%h(zMethod[g.eAuthMethod-1]))\
      @ <br>
    }
    @ g.isHuman = %d(g.isHuman)<br>
    @ g.isRobot = %d(g.isRobot)<br>
    @ g.jsHref = %d(g.jsHref)<br>
    if( g.zLocalRoot ){
      @ g.zLocalRoot = %h(g.zLocalRoot)<br>
    }else{
      @ g.zLocalRoot = <i>none</i><br>
    }
    if( g.nRequest ){
1501
1502
1503
1504
1505
1506
1507
1508

1509
1510
1511
1512
1513
1514
1515
1502
1503
1504
1505
1506
1507
1508

1509
1510
1511
1512
1513
1514
1515
1516







-
+







        blob_zero(&t);
      }
    }
    @ <hr>
    P("HTTP_USER_AGENT");
    P("SERVER_SOFTWARE");
    cgi_print_all(showAll, 0, 0);
    @ <p><form method="POST" action="%R/test_env">
    @ <p><form method="POST" action="%R/test-env">
    @ <input type="hidden" name="showall" value="%d(showAll)">
    @ <input type="submit" name="post-test-button" value="POST Test">
    @ </form>
    if( showAll && blob_size(&g.httpHeader)>0 ){
      @ <hr>
      @ <pre>
      @ %h(blob_str(&g.httpHeader))
Changes to src/style.chat.css.
211
212
213
214
215
216
217









218
219
220
221
222
223
224
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233







+
+
+
+
+
+
+
+
+







  overflow: auto;
  padding: 0 0.25em;
}
body.chat #chat-messages-wrapper.loading > * {
  /* An attempt at reducing flicker when loading lots of messages. */
  visibility: hidden;
}

/* Provide a visual cue when polling is offline. */
body.chat.connection-error #chat-input-line-wrapper {
  border-top: medium dotted red;
}
body.chat.fossil-dark-style.connection-error #chat-input-line-wrapper {
  border-color: yellow;
}

body.chat div.content {
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column-reverse;
  /* ^^^^ In order to get good automatic scrolling of new messages on
     the BOTTOM in bottom-up chat mode, such that they scroll up
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
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







+
-
+



-
-
+
+



-
+










-
+



-
+




-
+







}
body.chat:not(.chat-only-mode) #chat-input-area{
  /* Safari user reports that 2em is necessary to keep the file selection
     widget from overlapping the page footer, whereas a margin of 0 is fine
     for FF/Chrome (and 2em is a *huge* waste of space for those). */
  margin-bottom: 0;
}

.chat-input-field {
body.chat .chat-input-field {
  flex: 10 1 auto;
  margin: 0;
}
#chat-input-field-x,
#chat-input-field-multi {
body.chat #chat-input-field-x,
body.chat #chat-input-field-multi {
  overflow: auto;
  resize: vertical;
}
#chat-input-field-x {
body.chat #chat-input-field-x {
  display: inline-block/*supposed workaround for Chrome weirdness*/;
  padding: 0.2em;
  background-color: rgba(156,156,156,0.3);
  white-space: pre-wrap;
  /* ^^^ Firefox, when pasting plain text into a contenteditable field,
     loses all newlines unless we explicitly set this. Chrome does not. */
  cursor: text;
  /* ^^^ In some browsers the cursor may not change for a contenteditable
     element until it has focus, causing potential confusion. */
}
#chat-input-field-x:empty::before {
body.chat #chat-input-field-x:empty::before {
  content: attr(data-placeholder);
  opacity: 0.6;
}
.chat-input-field:not(:focus){
body.chat .chat-input-field:not(:focus){
  border-width: 1px;
  border-style: solid;
  border-radius: 0.25em;
}
.chat-input-field:focus{
body.chat .chat-input-field:focus{
  /* This transparent border helps avoid the text shifting around
     when the contenteditable attribute causes a border (which we
     apparently cannot style) to be added. */
  border-width: 1px;
  border-style: solid;
  border-color: transparent;
  border-radius: 0.25em;
450
451
452
453
454
455
456
457


458
459
460
461
462
463
464
465
466





















467
468
469
470
471
472
473
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







-
+
+









+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  flex: 20 1 auto
  /*ensure that these grow more than the non-.chat-view elements.
    Note that setting flex shrink to 0 breaks/disables scrolling!*/;
  margin-bottom: 0.2em;
}
body.chat #chat-config,
body.chat #chat-search,
body.chat #chat-preview {
body.chat #chat-preview,
body.chat #chat-zoom {
  /* /chat configuration widget */
  display: flex;
  flex-direction: column;
  overflow: auto;
  padding: 0;
  margin: 0;
  align-items: stretch;
  min-height: 6em;
}
body.chat #chat-zoom {
  justify-content: space-between;
}
body.chat #chat-zoom-content {
  display: flex;
  overflow: auto;
}
body.chat #chat-zoom-content > .message-widget {
  flex-grow: 1;
}
body.chat #chat-zoom-content > .message-widget > .message-widget-content {
  width: 99%;
}
body.chat #chat-zoom-content > .message-widget .toolbar.hide-in-zoom {
  /* The various Delete buttons misinteract with zoom mode's moving-around
     of message widgets, so hide them in zoom mode. */
  position: absolute !important;
  opacity: 0 !important;
  pointer-events: none !important;
  display: none !important;
}
body.chat #chat-config #chat-config-options {
  /* /chat config options go here */
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  overflow: auto;
  align-items: stretch;
Changes to src/sync.c.
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
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







-
+












+
+







#include "sync.h"
#include <assert.h>

/*
** Explain what type of sync operation is about to occur
*/
static void sync_explain(unsigned syncFlags){
  if( g.url.isAlias ){
  if( g.url.isAlias && (syncFlags & SYNC_QUIET)==0 ){
    const char *url;
    if( g.url.useProxy ){
      url = g.url.proxyUrlCanonical;
    }else{
      url = g.url.canonical;
    }
    if( (syncFlags & (SYNC_PUSH|SYNC_PULL))==(SYNC_PUSH|SYNC_PULL) ){
      fossil_print("Sync with %s\n", url);
    }else if( syncFlags & SYNC_PUSH ){
      fossil_print("Push to %s\n", url);
    }else if( syncFlags & SYNC_PULL ){
      fossil_print("Pull from %s\n", url);
    }else if( syncFlags & SYNC_PING ){
      fossil_print("Ping %s\n", url);
    }
  }
}


/*
** Call client_sync() one or more times in order to complete a
305
306
307
308
309
310
311
312

313
314
315
316
317
318
319
307
308
309
310
311
312
313

314
315
316
317
318
319
320
321







-
+







  **       COMMAND URL PAYLOAD REPLY
  **
  ** URL is the server name.  PAYLOAD is the name of a temporary file
  ** that will contain the xfer-protocol payload to send to the server.
  ** REPLY is a temporary filename in which COMMAND should write the
  ** content of the reply from the server.
  **
  ** CMD is reponsible for HTTP redirects.  The following Fossil command
  ** CMD is responsible for HTTP redirects.  The following Fossil command
  ** can be used for CMD to achieve a working sync:
  **
  **      fossil test-httpmsg --xfer
  */
  g.zHttpCmd = find_option("transport-command",0,1);

  url_proxy_options();
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
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







+


+


















+
+
+
+
+
+






+
-
-
-
+
+
+
+







** Options:
**   --all                      Sync with all remotes, not just the default
**   -B|--httpauth USER:PASS    Credentials for the simple HTTP auth protocol,
**                              if required by the remote website
**   --ipv4                     Use only IPv4, not IPv6
**   --no-http-compression      Do not compress HTTP traffic
**   --once                     Do not remember URL for subsequent syncs
**   --ping                     Just verify that the server is alive
**   --proxy PROXY              Use the specified HTTP proxy
**   --private                  Sync private branches too
**   -q|--quiet                 Omit all output
**   -R|--repository REPO       Local repository to sync with
**   --ssl-identity FILE        Local SSL credentials, if requested by remote
**   --ssh-command SSH          Use SSH as the "ssh" command
**   --transport-command CMD    Use external command CMD to move message
**                              between the client and the server
**   -u|--unversioned           Also sync unversioned content
**   -v|--verbose               Additional (debugging) output - use twice to
**                              get network debug info
**   --verily                   Exchange extra information with the remote
**                              to ensure no content is overlooked
**
** See also: [[clone]], [[pull]], [[push]], [[remote]]
*/
void sync_cmd(void){
  unsigned configFlags = 0;
  unsigned syncFlags = SYNC_PUSH|SYNC_PULL;
  if( find_option("unversioned","u",0)!=0 ){
    syncFlags |= SYNC_UNVERSIONED;
  }
  if( find_option("ping",0,0)!=0 ){
    syncFlags = SYNC_PING;
  }
  if( g.fQuiet ){
    syncFlags |= SYNC_QUIET;
  }
  process_sync_args(&configFlags, &syncFlags, 0, 0);

  /* We should be done with options.. */
  verify_all_options();

  if( (syncFlags & SYNC_PING)==0 ){
  if( db_get_boolean("dont-push",0) ) syncFlags &= ~SYNC_PUSH;
  if( (syncFlags & SYNC_PUSH)==0 ){
    fossil_warning("pull only: the 'dont-push' option is set");
    if( db_get_boolean("dont-push",0) ) syncFlags &= ~SYNC_PUSH;
    if( (syncFlags & SYNC_PUSH)==0 ){
      fossil_warning("pull only: the 'dont-push' option is set");
    }
  }
  client_sync_all_urls(syncFlags, configFlags, 0, 0);
}

/*
** Handle the "fossil unversioned sync" and "fossil unversioned revert"
** commands.
Changes to src/tag.c.
216
217
218
219
220
221
222
223
224


225
226
227
228
229
230
231
216
217
218
219
220
221
222


223
224
225
226
227
228
229
230
231







-
-
+
+







        rid
      );
    }
  }
  if( zCol ){
    db_multi_exec("UPDATE event SET \"%w\"=%Q WHERE objid=%d",
                  zCol, zValue, rid);
    if( tagid==TAG_COMMENT ){
      char *zCopy = mprintf("%s", zValue);
    if( tagid==TAG_COMMENT && zValue!=0 ){
      char *zCopy = fossil_strdup(zValue);
      backlink_extract(zCopy, MT_NONE, rid, BKLNK_COMMENT, mtime, 1);
      free(zCopy);
    }
  }
  if( tagid==TAG_DATE ){
    db_multi_exec("UPDATE event "
                  "   SET mtime=julianday(%Q),"
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
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







-
+









-
+







**           --propagate                Propagating tag
**           --raw                      Raw tag name. Ignored for
**                                      non-CHECK-IN artifacts.
**           --user-override USER       Name USER when adding the tag
**
**         The --date-override and --user-override options support
**         importing history from other SCM systems. DATETIME has
**         the form 'YYYY-MMM-DD HH:MM:SS'.
**         the form 'YYYY-MM-DD HH:MM:SS'.
**
**         Note that fossil uses some tag prefixes internally and this
**         command will reject tags with these prefixes to avoid
**         causing problems or confusion: "wiki-", "tkt-", "event-".
**
** > fossil tag cancel ?--raw? TAGNAME ARTIFACT-ID
**
**         Remove the tag TAGNAME from the artifact referenced by
**         ARTIFACT-ID, and also remove the propagation of the tag to
**         any descendants.  Use the the -n|--dry-run option to see
**         any descendants.  Use the -n|--dry-run option to see
**         what would have happened. Certain tag name prefixes are
**         forbidden, as documented for the 'add' subcommand.
**
**         Options:
**           --date-override DATETIME    Set date and time deleted
**           -n|--dry-run                Display the control artifact, but do
**                                       not insert it into the database
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
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







+




+






-
+











-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-







  if( strncmp(g.argv[2],"find",n)==0 ){
    Stmt q;
    int fRaw = find_option("raw","",0)!=0;
    const char *zFindLimit = find_option("limit","n",1);
    const int nFindLimit = zFindLimit ? atoi(zFindLimit) : -2000;
    const char *zType = find_option("type","t",1);
    Blob sql = empty_blob;
    const char *zTag;
    if( zType==0 || zType[0]==0 ) zType = "*";
    if( g.argc!=4 ){
      usage("find ?--raw? ?-t|--type TYPE? ?-n|--limit #? TAGNAME");
    }
    zTag = g.argv[3];
    if( fRaw ){
      blob_append_sql(&sql,
        "SELECT blob.uuid FROM tagxref, blob"
        " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
        "   AND tagxref.tagtype>0"
        "   AND blob.rid=tagxref.rid",
        g.argv[3]
        zTag
      );
      if( nFindLimit>0 ){
        blob_append_sql(&sql, " LIMIT %d", nFindLimit);
      }
      db_prepare(&q, "%s", blob_sql_text(&sql));
      blob_reset(&sql);
      while( db_step(&q)==SQLITE_ROW ){
        fossil_print("%s\n", db_column_text(&q, 0));
      }
      db_finalize(&q);
    }else{
      int tagid = db_int(0, "SELECT tagid FROM tag "
                         "WHERE tagname='%s%q'",
                         (zType && 'c'==zType[0])
                         ? "sym-" : ""/*safe-for-%s*/,
                         g.argv[3]);
      if( tagid>0 ){
        blob_append_sql(&sql,
          "%s"
          "  AND event.type GLOB '%q'"
          "  AND blob.rid IN ("
                    " SELECT rid FROM tagxref"
                    "  WHERE tagtype>0 AND tagid=%d"
                    ")"
          " ORDER BY event.mtime DESC /*sort*/",
          timeline_query_for_tty(), zType, tagid
        );
        db_prepare(&q, "%s", blob_sql_text(&sql));
        blob_reset(&sql);
        print_timeline(&q, nFindLimit, 79, 0, 0);
        db_finalize(&q);
      blob_append_sql(&sql,
        "%s"
        "  AND event.type GLOB '%q'"
        "  AND blob.rid IN ("
                  " SELECT rid FROM tagxref"
                  "  WHERE tagtype>0 AND tagid IN ("
                  "    SELECT tagid FROM tag WHERE tagname IN "
                  "    ('%q','sym-%q','wiki-%q','tkt-%q','event-%q')"
                  "  )"
                  ")"
        " ORDER BY event.mtime DESC /*sort*/",
        timeline_query_for_tty(), zType, zTag, zTag, zTag, zTag, zTag
      );
      db_prepare(&q, "%s", blob_sql_text(&sql));
      blob_reset(&sql);
      print_timeline(&q, nFindLimit, 79, 0, 0);
      db_finalize(&q);
      }
    }
  }else

  if(( strncmp(g.argv[2],"list",n)==0 )||( strncmp(g.argv[2],"ls",n)==0 )){
    Stmt q;
    const int fRaw = find_option("raw","",0)!=0;
    const char *zTagType = find_option("tagtype","t",1);
Changes to src/tar.c.
29
30
31
32
33
34
35























































36
37
38
39
40
41
42
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  unsigned char *aHdr;      /* Space for building headers */
  char *zSpaces;            /* Spaces for padding */
  char *zPrevDir;           /* Name of directory for previous entry */
  int nPrevDirAlloc;        /* size of zPrevDir */
  Blob pax;                 /* PAX data */
} tball;

/*
** Convert a string so that it contains only lower-case ASCII, digits,
** "_" and "-".  Changes are made in-place.
*/
static void sanitize_name(char *zName){
  int i;
  char c;
  if( zName==0 ) return;
  for(i=0; (c = zName[i])!=0; i++){
    if( fossil_isupper(c) ){
      zName[i] = fossil_tolower(c);
    }else if( !fossil_isalnum(c) && c!='_' && c!='-' ){
      if( c<=0x7f ){
        zName[i] = '_';
      }else{
                /*  123456789 123456789 123456  */
        zName[i] = "abcdefghijklmnopqrstuvwxyz"[(unsigned)c%26];
      }
    }
  }
}

/*
** Compute a sensible base-name for an archive file (tarball, ZIP, or SQLAR)
** based on the rid of the check-in contained in that file.
**
**      PROJECTNAME-DATETIME-HASHPREFIX
**
** So that the name will be safe to use as a URL or a filename on any system,
** the name is only allowed to contain lower-case ASCII alphabetics,
** digits, '_' and '-'.  Upper-case ASCII is converted to lower-case.  All
** other bytes are mapped into a lower-case alphabetic.
**
** The value returned is obtained from mprintf() or fossil_strdup() and should
** be released by the caller using fossil_free().
*/
char *archive_base_name(int rid){
  char *zPrefix;
  char *zName;
  zPrefix = db_get("short-project-name",0);
  if( zPrefix==0 || zPrefix[0]==0 ){
    zPrefix = db_get("project-name","unnamed");
  }
  zName = db_text(0,
    "SELECT %Q||"
          " strftime('-%%Y%%m%%d%%H%%M%%S-',event.mtime)||"
          " substr(blob.uuid,1,10)"
     " FROM blob, event"
    " WHERE blob.rid=%d"
      " AND event.objid=%d",
    zPrefix, rid, rid);
  fossil_free(zPrefix);
  sanitize_name(zName);
  return zName;
}

/*
** field lengths of 'ustar' name and prefix fields.
*/
#define USTAR_NAME_LEN    100
#define USTAR_PREFIX_LEN  155

497
498
499
500
501
502
503
504

505
506
507
508
509
510
511
552
553
554
555
556
557
558

559
560
561
562
563
564
565
566







-
+







  nPrefix = blob_size(&filename);

  pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0);
  if( pManifest ){
    int flg, eflg = 0;
    mTime = (unsigned)((pManifest->rDate - 2440587.5)*86400.0);
    if( pTar ) tar_begin(mTime);
    flg = db_get_manifest_setting();
    flg = db_get_manifest_setting(blob_str(&hash));
    if( flg ){
      /* eflg is the effective flags, taking include/exclude into account */
      if( (pInclude==0 || glob_match(pInclude, "manifest"))
       && !glob_match(pExclude, "manifest")
       && (flg & MFESTFLG_RAW) ){
        eflg |= MFESTFLG_RAW;
      }
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
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
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
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
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

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







-
+
-
-
-
-
-
-
-
-













+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+

-
+
+
+

-
-
+
+
+
+
+

-
-
+
+
+
+

-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
-
-
+
+
+
+
+
+

-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
+
+
+



-
+
+

-
+
+












-
+
+





-
+
+





-
+
+

-
+






+
+
+
+
+
+
+
+
+
+
+
+
+
+
+















+





-
+







  }
  zOut = g.argv[3];
  if( fossil_strcmp("/dev/null",zOut)==0 || fossil_strcmp("",zOut)==0 ){
    zOut = 0;
  }

  if( zName==0 ){
    zName = db_text("default-name",
    zName = archive_base_name(rid);
       "SELECT replace(%Q,' ','_') "
          " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) "
          " || substr(blob.uuid, 1, 10)"
       "  FROM event, blob"
       " WHERE event.objid=%d"
       "   AND blob.rid=%d",
       db_get("project-name", "unnamed"), rid, rid
    );
  }
  tarball_of_checkin(rid, zOut ? &tarball : 0,
                     zName, pInclude, pExclude, listFlag);
  glob_free(pInclude);
  glob_free(pExclude);
  if( listFlag ) fflush(stdout);
  if( zOut ){
    blob_write_to_file(&tarball, zOut);
    blob_reset(&tarball);
  }
}

/*
** This is a helper routine for tar_uuid_from_name().  It handles
** the case where *pzName contains no "/" character.  Check for
** format (3).  Return the hash if the name matches format (3),
** or return NULL if it does not.
*/
static char *format_three_parser(const char *zName){
  int iDot = 0;    /* Index in zName[] of the first '.' */
  int iDash1 = 0;  /* Index in zName[] of the '-' before the timestamp */
  int iDash2 = 0;  /* Index in zName[] of the '-' between timestamp and hash */
  int nHash;       /* Size of the hash */
  char *zHash;     /* A copy of the hash value */
  char *zDate;     /* Copy of the timestamp */
  char *zUuid;     /* Final result */
  int i;           /* Loop query */
  Stmt q;          /* Query to verify that hash and timestamp agree */

  for(i=0; zName[i]; i++){
    char c = zName[i];
    if( c=='.' ){ iDot = i;  break; }
    if( c=='-' ){ iDash1 = iDash2; iDash2 = i; }
    if( !fossil_isalnum(c) && c!='_' && c!='-' ){ break; }
  }
  if( iDot==0 ) return 0;
  if( iDash1==0 ) return 0;
  nHash = iDot - iDash2 - 1;
  if( nHash<8 ) return 0;                /* HASH value too short */  
  if( (iDash2 - iDash1)!=15 ) return 0;  /* Wrong timestamp size */
  zHash = fossil_strndup(&zName[iDash2+1], nHash);
  zDate = fossil_strndup(&zName[iDash1+1], 14);
  db_prepare(&q, 
    "SELECT blob.uuid"
    "  FROM blob JOIN event ON event.objid=blob.rid"
    " WHERE blob.uuid GLOB '%q*'"
    "   AND strftime('%%Y%%m%%d%%H%%M%%S',event.mtime)='%q'", 
    zHash, zDate
  );
  fossil_free(zHash);
  fossil_free(zDate);
  if( db_step(&q)==SQLITE_ROW ){
    zUuid = fossil_strdup(db_column_text(&q,0));
  }else{
    zUuid = 0;
  }
  db_finalize(&q);
  return zUuid;
}

/*
** Check to see if the input string is of the form:
** Check to see if the input string is of one of the following
** two the forms:
**
**        check-in-name/filename.ext
**        check-in-name/filename.ext                       (1)
**        tag-name/check-in-name/filename.ext              (2)
**        project-datetime-hash.ext                        (3)
**
** In other words, check to see if the input contains a single '/'
** character that separates a valid check-in name from a filename.
** In other words, check to see if the input string contains either
** a check-in name or a tag-name and a check-in name separated by
** a slash.  There must be between 0 or 2 "/" characters.  In the
** second form, tag-name must be an individual tag (not a branch-tag)
** that is found on the check-in identified by the check-in-name.
**
** If the condition is true, return the check-in name and set the
** input string to be the filename.
** If the condition is true, then:
**
**   *  Make *pzName point to the filename suffix only
**   *  return a copy of the check-in name in memory from mprintf().
**
** If the condition is false, return NULL
** If the condition is false, leave *pzName unchanged and return either
** NULL or an empty string.  Normally NULL is returned, however an
** empty string is returned for format (2) if check-in-name does not
** match tag-name.
**
** Format (2) is specifically designed to allow URLs like this:
**
**      /tarball/release/UUID/PROJECT.tar.gz
**
** Such URLs will pass through most anti-robot filters because of the
** "/tarball/release" prefix will match the suggested "robot-exception"
** pattern and can still refer to an historic release rather than just
** the most recent release.
**
** Format (3) is designed to allow URLs like this:
**
**     /tarball/fossil-20251018193920-d6c9aee97df.tar.gz
**
** In other words, filename itself contains sufficient information to
** uniquely identify the check-in, including a timestamp of the form
** YYYYMMDDHHMMSS and a prefix of the check-in hash.  The timestamp
** and hash must immediately precede the first "." in the name.
*/
char *tar_uuid_from_name(char **pzName){
  char *zName = *pzName;
  int i, n;
  for(i=n=0; zName[i]; i++){
  char *zName = *pzName;      /* Original input */
  int n1 = 0;                 /* Bytes in first prefix (tag-name) */
  int n2 = 0;                 /* Bytes in second prefix (check-in-name) */
  int n = 0;                  /* max(n1,n2) */
  int i;                      /* Loop counter */
  for(i=n1=n2=0; zName[i]; i++){
    if( zName[i]=='/' ){
      if( n==0 ) n = i;
      else return 0;
    }
  }
  if( n==0 ) return 0;
  if( zName[n+1]==0 ) return 0;
  zName[n] = 0;
  *pzName = fossil_strdup(&zName[n+1]);
  return zName;
}

      if( n1==0 ){
        n = n1 = i;
      }else if( n2==0 ){
        n = n2 = i;
      }else{
        return 0;   /* More than two "/" characters seen */
      }
    }
  }
  if( n1==0 ){
    /* Check for format (3) */
    return format_three_parser(*pzName);
  }
  if( zName[n+1]==0 ){
    return 0;    /* No filename suffix */
  }
  if( n2==0 ){
    /* Format (1): check-in name only.  The check-in-name is not verified */
    zName[n1] = 0;
    *pzName = fossil_strdup(&zName[n1+1]);
    return zName;
  }else if( n2>n1+1 ){
    /* Format (2): tag-name/check-in-name.  Verify that check-in-name is real
    ** and that the check-in has the tag named by tag-name.
    */
    char *zCkin = mprintf("%.*s", n2-n1-1, &zName[n1+1]);
    char *zTag;
    int rid = symbolic_name_to_rid(zCkin,"ci");
    int hasTag;
    if( rid<=0 ){
      fossil_free(zCkin);
      return fossil_strdup("");
    }
    zTag = mprintf("%.*s", n1, zName);
    hasTag = db_exists(
      "SELECT 1 FROM tagxref, tag"
      " WHERE tagxref.rid=%d"
      "   AND tag.tagid=tagxref.tagid"
      "   AND tagxref.tagtype=1"
      "   AND tag.tagname='sym-%q'",
      rid, zTag
    );
    fossil_free(zTag);
    if( !hasTag ){
      fossil_free(zCkin);
      return fossil_strdup("");
    }
    *pzName = fossil_strdup(&zName[n2+1]);
    return zCkin;             
  }else{
    return 0;
  }
}

/*
** WEBPAGE: tarball
** URL: /tarball/[VERSION/]NAME.tar.gz
** URL: /tarball/NAME.tar.gz
**  or: /tarball/VERSION/NAME.tar.gz
**  or: /tarball/TAG/VERSION/NAME.tar.gz
**
** Generate a compressed tarball for the check-in specified by VERSION.
** The tarball is called NAME.tar.gz and has a top-level directory called
** NAME.
** NAME.  If TAG is provided, then VERSION must hold TAG or else an error
** is returned.
**
** The optional VERSION element defaults to "trunk" per the r= rules below.
** The optional VERSION element defaults to the name of the main branch
** (usually "trunk") per the r= rules below.
** All of the following URLs are equivalent:
**
**      /tarball/release/xyz.tar.gz
**      /tarball?r=release&name=xyz.tar.gz
**      /tarball/xyz.tar.gz?r=release
**      /tarball?name=release/xyz.tar.gz
**
** Query parameters:
**
**   name=[CKIN/]NAME    The optional CKIN component of the name= parameter
**                       identifies the check-in from which the tarball is
**                       constructed.  If CKIN is omitted and there is no
**                       r= query parameter, then use "trunk".  NAME is the
**                       r= query parameter, then use the name of the main
**                       branch (usually "trunk").  NAME is the
**                       name of the download file.  The top-level directory
**                       in the generated tarball is called by NAME with the
**                       file extension removed.
**
**   r=TAG               TAG identifies the check-in that is turned into a
**                       compressed tarball.  The default value is "trunk".
**                       compressed tarball.  The default value is the name of
**                       the main branch (usually "trunk").
**                       If r= is omitted and if the name= query parameter
**                       contains one "/" character then the of part the
**                       name= value before the / becomes the TAG and the
**                       part of the name= value  after the / is the download
**                       filename.  If no check-in is specified by either
**                       name= or r=, then "trunk" is used.
**                       name= or r=, then the name of the main branch
**                       (usually "trunk") is used.
**
**   in=PATTERN          Only include files that match the comma-separate
**   in=PATTERN          Only include files that match the comma-separated
**                       list of GLOB patterns in PATTERN, as with ex=
**
**   ex=PATTERN          Omit any file that match PATTERN.  PATTERN is a
**                       comma-separated list of GLOB patterns, where each
**                       pattern can optionally be quoted using ".." or '..'.
**                       Any file matching both ex= and in= is excluded.
**
** Robot Defenses:
**
**   *    If "zip" appears in the robot-restrict setting, then robots are
**        not allowed to access this page.  Suspected robots will be
**        presented with a captcha.
**
**   *    If "zipX" appears in the robot-restrict setting, then robots are
**        restricted in the same way as with "zip", but with exceptions.
**        If the check-in for which an archive is requested is a leaf check-in
**        and if the robot-zip-leaf setting is true, then the request is
**        allowed.  Or if the check-in has a tag that matches any of the
**        GLOB patterns on the list in the robot-zip-tag setting, then the
**        request is allowed.  Otherwise, the usual robot defenses are
**        activated.
*/
void tarball_page(void){
  int rid;
  char *zName, *zRid, *zKey;
  int nName, nRid;
  const char *zInclude;         /* The in= query parameter */
  const char *zExclude;         /* The ex= query parameter */
  Blob cacheKey;                /* The key to cache */
  Glob *pInclude = 0;           /* The compiled in= glob pattern */
  Glob *pExclude = 0;           /* The compiled ex= glob pattern */
  Blob tarball;                 /* Tarball accumulated here */
  const char *z;

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
  if( robot_restrict("zip") ) return;
  fossil_nice_default();
  zName = fossil_strdup(PD("name",""));
  z = P("r");
  if( z==0 ) z = P("uuid");
  if( z==0 ) z = tar_uuid_from_name(&zName);
  if( z==0 ) z = "trunk";
  if( z==0 ) z = fossil_strdup(db_main_branch());
  g.zOpenRevision = zRid = fossil_strdup(z);
  nRid = strlen(zRid);
  zInclude = P("in");
  if( zInclude ) pInclude = glob_create(zInclude);
  zExclude = P("ex");
  if( zExclude ) pExclude = glob_create(zExclude);
  if( zInclude==0 && zExclude==0 ){
794
795
796
797
798
799
800

801
802
803
804
805
806
807
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001







+







  }
  rid = symbolic_name_to_rid(nRid?zRid:zName, "ci");
  if( rid==0 ){
    cgi_set_status(404, "Not Found");
    @ Not found
    return;
  }
  if( robot_restrict_zip(rid) ) return;
  if( nRid==0 && nName>10 ) zName[10] = 0;

  /* Compute a unique key for the cache entry based on query parameters */
  blob_init(&cacheKey, 0, 0);
  blob_appendf(&cacheKey, "/tarball/%z", rid_to_uuid(rid));
  blob_appendf(&cacheKey, "/%q", zName);
  if( zInclude ) blob_appendf(&cacheKey, ",in=%Q", zInclude);
845
846
847
848
849
850
851









































































































































































































































































































































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
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
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
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
1210
1211
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
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
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
1320
1321
1322
1323
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
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
  fossil_free(zName);
  fossil_free(zRid);
  g.zOpenRevision = 0;
  blob_reset(&cacheKey);
  cgi_set_content(&tarball);
  cgi_set_content_type("application/x-compressed");
}

/*
** This routine is called for each check-in on the /download page to
** construct the "extra" information after the description.
*/
void download_extra(
  Stmt *pQuery,               /* Current row of the timeline query */
  int tmFlags,                /* Flags to www_print_timeline() */
  const char *zThisUser,      /* Suppress links to this user */
  const char *zThisTag        /* Suppress links to this tag */
){
  const char *zType = db_column_text(pQuery, 7);
  assert( zType!=0 );
  if( zType[0]!='c' ){
    timeline_extra(pQuery, tmFlags, zThisUser, zThisTag);
  }else{    
    int rid = db_column_int(pQuery, 0);
    const char *zUuid = db_column_text(pQuery, 1);
    char *zBrName = branch_of_rid(rid);
    char *zNm;

    if( tmFlags & TIMELINE_COLUMNAR ){
      @ <nobr>check-in:&nbsp;\
      @   %z(href("%R/info/%!S",zUuid))<span class='timelineHash'>\
      @   %S(zUuid)</span></a></nobr><br>
      if( fossil_strcmp(zBrName,"trunk")!=0 ){
        @ <nobr>branch:&nbsp;\
        @   %z(href("%R/timeline?r=%t",zBrName))%h(zBrName)</a></nobr><br>\
      }
    }else{
      if( (tmFlags & TIMELINE_CLASSIC)==0 ){
        @ check-in:&nbsp;%z(href("%R/info/%!S",zUuid))\
        @ <span class='timelineHash'>%S(zUuid)</span></a>
      }
      if( (tmFlags & TIMELINE_GRAPH)==0 && fossil_strcmp(zBrName,"trunk")!=0 ){
        @ branch:&nbsp;\
        @   %z(href("%R/timeline?r=%t",zBrName))%h(zBrName)</a>
      }
    }
    zNm = archive_base_name(rid);
    @ %z(href("%R/tarball/%s.tar.gz",zNm))\
    @    <button>Tarball</button></a>
    @  %z(href("%R/zip/%s.zip",zNm))\
    @    <button>ZIP&nbsp;Archive</button></a>
    fossil_free(zBrName);
    fossil_free(zNm);
  }
}

/*
** SETTING: suggested-downloads               width=70  block-text
**
** This setting controls the suggested tarball/ZIP downloads on the
** [[/download]] page.  The value is a TCL list.  Each set of four items
** defines a set of check-ins to be added to the suggestion list.
** The items in each group are:
**
** |    COUNT   TAG   MAX_AGE    COMMENT
**
** COUNT is the number of check-ins to match, starting with the most
** recent and working bacwards in time.  Check-ins match if they contain
** the tag TAG.  If MAX_AGE is not an empty string, then it specifies
** the maximum age of any matching check-in.  COMMENT is an optional
** comment for each match.
**
** The special value of "OPEN-LEAF" for TAG matches any check-in that
** is an open leaf.
**
** MAX_AGE is of the form "{AMT UNITS}"  where AMT is a floating point
** value and UNITS is one of "seconds", "hours", "days", "weeks", "months",
** or "years".  If MAX_AGE is an empty string then there is no age limit.
**
** If COMMENT is not an empty string, then it is an additional comment
** added to the output description of the suggested download.  The idea of
** COMMENT is to explain to the reader why a check-in is a suggested
** download.  
**
** Example:
**
** |       1   trunk     {}         {Latest Trunk Check-in}
** |       5   OPEN-LEAF {1 month}  {Active Branch}
** |       999 release   {1 year}   {Official Release}
**
** The value causes the /download page to show the union of the most
** recent trunk check-in of any age, the five most recent
** open leaves within the past month, and essentially
** all releases within the past year.  If the same check-in matches more
** than one rule, the COMMENT of the first match is used.
*/

/*
** WEBPAGE: /download
**
** Show a special no-graph timeline of recent important check-ins with
** an opportunity to pull tarballs and ZIPs.
*/
void download_page(void){
  Stmt q;                       /* The actual timeline query */
  const char *zTarlistCfg;      /* Configuration string */
  char **azItem;                /* Decomposed elements of zTarlistCfg */
  int *anItem;                  /* Bytes in each term of azItem[] */
  int nItem;                    /* Number of terms in azItem[] */
  int i;                        /* Loop counter */
  int tmFlags;                  /* Timeline display flags */
  int n;                        /* Number of suggested downloads */
  double rNow;                  /* Current time.  Julian day number */
  int bPlainTextCom;            /* Use plain-text comments */

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }

  style_set_current_feature("timeline");
  style_header("Suggested Downloads");

  zTarlistCfg = db_get("suggested-downloads","off");
  db_multi_exec(
    "CREATE TEMP TABLE tarlist(rid INTEGER PRIMARY KEY, com TEXT);"
  );
  rNow = db_double(0.0,"SELECT julianday()");
  if( !g.interp ) Th_FossilInit(0);
  Th_SplitList(g.interp, zTarlistCfg, (int)strlen(zTarlistCfg),
                   &azItem, &anItem, &nItem);
  bPlainTextCom = db_get_boolean("timeline-plaintext",0);
  for(i=0; i<nItem-3; i+=4){
    int cnt;             /* The number of instances of zLabel to use */
    char *zLabel;        /* The label to match */
    double rStart;       /* Starting time, Julian day number */
    char *zComment = 0;  /* Comment to apply */
    if( anItem[i]==1 && azItem[i][0]=='*' ){
      cnt = -1;
    }else if( anItem[i]<1 ){
      cnt = 0;
    }else{
      cnt = atoi(azItem[i]);
    }
    if( cnt==0 ) continue;
    zLabel = fossil_strndup(azItem[i+1],anItem[i+1]);
    if( anItem[i+2]==0 ){
      rStart = 0.0;
    }else{
      char *zMax = fossil_strndup(azItem[i+2], anItem[i+2]);
      double r = atof(zMax);
      if( strstr(zMax,"sec") ){
        rStart = rNow - r/86400.0;
      }else
      if( strstr(zMax,"hou") ){
        rStart = rNow - r/24.0;
      }else
      if( strstr(zMax,"da") ){
        rStart = rNow - r;
      }else
      if( strstr(zMax,"wee") ){
        rStart = rNow - r*7.0;
      }else
      if( strstr(zMax,"mon") ){
        rStart = rNow - r*30.44;
      }else
      if( strstr(zMax,"yea") ){
        rStart = rNow - r*365.24;
      }else
      { /* Default to seconds */
        rStart = rNow - r/86400.0;
      }
    }
    if( anItem[i+3]==0 ){
      zComment = fossil_strdup("");
    }else if( bPlainTextCom ){
      zComment = mprintf("** %.*s ** ", anItem[i+3], azItem[i+3]);
    }else{
      zComment = mprintf("<b>%.*s</b>\n<p>", anItem[i+3], azItem[i+3]);
    }
    if( fossil_strcmp("OPEN-LEAF",zLabel)==0 ){
      db_multi_exec(
        "INSERT OR IGNORE INTO tarlist(rid,com)"
         " SELECT leaf.rid, %Q FROM leaf, event"
          " WHERE event.objid=leaf.rid"
            " AND event.mtime>=%.6f"
            " AND NOT EXISTS(SELECT 1 FROM tagxref"
                            " WHERE tagxref.rid=leaf.rid"
                              " AND tagid=%d AND tagtype>0)"
          " ORDER BY event.mtime DESC LIMIT %d",
          zComment, rStart, TAG_CLOSED, cnt
      );
    }else{
      db_multi_exec(
        "WITH taglist(tid) AS"
            " (SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q')"
        "INSERT OR IGNORE INTO tarlist(rid,com)"
        " SELECT event.objid, %Q FROM event CROSS JOIN tagxref"
        "  WHERE event.type='ci'"
        "    AND event.mtime>=%.6f"
        "    AND tagxref.tagid IN taglist"
        "    AND tagtype>0"
        "    AND tagxref.rid=event.objid"
        "  ORDER BY event.mtime DESC LIMIT %d",
        zLabel, zComment, rStart, cnt
      );
    }
    fossil_free(zLabel);
    fossil_free(zComment);
  }
  Th_Free(g.interp, azItem);

  n = db_int(0, "SELECT count(*) FROM tarlist");
  if( n==0 ){
    @ <h2>No tarball/ZIP suggestions are available at this time</h2>
  }else{
    @ <h2>%d(n) Tarball/ZIP Download Suggestion%s(n>1?"s":""):</h2>
    db_prepare(&q,
      "WITH matches AS (%s AND blob.rid IN (SELECT rid FROM tarlist))\n"
      "SELECT blobRid, uuid, timestamp,"
            " com||comment,"
            " user, leaf, bgColor, eventType, tags, tagid, brief, mtime"
      "  FROM matches JOIN tarlist ON tarlist.rid=blobRid"
      " ORDER BY matches.mtime DESC",
      timeline_query_for_www()
    );

    tmFlags = TIMELINE_DISJOINT | TIMELINE_NOSCROLL | TIMELINE_COLUMNAR
            | TIMELINE_BRCOLOR;
    www_print_timeline(&q, tmFlags, 0, 0, 0, 0, 0, download_extra);
    db_finalize(&q);
  }
  if( g.perm.Clone ){
    char *zNm = fossil_strdup(db_get("project-name","clone"));
    sanitize_name(zNm);    
    @ <hr>
    @ <h2>You Can Clone This Repository</h2>
    @
    @ <p>Clone this repository by running a command similar to the following:
    @ <blockquote><pre>
    @ fossil  clone  %s(g.zBaseURL)  %h(zNm).fossil
    @ </pre></blockquote>
    @ <p>A clone gives you local access to all historical content.
    @ Cloning is a bandwidth- and CPU-efficient alternative to extracting
    @ multiple tarballs and ZIPs.
    @ Do a web search for "fossil clone" or similar to find additional
    @ information about using a cloned Fossil repository.  Or ask your
    @ favorite AI how to extract content from a Fossil clone.
    fossil_free(zNm);
  }

  style_finish_page();
}

/*
** WEBPAGE: rchvdwnld
**
** Short for "archive download".  This page should have a single name=
** query parameter that is a check-in hash or symbolic name.  The resulting
** page offers a menu of possible download options for that check-in,
** including tarball, ZIP, or SQLAR.
**
** This is a utility page.  The /dir and /tree pages sometimes have a
** "Download" option in their submenu which redirects here.  Those pages
** used to have separate "Tarball" and "ZIP" submenu entries, but as
** submenu entries appear in alphabetical order, that caused the two
** submenu entries to be separated from one another, which is distracting.
**
** If the name= does not have a unique resolution, no error is generated.
** Instead, a redirect to the home page for the repository is made.
**
** Robots are excluded from this page if either of the keywords
** "zip" or "download" appear in the [[robot-restrict]] setting.
*/
void rchvdwnld_page(void){
  const char *zUuid;
  char *zBase;
  int nUuid;
  int rid;
  char *zTags;
  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
  if( robot_restrict("zip") || robot_restrict("download") ) return;

  zUuid = P("name");
  if( zUuid==0
   || (nUuid = (int)strlen(zUuid))<6
   || !validate16(zUuid,-1)
   || (rid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUuid))==0
   || !db_exists("SELECT 1 from event WHERE type='ci' AND objid=%d",rid)
  ){
    rid = symbolic_name_to_rid(zUuid, "ci");
    if( rid<=0 ){
      fossil_redirect_home();
    }
  }
  zUuid = db_text(zUuid, "SELECT uuid FROM blob WHERE rid=%d", rid);
  zTags = db_text(0,
    "SELECT if(cnt,' ('||tags||')','') FROM ("
      "SELECT group_concat(substr(tagname,5),', ') AS tags, count(*) AS cnt"
      "  FROM tag JOIN tagxref USING(tagid)"
      " WHERE rid=%d"
      "   AND tagtype=1"
      "   AND tagname GLOB 'sym-*'"
    ")",
    rid
  );
  style_header("Downloads For Check-in %!S", zUuid);
  zBase = archive_base_name(rid);
  @ <div class="section accordion">Downloads for check-in \
  @ %z(href("%R/info/%!S",zUuid))%S(zUuid)</a>%h(zTags)</div>
  @ <div class="accordion_panel">
  @ <table class="label-value">
  @ <tr>
  @ <th>Tarball:</th>
  @ <td>%z(href("%R/tarball/%s.tar.gz",zBase))\
  @ %s(g.zBaseURL)/tarball/%s(zBase).tar.gz</a></td>
  @ </tr>
  @
  @ <tr>
  @ <th>ZIP:</th>
  @ <td>%z(href("%R/zip/%s.zip",zBase))\
  @ %s(g.zBaseURL)/zip/%s(zBase).zip</a></td>
  @ </tr>
  @
  @ <tr>
  @ <th>SQLAR:</th>
  @ <td>%z(href("%R/sqlar/%s.sqlar",zBase))\
  @ %s(g.zBaseURL)/sqlar/%s(zBase).sqlar</a></td>
  @ </tr>
  @ </table></div>
  fossil_free(zBase);
  @ <div class="section accordion">Context</div><div class="accordion_panel">
  render_checkin_context(rid, 0, 0, 0);
  @ </div>
  builtin_request_js("accordion.js");
  style_finish_page();
}
Changes to src/terminal.c.
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
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







-
+














-
+
+
+
+









-
+
+
+
+









-
+
+
+
+







  unsigned int nLines;           /* Number of lines */
};
#endif


/* Get the current terminal size by calling a system service.
**
** Return 1 on success. This sets the size parameters to the values retured by
** Return 1 on success. This sets the size parameters to the values returned by
** the system call, when such is supported; set the size to zero otherwise.
** Return 0 on the system service call failure.
**
** Under Linux/bash the size info is also available from env $LINES, $COLUMNS.
** Or it can be queried using tput `echo -e "lines\ncols"|tput -S`.
** Technically, this info could be cached, but then we'd need to handle
** SIGWINCH signal to requery the terminal on resize event.
*/
int terminal_get_size(TerminalSize *t){
  memset(t, 0, sizeof(*t));

#if defined(TIOCGSIZE)
  {
    struct ttysize ts;
    if( ioctl(STDIN_FILENO, TIOCGSIZE, &ts)!=-1 ){
    if( ioctl(STDIN_FILENO, TIOCGSIZE, &ts)>=0
     || ioctl(STDOUT_FILENO, TIOCGSIZE, &ts)>=0
     || ioctl(STDERR_FILENO, TIOCGSIZE, &ts)>=0
    ){
      t->nColumns = ts.ts_cols;
      t->nLines = ts.ts_lines;
      return 1;
    }
    return 0;
  }
#elif defined(TIOCGWINSZ)
  {
    struct winsize ws;
    if( ioctl(STDIN_FILENO, TIOCGWINSZ, &ws)!=-1 ){
    if( ioctl(STDIN_FILENO, TIOCGWINSZ, &ws)>=0
     || ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws)>=0
     || ioctl(STDERR_FILENO, TIOCGWINSZ, &ws)>=0
    ){
      t->nColumns = ws.ws_col;
      t->nLines = ws.ws_row;
      return 1;
    }
    return 0;
  }
#elif defined(_WIN32)
  {
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    if( GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) ){
    if( GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)
     || GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE), &csbi)
     || GetConsoleScreenBufferInfo(GetStdHandle(STD_INPUT_HANDLE), &csbi)
    ){
      t->nColumns = csbi.srWindow.Right - csbi.srWindow.Left + 1;
      t->nLines = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
      return 1;
    }
    return 0;
  }
#else
115
116
117
118
119
120
121














122




123
124
125




126
127





128



129
130
131















132
133

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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+

-
-
+
+
+
+

-
+
+
+
+
+

+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-

+
  if( terminal_get_size(&ts) ){
    return ts.nLines;
  }
  return nDefault;
}

/*
** Return true if it is reasonable is emit VT100 escape codes.
*/
int terminal_is_vt100(void){
  char *zNoColor;
#ifdef _WIN32
  if( !win32_terminal_is_vt100(1) ) return 0;
#endif /* _WIN32 */
  if( !fossil_isatty(1) ) return 0;
  zNoColor =fossil_getenv("NO_COLOR");
  if( zNoColor==0 ) return 1;
  if( zNoColor[0]==0 ) return 1;
  if( is_false(zNoColor) ) return 1;
  return 0;
}
** COMMAND: test-terminal-size

#ifdef _WIN32
/*
** Return true if the Windows console supports VT100 escape codes.
**
** Show the size of the terminal window from which the command is launched
** as two integers, the width in characters and the height in lines.
** Support for VT100 escape codes is enabled by default in Windows Terminal
** on Windows 10 and Windows 11, and disabled by default in Legacy Consoles
** and on older versions of Windows. Programs can turn on VT100 support for
** Legacy Consoles using the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag.
**
** If the size cannot be determined, two zeros are shown.
** NOTE: If this function needs to be called in more complex scenarios with
** reassigned stdout and stderr streams, the following CRT calls are useful
** to translate from CRT streams to file descriptors and to Win32 handles:
**
**    HANDLE hOutputHandle = (HANDLE)_get_osfhandle(_fileno(<FILE*>));
*/
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif
void test_terminal_size_cmd(void){
  TerminalSize ts;
  terminal_get_size(&ts);
int win32_terminal_is_vt100(int fd){
  HANDLE hConsole = NULL;
  DWORD dwConsoleMode;
  switch( fd ){
    case 1:
      hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
      break;
    case 2:
      hConsole = GetStdHandle(STD_ERROR_HANDLE);
      break;
  }
  if( GetConsoleMode(hConsole,&dwConsoleMode) ){
    return (dwConsoleMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)!=0;
  }
  return 0;
  fossil_print("%d %d\n", ts.nColumns, ts.nLines);
}
#endif /* _WIN32 */
Changes to src/th.c.
1
2
3
4
5
6
7
8
9
10
11






12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24











+
+
+
+
+
+








/*
** The implementation of the TH core. This file contains the parser, and
** the implementation of the interface in th.h.
*/

#include "config.h"
#include "th.h"
#include <string.h>
#include <assert.h>

/*
** External routines
*/
void fossil_panic(const char*,...);
void fossil_errorlog(const char*,...);

/*
** Values used for element values in the tcl_platform array.
*/

#if !defined(TH_ENGINE)
#  define TH_ENGINE          "TH1"
#endif
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
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







+












+
+
+
+
+
+
+
+










-
+

+



+





+






+





-
+

+

-
+





+







** The Buffer structure and the thBufferXXX() functions are used to make
** memory allocation easier when building up a result.
*/
struct Buffer {
  char *zBuf;
  int nBuf;
  int nBufAlloc;
  int bTaint;
};
typedef struct Buffer Buffer;
static void thBufferInit(Buffer *);
static void thBufferFree(Th_Interp *interp, Buffer *);

/*
** This version of memcpy() allows the first and second argument to
** be NULL as long as the number of bytes to copy is zero.
*/
static void th_memcpy(void *dest, const void *src, size_t n){
  if( n>0 ) memcpy(dest,src,n);
}

/*
** An oversized string has been encountered.  Do not try to recover.
** Panic the process.
*/
void Th_OversizeString(void){
  fossil_panic("string too large. maximum size 286MB.");
}

/*
** Append nAdd bytes of content copied from zAdd to the end of buffer
** pBuffer. If there is not enough space currently allocated, resize
** the allocation to make space.
*/
static void thBufferWriteResize(
  Th_Interp *interp,
  Buffer *pBuffer,
  const char *zAdd,
  int nAdd
  int nAddX
){
  int nAdd = TH1_LEN(nAddX);
  int nNew = (pBuffer->nBuf+nAdd)*2+32;
#if defined(TH_MEMDEBUG)
  char *zNew = (char *)Th_Malloc(interp, nNew);
  TH1_SIZECHECK(nNew);
  th_memcpy(zNew, pBuffer->zBuf, pBuffer->nBuf);
  Th_Free(interp, pBuffer->zBuf);
  pBuffer->zBuf = zNew;
#else
  int nOld = pBuffer->nBufAlloc;
  TH1_SIZECHECK(nNew);
  pBuffer->zBuf = Th_Realloc(interp, pBuffer->zBuf, nNew);
  memset(pBuffer->zBuf+nOld, 0, nNew-nOld);
#endif
  pBuffer->nBufAlloc = nNew;
  th_memcpy(&pBuffer->zBuf[pBuffer->nBuf], zAdd, nAdd);
  pBuffer->nBuf += nAdd;
  TH1_XFER_TAINT(pBuffer->bTaint, nAddX);
}
static void thBufferWriteFast(
  Th_Interp *interp,
  Buffer *pBuffer,
  const char *zAdd,
  int nAdd
  int nAddX
){
  int nAdd = TH1_LEN(nAddX);
  if( pBuffer->nBuf+nAdd > pBuffer->nBufAlloc ){
    thBufferWriteResize(interp, pBuffer, zAdd, nAdd);
    thBufferWriteResize(interp, pBuffer, zAdd, nAddX);
  }else{
    if( pBuffer->zBuf ){
      memcpy(pBuffer->zBuf + pBuffer->nBuf, zAdd, nAdd);
    }
    pBuffer->nBuf += nAdd;
    TH1_XFER_TAINT(pBuffer->bTaint, nAddX);
  }
}
#define thBufferWrite(a,b,c,d) thBufferWriteFast(a,b,(const char *)c,d)

/*
** Add a single character to a buffer
*/
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
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







+



-
-
+
+



-
+

-
+


-
+







  Th_Interp *interp,
  const char *zWord,
  int nWord
){
  int rc = TH_OK;
  Buffer output;
  int i;
  int nn = TH1_LEN(nWord);

  thBufferInit(&output);

  if( nWord>1 && (zWord[0]=='{' && zWord[nWord-1]=='}') ){
    thBufferWrite(interp, &output, &zWord[1], nWord-2);
  if( nn>1 && (zWord[0]=='{' && zWord[nn-1]=='}') ){
    thBufferWrite(interp, &output, &zWord[1], nn-2);
  }else{

    /* If the word is surrounded by double-quotes strip these away. */
    if( nWord>1 && (zWord[0]=='"' && zWord[nWord-1]=='"') ){
    if( nn>1 && (zWord[0]=='"' && zWord[nn-1]=='"') ){
      zWord++;
      nWord -= 2;
      nn -= 2;
    }

    for(i=0; rc==TH_OK && i<nWord; i++){
    for(i=0; rc==TH_OK && i<nn; i++){
      int nGet;

      int (*xGet)(Th_Interp *, const char*, int, int *) = 0;
      int (*xSubst)(Th_Interp *, const char*, int) = 0;

      switch( zWord[i] ){
        case '\\':
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
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







-
+














-
+







          }
        default: {
          thBufferAddChar(interp, &output, zWord[i]);
          continue; /* Go to the next iteration of the for(...) loop */
        }
      }

      rc = xGet(interp, &zWord[i], nWord-i, &nGet);
      rc = xGet(interp, &zWord[i], nn-i, &nGet);
      if( rc==TH_OK ){
        rc = xSubst(interp, &zWord[i], nGet);
      }
      if( rc==TH_OK ){
        const char *zRes;
        int nRes;
        zRes = Th_GetResult(interp, &nRes);
        thBufferWrite(interp, &output, zRes, nRes);
        i += (nGet-1);
      }
    }
  }

  if( rc==TH_OK ){
    Th_SetResult(interp, output.zBuf, output.nBuf);
    Th_SetResult(interp, output.zBuf, output.nBuf|output.bTaint);
  }
  thBufferFree(interp, &output);
  return rc;
}

/*
** Return true if one of the following is true of the buffer pointed
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
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







-
+










-
+






-
-
+
+







  int rc = TH_OK;

  Buffer strbuf;
  Buffer lenbuf;
  int nCount = 0;

  const char *zInput = zList;
  int nInput = nList;
  int nInput = TH1_LEN(nList);

  thBufferInit(&strbuf);
  thBufferInit(&lenbuf);

  while( nInput>0 ){
    const char *zWord;
    int nWord;

    thNextSpace(interp, zInput, nInput, &nWord);
    zInput += nWord;
    nInput = nList-(zInput-zList);
    nInput = TH1_LEN(nList)-(zInput-zList);

    if( TH_OK!=(rc = thNextWord(interp, zInput, nInput, &nWord, 0))
     || TH_OK!=(rc = thSubstWord(interp, zInput, nWord))
    ){
      goto finish;
    }
    zInput = &zInput[nWord];
    nInput = nList-(zInput-zList);
    zInput = &zInput[TH1_LEN(nWord)];
    nInput = TH1_LEN(nList)-(zInput-zList);
    if( nWord>0 ){
      zWord = Th_GetResult(interp, &nWord);
      thBufferWrite(interp, &strbuf, zWord, nWord);
      thBufferAddChar(interp, &strbuf, 0);
      thBufferWrite(interp, &lenbuf, &nWord, sizeof(int));
      nCount++;
    }
870
871
872
873
874
875
876
877

878
879
880
881
882
883
884
892
893
894
895
896
897
898

899
900
901
902
903
904
905
906







-
+







    );
    anElem = (int *)&azElem[nCount];
    zElem = (char *)&anElem[nCount];
    th_memcpy(anElem, lenbuf.zBuf, lenbuf.nBuf);
    th_memcpy(zElem, strbuf.zBuf, strbuf.nBuf);
    for(i=0; i<nCount;i++){
      azElem[i] = zElem;
      zElem += (anElem[i] + 1);
      zElem += (TH1_LEN(anElem[i]) + 1);
    }
    *pazElem = azElem;
    *panElem = anElem;
  }
  if( pnCount ){
    *pnCount = nCount;
  }
892
893
894
895
896
897
898
899

900





901
902
903
904
905
906
907
914
915
916
917
918
919
920

921
922
923
924
925
926
927
928
929
930
931
932
933
934







-
+

+
+
+
+
+







/*
** Evaluate the th1 script contained in the string (zProgram, nProgram)
** in the current stack frame.
*/
static int thEvalLocal(Th_Interp *interp, const char *zProgram, int nProgram){
  int rc = TH_OK;
  const char *zInput = zProgram;
  int nInput = nProgram;
  int nInput = TH1_LEN(nProgram);

  if( TH1_TAINTED(nProgram)
   && Th_ReportTaint(interp, "script", zProgram, nProgram)
  ){
    return TH_ERROR;
  }
  while( rc==TH_OK && nInput ){
    Th_HashEntry *pEntry;
    int nSpace;
    const char *zFirst;

    char **argv;
    int *argl;
947
948
949
950
951
952
953
954

955
956

957
958
959
960
961
962
963
974
975
976
977
978
979
980

981
982

983
984
985
986
987
988
989
990







-
+

-
+







    */
    rc = thSplitList(interp, zFirst, zInput-zFirst, &argv, &argl, &argc);
    if( rc!=TH_OK ) continue;

    if( argc>0 ){

      /* Look up the command name in the command hash-table. */
      pEntry = Th_HashFind(interp, interp->paCmd, argv[0], argl[0], 0);
      pEntry = Th_HashFind(interp, interp->paCmd, argv[0], TH1_LEN(argl[0]),0);
      if( !pEntry ){
        Th_ErrorMessage(interp, "no such command: ", argv[0], argl[0]);
        Th_ErrorMessage(interp, "no such command: ", argv[0], TH1_LEN(argl[0]));
        rc = TH_ERROR;
      }

      /* Call the command procedure. */
      if( rc==TH_OK ){
        Th_Command *p = (Th_Command *)(pEntry->pData);
        const char **azArg = (const char **)argv;
1000
1001
1002
1003
1004
1005
1006
1007

1008
1009
1010
1011
1012
1013
1014
1027
1028
1029
1030
1031
1032
1033

1034
1035
1036
1037
1038
1039
1040
1041







-
+







**   * If iFrame is 0, this means the current frame.
**
**   * If iFrame is negative, then the nth frame up the stack, where
**     n is the absolute value of iFrame. A value of -1 means the
**     calling procedure.
**
**   * If iFrame is +ve, then the nth frame from the bottom of the
**     stack. An iFrame value of 1 means the toplevel (global) frame.
**     stack. An iFrame value of 1 means the top level (global) frame.
*/
static Th_Frame *getFrame(Th_Interp *interp, int iFrame){
  Th_Frame *p = interp->pFrame;
  int i;
  if( iFrame>0 ){
    for(i=0; p; i++){
      p = p->pCaller;
1051
1052
1053
1054
1055
1056
1057


1058
1059
1060
1061
1062
1063
1064
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093







+
+







  if( !interp->pFrame ){
    rc = TH_ERROR;
  }else{
    int nInput = nProgram;

    if( nInput<0 ){
      nInput = th_strlen(zProgram);
    }else{
      nInput = TH1_LEN(nInput);
    }
    rc = thEvalLocal(interp, zProgram, nInput);
  }

  interp->pFrame = pSavedFrame;
  return rc;
}
1093
1094
1095
1096
1097
1098
1099


1100
1101
1102
1103
1104
1105
1106
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137







+
+







  const char *zInner = 0;
  int nInner = 0;
  int isGlobal = 0;
  int i;

  if( nVarname<0 ){
    nVarname = th_strlen(zVarname);
  }else{
    nVarname = TH1_LEN(nVarname);
  }
  nOuter = nVarname;

  /* If the variable name starts with "::", then do the lookup is in the
  ** uppermost (global) frame.
  */
  if( nVarname>2 && zVarname[0]==':' && zVarname[1]==':' ){
1269
1270
1271
1272
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
1300
1301
1302
1303
1304
1305
1306





















1307
1308
1309
1310
1311
1312
1313







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







    Th_ErrorMessage(interp, "no such variable:", zVar, nVar);
    return TH_ERROR;
  }

  return Th_SetResult(interp, pValue->zData, pValue->nData);
}

/*
** If interp has a variable with the given name, its value is returned
** and its length is returned via *nOut if nOut is not NULL.  If
** interp has no such var then NULL is returned without setting any
** error state and *nOut, if not NULL, is set to -1. The returned value
** is owned by the interpreter and may be invalidated the next time
** the interpreter is modified.
*/
const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName,
                            int *nOut){
  Th_Variable *pValue;

  pValue = thFindValue(interp, zVarName, -1, 0, 0, 1, 0);
  if( !pValue || !pValue->zData ){
    if( nOut!=0 ) *nOut = -1;
    return NULL;
  }
  if( nOut!=0 ) *nOut = pValue->nData;
  return pValue->zData;
}

/*
** Return true if variable (zVar, nVar) exists.
*/
int Th_ExistsVar(Th_Interp *interp, const char *zVar, int nVar){
  Th_Variable *pValue = thFindValue(interp, zVar, nVar, 0, 1, 1, 0);
  return pValue && (pValue->zData || pValue->pHash);
}
1322
1323
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
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
1362
1363
1364
1365
1366
1367







+

+






-
+
+
+






-
-
-
-
+
+
+
+







  Th_Interp *interp,
  const char *zVar,
  int nVar,
  const char *zValue,
  int nValue
){
  Th_Variable *pValue;
  int nn;

  nVar = TH1_LEN(nVar);
  pValue = thFindValue(interp, zVar, nVar, 1, 0, 0, 0);
  if( !pValue ){
    return TH_ERROR;
  }

  if( nValue<0 ){
    nValue = th_strlen(zValue);
    nn = th_strlen(zValue);
  }else{
    nn = TH1_LEN(nValue);
  }
  if( pValue->zData ){
    Th_Free(interp, pValue->zData);
    pValue->zData = 0;
  }

  assert(zValue || nValue==0);
  pValue->zData = Th_Malloc(interp, nValue+1);
  pValue->zData[nValue] = '\0';
  th_memcpy(pValue->zData, zValue, nValue);
  assert(zValue || nn==0);
  pValue->zData = Th_Malloc(interp, nn+1);
  pValue->zData[nn] = '\0';
  th_memcpy(pValue->zData, zValue, nn);
  pValue->nData = nValue;

  return TH_OK;
}

/*
** Create a variable link so that accessing variable (zLocal, nLocal) is
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







+
+







** caller is responsible for eventually calling Th_Free() to free
** the returned buffer.
*/
char *th_strdup(Th_Interp *interp, const char *z, int n){
  char *zRes;
  if( n<0 ){
    n = th_strlen(z);
  }else{
    n = TH1_LEN(n);
  }
  zRes = Th_Malloc(interp, n+1);
  th_memcpy(zRes, z, n);
  zRes[n] = '\0';
  return zRes;
}

1517
1518
1519
1520
1521
1522
1523

1524
1525
1526



1527
1528
1529
1530
1531
1532
1533
1533
1534
1535
1536
1537
1538
1539
1540



1541
1542
1543
1544
1545
1546
1547
1548
1549
1550







+
-
-
-
+
+
+








  if( n<0 ){
    n = th_strlen(z);
  }

  if( z && n>0 ){
    char *zResult;
    int nn = TH1_LEN(n);
    zResult = Th_Malloc(pInterp, n+1);
    th_memcpy(zResult, z, n);
    zResult[n] = '\0';
    zResult = Th_Malloc(pInterp, nn+1);
    th_memcpy(zResult, z, nn);
    zResult[nn] = '\0';
    pInterp->zResult = zResult;
    pInterp->nResult = n;
  }

  return TH_OK;
}

1775
1776
1777
1778
1779
1780
1781
1782

1783


1784
1785
1786


1787
1788
1789
1790
1791
1792
1793
1792
1793
1794
1795
1796
1797
1798

1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814







-
+

+
+



+
+







  int i;

  int hasSpecialChar = 0;  /* Whitespace or {}[]'" */
  int hasEscapeChar = 0;   /* '}' without matching '{' to the left or a '\\' */
  int nBrace = 0;

  output.zBuf = *pzList;
  output.nBuf = *pnList;
  output.nBuf = TH1_LEN(*pnList);
  output.nBufAlloc = output.nBuf;
  output.bTaint = 0;
  TH1_XFER_TAINT(output.bTaint, *pnList);

  if( nElem<0 ){
    nElem = th_strlen(zElem);
  }else{
    nElem = TH1_LEN(nElem);
  }
  if( output.nBuf>0 ){
    thBufferAddChar(interp, &output, ' ');
  }

  for(i=0; i<nElem; i++){
    char c = zElem[i];
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
1853
1854
1855
1856
1857
1858
1859

1860
1861
1862
1863

1864
1865
1866
1867
1868

1869
1870
1871
1872

1873
1874
1875
1876

1877
1878
1879
1880
1881
1882
1883
1884







-
+
+


-
+
+
+


-
+
+


-
+



-
+







  Th_Interp *interp,           /* Interpreter context */
  char **pzStr,                /* IN/OUT: Ptr to ptr to list */
  int *pnStr,                  /* IN/OUT: Current length of *pzStr */
  const char *zElem,           /* Data to append */
  int nElem                    /* Length of nElem */
){
  char *zNew;
  int nNew;
  long long int nNew;
  int nn;

  if( nElem<0 ){
    nElem = th_strlen(zElem);
    nn = th_strlen(zElem);
  }else{
    nn = TH1_LEN(nElem);
  }

  nNew = *pnStr + nElem;
  nNew = TH1_LEN(*pnStr) + nn;
  TH1_SIZECHECK(nNew);
  zNew = Th_Malloc(interp, nNew);
  th_memcpy(zNew, *pzStr, *pnStr);
  th_memcpy(&zNew[*pnStr], zElem, nElem);
  th_memcpy(&zNew[TH1_LEN(*pnStr)], zElem, nn);

  Th_Free(interp, *pzStr);
  *pzStr = zNew;
  *pnStr = nNew;
  *pnStr = (int)nNew;

  return TH_OK;
}

/*
** Initialize an interpreter.
*/
2104
2105
2106
2107
2108
2109
2110

2111
2112
2113
2114
2115
2116

2117
2118
2119
2120
2121
2122
2123
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150







+






+







    char *zRight = 0; int nRight = 0;

    /* Evaluate left and right arguments, if they exist. */
    if( pExpr->pLeft ){
      rc = exprEval(interp, pExpr->pLeft);
      if( rc==TH_OK ){
        zLeft = Th_TakeResult(interp, &nLeft);
        nLeft = TH1_LEN(nLeft);
      }
    }
    if( rc==TH_OK && pExpr->pRight ){
      rc = exprEval(interp, pExpr->pRight);
      if( rc==TH_OK ){
        zRight = Th_TakeResult(interp, &nRight);
        nRight = TH1_LEN(nRight);
      }
    }

    /* Convert arguments to their required forms. */
    if( rc==TH_OK ){
      eArgType = pExpr->pOp->eArgType;
      if( eArgType==ARG_NUMBER ){
2158
2159
2160
2161
2162
2163
2164
2165
2166





2167
2168
2169
2170
2171
2172
2173
2185
2186
2187
2188
2189
2190
2191


2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203







-
-
+
+
+
+
+







            rc = TH_ERROR;
            goto finish;
          }
          iRes = iLeft%iRight;
          break;
        case OP_ADD:          iRes = iLeft+iRight;  break;
        case OP_SUBTRACT:     iRes = iLeft-iRight;  break;
        case OP_LEFTSHIFT:    iRes = iLeft<<iRight; break;
        case OP_RIGHTSHIFT:   iRes = iLeft>>iRight; break;
        case OP_LEFTSHIFT: {
          iRes = (int)(((unsigned int)iLeft)<<(iRight&0x1f));
          break;
        }
        case OP_RIGHTSHIFT:   iRes = iLeft>>(iRight&0x1f); break;
        case OP_LT:           iRes = iLeft<iRight;  break;
        case OP_GT:           iRes = iLeft>iRight;  break;
        case OP_LE:           iRes = iLeft<=iRight; break;
        case OP_GE:           iRes = iLeft>=iRight; break;
        case OP_EQ:           iRes = iLeft==iRight; break;
        case OP_NE:           iRes = iLeft!=iRight; break;
        case OP_BITWISE_AND:  iRes = iLeft&iRight;  break;
2451
2452
2453
2454
2455
2456
2457


2458
2459
2460
2461
2462
2463
2464
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496







+
+







  int i;                            /* Loop counter */

  int nToken = 0;
  Expr **apToken = 0;

  if( nExpr<0 ){
    nExpr = th_strlen(zExpr);
  }else{
    nExpr = TH1_LEN(nExpr);
  }

  /* Parse the expression to a list of tokens. */
  rc = exprParse(interp, zExpr, nExpr, &apToken, &nToken);

  /* If the parsing was successful, create an expression tree from
  ** the parsed list of tokens. If successful, apToken[0] is set
2562
2563
2564
2565
2566
2567
2568


2569
2570
2571
2572
2573
2574
2575
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609







+
+







  unsigned int iKey = 0;
  int i;
  Th_HashEntry *pRet;
  Th_HashEntry **ppRet;

  if( nKey<0 ){
    nKey = th_strlen(zKey);
  }else{
    nKey = TH1_LEN(nKey);
  }

  for(i=0; i<nKey; i++){
    iKey = (iKey<<3) ^ iKey ^ zKey[i];
  }
  iKey = iKey % TH_HASHSIZE;

2795
2796
2797
2798
2799
2800
2801


2802
2803
2804
2805
2806
2807
2808
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844







+
+







  int i = 0;
  int iOut = 0;
  int base = 10;
  int (*isdigit)(char) = th_isdigit;

  if( n<0 ){
    n = th_strlen(z);
  }else{
    n = TH1_LEN(n);
  }

  if( n>1 && (z[0]=='-' || z[0]=='+') ){
    i = 1;
  }
  if( (n-i)>2 && z[i]=='0' ){
    if( z[i+1]=='x' || z[i+1]=='X' ){
2854
2855
2856
2857
2858
2859
2860
2861

2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879



2880
2881
2882
2883
2884
2885
2886
2890
2891
2892
2893
2894
2895
2896

2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925







-
+


















+
+
+







int Th_ToDouble(
  Th_Interp *interp,
  const char *z,
  int n,
  double *pfOut
){
  if( !sqlite3IsNumber((const char *)z, 0) ){
    Th_ErrorMessage(interp, "expected number, got: \"", z, n);
    Th_ErrorMessage(interp, "expected number, got: \"", z, TH1_LEN(n));
    return TH_ERROR;
  }

  sqlite3AtoF((const char *)z, pfOut);
  return TH_OK;
}

/*
** Set the result of the interpreter to the th1 representation of
** the integer iVal and return TH_OK.
*/
int Th_SetResultInt(Th_Interp *interp, int iVal){
  int isNegative = 0;
  unsigned int uVal = iVal;
  char zBuf[32];
  char *z = &zBuf[32];

  if( iVal<0 ){
    if( iVal==0x80000000 ){
      return Th_SetResult(interp, "-2147483648", -1);
    }
    isNegative = 1;
    uVal = iVal * -1;
  }
  *(--z) = '\0';
  *(--z) = (char)(48+(uVal%10));
  while( (uVal = (uVal/10))>0 ){
    *(--z) = (char)(48+(uVal%10));
Changes to src/th.h.
1
2
3
4

























5






















6
7
8
9
10
11
12

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
-



+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








/* 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.
**
** TH1 was original developed to run SQLite tests on SymbianOS.  This version
** of TH1 was repurposed as a scripted language for Fossil, and was heavily
** modified for that purpose, beginning in early 2008.
**
** More recently, TH1 has been enhanced to distinguish between regular text
** and "tainted" text.  "Tainted" text is text that might have originated
** from an outside source and hence might not be trustworthy.  To prevent
** cross-site scripting (XSS) and SQL-injections and similar attacks,
** tainted text should not be used for the following purposes:
**
**     *   executed as TH1 script or expression.
**     *   output as HTML or Javascript
**     *   used as part of an SQL query
**
** Tainted text can be converted into a safe form using commands like
** "htmlize".  And some commands ("query" and "expr") know how to use
** potentially tainted variable values directly, and thus can bypass
** the restrictions above.
**
** Whether a string is clean or tainted is determined by its length integer.
** TH1 limits strings to be no more than 0x0fffffff bytes bytes in length
** (about 268MB - more than sufficient for the purposes of Fossil).  The top
** bit of the length integer is the sign bit, of course.  The next three bits
** are reserved.  One of those, the 0x10000000 bit, marks tainted strings.
*/
#define TH1_MX_STRLEN     0x0fffffff      /* Maximum length of a TH1-C string */
#define TH1_TAINT_BIT     0x10000000      /* The taint bit */
#define TH1_SIGN          0x80000000

/* Convert an integer into a string length.  Negative values remain negative */
#define TH1_LEN(X)        ((TH1_SIGN|TH1_MX_STRLEN)&(X))

/* Return true if the string is tainted */
#define TH1_TAINTED(X)    (((X)&TH1_TAINT_BIT)!=0)

/* Remove taint from a string */
#define TH1_RM_TAINT(X)   ((X)&~TH1_TAINT_BIT)

/* Add taint to a string */
#define TH1_ADD_TAINT(X)  ((X)|TH1_TAINT_BIT)

/* If B is tainted, make A tainted too */
#define TH1_XFER_TAINT(A,B)  (A)|=(TH1_TAINT_BIT&(B))

/* Check to see if a string is too big for TH1 */
#define TH1_SIZECHECK(N)  if((N)>TH1_MX_STRLEN){Th_OversizeString();}
void Th_OversizeString(void);

/*
** 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;
22
23
24
25
26
27
28






29
30
31
32
33
34
35
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87







+
+
+
+
+
+








/*
** Create and delete interpreters.
*/
Th_Interp * Th_CreateInterp(Th_Vtab *);
void Th_DeleteInterp(Th_Interp *);

/*
** Report taint in the string zStr,nStr.  That string represents "zTitle"
** If non-zero is returned error out of the caller.
*/
int Th_ReportTaint(Th_Interp*,const char*,const char*zStr,int nStr);

/*
** Evaluate an TH program in the stack frame identified by parameter
** iFrame, according to the following rules:
**
**   * If iFrame is 0, this means the current frame.
**
**   * If iFrame is negative, then the nth frame up the stack, where n is
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
106
107
108
109
110
111
112













113
114
115
116
117
118
119







-
-
-
-
-
-
-
-
-
-
-
-
-







int Th_ExistsVar(Th_Interp *, const char *, int);
int Th_ExistsArrayVar(Th_Interp *, const char *, int);
int Th_GetVar(Th_Interp *, const char *, int);
int Th_SetVar(Th_Interp *, const char *, int, const char *, int);
int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int);
int Th_UnsetVar(Th_Interp *, const char *, int);

/*
** If interp has a variable with the given name, its value is returned
** and its length is returned via *nOut if nOut is not NULL.  If
** interp has no such var then NULL is returned without setting any
** error state and *nOut, if not NULL, is set to 0. The returned value
** is owned by the interpreter and may be invalidated the next time
** the interpreter is modified.
**
** zVarName must be NUL-terminated.
*/
const char * Th_MaybeGetVar(Th_Interp *interp, const char *zVarName,
                            int *nOut);

typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *);

/*
** Register new commands.
*/
int Th_CreateCommand(
  Th_Interp *interp,
Changes to src/th_lang.c.
37
38
39
40
41
42
43
44

45
46
47
48
49
50
51
37
38
39
40
41
42
43

44
45
46
47
48
49
50
51







-
+







    return Th_WrongNumArgs(interp, "catch script ?varname?");
  }

  rc = Th_Eval(interp, 0, argv[1], -1);
  if( argc==3 ){
    int nResult;
    const char *zResult = Th_GetResult(interp, &nResult);
    Th_SetVar(interp, argv[2], argl[2], zResult, nResult);
    Th_SetVar(interp, argv[2], TH1_LEN(argl[2]), zResult, nResult);
  }

  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*
178
179
180
181
182
183
184

185
186
187
188
189
190

191
192
193


194

195
196
197
198
199
200
201
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







+






+



+
+
-
+







  char **azVar = 0;
  int *anVar;
  int nVar;
  char **azValue = 0;
  int *anValue;
  int nValue;
  int ii, jj;
  int bTaint = 0;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "foreach varlist list script");
  }
  rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar);
  if( rc ) return rc;
  TH1_XFER_TAINT(bTaint, argl[2]);
  rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue);
  for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){
    for(jj=0; jj<nVar; jj++){
      int x = anValue[ii+jj];
      TH1_XFER_TAINT(x, bTaint);
      Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], anValue[ii+jj]);
      Th_SetVar(interp, azVar[jj], anVar[jj], azValue[ii+jj], x);
    }
    rc = eval_loopbody(interp, argv[3], argl[3]);
  }
  if( rc==TH_BREAK ) rc = TH_OK;
  Th_Free(interp, azVar);
  Th_Free(interp, azValue);
  return rc;
213
214
215
216
217
218
219

220
221

222
223
224

225
226
227
228
229
230
231
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238







+


+



+







  int argc,
  const char **argv,
  int *argl
){
  char *zList = 0;
  int nList = 0;
  int i;
  int bTaint = 0;

  for(i=1; i<argc; i++){
    TH1_XFER_TAINT(bTaint,argl[i]);
    Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
  }

  TH1_XFER_TAINT(nList, bTaint);
  Th_SetResult(interp, zList, nList);
  Th_Free(interp, zList);

  return TH_OK;
}

/*
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
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







+









+

+



+







  int argc,
  const char **argv,
  int *argl
){
  char *zList = 0;
  int nList = 0;
  int i, rc;
  int bTaint = 0;

  if( argc<2 ){
    return Th_WrongNumArgs(interp, "lappend var ...");
  }
  rc = Th_GetVar(interp, argv[1], argl[1]);
  if( rc==TH_OK ){
    zList = Th_TakeResult(interp, &nList);
  }

  TH1_XFER_TAINT(bTaint, nList);
  for(i=2; i<argc; i++){
    TH1_XFER_TAINT(bTaint, argl[i]);
    Th_ListAppend(interp, &zList, &nList, argv[i], argl[i]);
  }

  TH1_XFER_TAINT(nList, bTaint);
  Th_SetVar(interp, argv[1], argl[1], zList, nList);
  Th_SetResult(interp, zList, nList);
  Th_Free(interp, zList);

  return TH_OK;
}

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
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







+









+



+
+
-
+







){
  int iElem;
  int rc;

  char **azElem;
  int *anElem;
  int nCount;
  int bTaint = 0;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "lindex list index");
  }

  if( TH_OK!=Th_ToInt(interp, argv[2], argl[2], &iElem) ){
    return TH_ERROR;
  }

  TH1_XFER_TAINT(bTaint, argl[1]);
  rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
  if( rc==TH_OK ){
    if( iElem<nCount && iElem>=0 ){
      int sz = anElem[iElem];
      TH1_XFER_TAINT(sz, bTaint);
      Th_SetResult(interp, azElem[iElem], anElem[iElem]);
      Th_SetResult(interp, azElem[iElem], sz);
    }else{
      Th_SetResult(interp, 0, 0);
    }
    Th_Free(interp, azElem);
  }

  return rc;
354
355
356
357
358
359
360

361
362
363

364
365
366
367
368
369
370
369
370
371
372
373
374
375
376
377
378

379
380
381
382
383
384
385
386







+


-
+








  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "lsearch list string");
  }

  rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount);
  if( rc==TH_OK ){
    int nn = TH1_LEN(argl[2]);
    Th_SetResultInt(interp, -1);
    for(i=0; i<nCount; i++){
      if( anElem[i]==argl[2] && 0==memcmp(azElem[i], argv[2], argl[2]) ){
      if( TH1_LEN(anElem[i])==nn && 0==memcmp(azElem[i], argv[2], nn) ){
        Th_SetResultInt(interp, i);
        break;
      }
    }
    Th_Free(interp, azElem);
  }

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
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







-
+
+







-
-
+
+







+
-
+
+











-
-
+
+








  char *zUsage = 0;                /* Build up a usage message here */
  int nUsage = 0;                  /* Number of bytes at zUsage */

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "proc name arglist code");
  }
  if( Th_SplitList(interp, argv[2], argl[2], &azParam, &anParam, &nParam) ){
  if( Th_SplitList(interp, argv[2], TH1_LEN(argl[2]),
                   &azParam, &anParam, &nParam) ){
    return TH_ERROR;
  }

  /* Allocate the new ProcDefn structure. */
  nByte = sizeof(ProcDefn) +                        /* ProcDefn structure */
      (sizeof(char *) + sizeof(int)) * nParam +     /* azParam, anParam */
      (sizeof(char *) + sizeof(int)) * nParam +     /* azDefault, anDefault */
      argl[3] +                                     /* zProgram */
      argl[2];    /* Space for copies of parameter names and default values */
      TH1_LEN(argl[3]) +                            /* zProgram */
      TH1_LEN(argl[2]);   /* Space for copies of param names and dflt values */
  p = (ProcDefn *)Th_Malloc(interp, nByte);

  /* If the last parameter in the parameter list is "args", then set the
  ** ProcDefn.hasArgs flag. The "args" parameter does not require an
  ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays.
  */
  if( nParam>0 ){
    if( TH1_LEN(anParam[nParam-1])==4
    if( anParam[nParam-1]==4 && 0==memcmp(azParam[nParam-1], "args", 4) ){
     && 0==memcmp(azParam[nParam-1], "args", 4)
    ){
      p->hasArgs = 1;
      nParam--;
    }
  }

  p->nParam    = nParam;
  p->azParam   = (char **)&p[1];
  p->anParam   = (int *)&p->azParam[nParam];
  p->azDefault = (char **)&p->anParam[nParam];
  p->anDefault = (int *)&p->azDefault[nParam];
  p->zProgram = (char *)&p->anDefault[nParam];
  memcpy(p->zProgram, argv[3], argl[3]);
  p->nProgram = argl[3];
  memcpy(p->zProgram, argv[3], TH1_LEN(argl[3]));
  p->nProgram = TH1_LEN(argl[3]);
  zSpace = &p->zProgram[p->nProgram];

  for(i=0; i<nParam; i++){
    char **az;
    int *an;
    int n;
    if( Th_SplitList(interp, azParam[i], anParam[i], &az, &an, &n) ){
670
671
672
673
674
675
676
677


678
679
680
681
682
683
684
689
690
691
692
693
694
695

696
697
698
699
700
701
702
703
704







-
+
+







  int argc,
  const char **argv,
  int *argl
){
  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "rename oldcmd newcmd");
  }
  return Th_RenameCommand(interp, argv[1], argl[1], argv[2], argl[2]);
  return Th_RenameCommand(interp, argv[1], TH1_LEN(argl[1]),
                          argv[2], TH1_LEN(argl[2]));
}

/*
** TH Syntax:
**
**   break    ?value...?
**   continue ?value...?
744
745
746
747
748
749
750
751

752
753

754
755
756
757
758
759
760
764
765
766
767
768
769
770

771
772

773
774
775
776
777
778
779
780







-
+

-
+







  int iRes = 0;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string compare str1 str2");
  }

  zLeft = argv[2];
  nLeft = argl[2];
  nLeft = TH1_LEN(argl[2]);
  zRight = argv[3];
  nRight = argl[3];
  nRight = TH1_LEN(argl[3]);

  for(i=0; iRes==0 && i<nLeft && i<nRight; i++){
    iRes = zLeft[i]-zRight[i];
  }
  if( iRes==0 ){
    iRes = nLeft-nRight;
  }
777
778
779
780
781
782
783
784
785


786
787
788
789
790
791
792
797
798
799
800
801
802
803


804
805
806
807
808
809
810
811
812







-
-
+
+







  int nHaystack;
  int iRes = -1;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string first needle haystack");
  }

  nNeedle = argl[2];
  nHaystack = argl[3];
  nNeedle = TH1_LEN(argl[2]);
  nHaystack = TH1_LEN(argl[3]);

  if( nNeedle && nHaystack && nNeedle<=nHaystack ){
    const char *zNeedle = argv[2];
    const char *zHaystack = argv[3];
    int i;

    for(i=0; i<=(nHaystack-nNeedle); i++){
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
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
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
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
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
920
921
922
923
924
925
926
927







-
-
+
+






-
-
+
+
+
+
















-
+



-
+






-
+





-
+





-
+




+
+


-
+
+




















-
-
+
+







){
  int iIndex;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string index string index");
  }

  if( argl[3]==3 && 0==memcmp("end", argv[3], 3) ){
    iIndex = argl[2]-1;
  if( TH1_LEN(argl[3])==3 && 0==memcmp("end", argv[3], 3) ){
    iIndex = TH1_LEN(argl[2])-1;
  }else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){
    Th_ErrorMessage(
        interp, "Expected \"end\" or integer, got:", argv[3], argl[3]);
    return TH_ERROR;
  }

  if( iIndex>=0 && iIndex<argl[2] ){
    return Th_SetResult(interp, &argv[2][iIndex], 1);
  if( iIndex>=0 && iIndex<TH1_LEN(argl[2]) ){
    int sz = 1;
    TH1_XFER_TAINT(sz, argl[2]);
    return Th_SetResult(interp, &argv[2][iIndex], sz);
  }else{
    return Th_SetResult(interp, 0, 0);
  }
}

/*
** TH Syntax:
**
**   string is CLASS STRING
*/
static int string_is_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string is class string");
  }
  if( argl[2]==5 && 0==memcmp(argv[2], "alnum", 5) ){
  if( TH1_LEN(argl[2])==5 && 0==memcmp(argv[2], "alnum", 5) ){
    int i;
    int iRes = 1;

    for(i=0; i<argl[3]; i++){
    for(i=0; i<TH1_LEN(argl[3]); i++){
      if( !th_isalnum(argv[3][i]) ){
        iRes = 0;
      }
    }

    return Th_SetResultInt(interp, iRes);
  }else if( argl[2]==6 && 0==memcmp(argv[2], "double", 6) ){
  }else if( TH1_LEN(argl[2])==6 && 0==memcmp(argv[2], "double", 6) ){
    double fVal;
    if( Th_ToDouble(interp, argv[3], argl[3], &fVal)==TH_OK ){
      return Th_SetResultInt(interp, 1);
    }
    return Th_SetResultInt(interp, 0);
  }else if( argl[2]==7 && 0==memcmp(argv[2], "integer", 7) ){
  }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "integer", 7) ){
    int iVal;
    if( Th_ToInt(interp, argv[3], argl[3], &iVal)==TH_OK ){
      return Th_SetResultInt(interp, 1);
    }
    return Th_SetResultInt(interp, 0);
  }else if( argl[2]==4 && 0==memcmp(argv[2], "list", 4) ){
  }else if( TH1_LEN(argl[2])==4 && 0==memcmp(argv[2], "list", 4) ){
    if( Th_SplitList(interp, argv[3], argl[3], 0, 0, 0)==TH_OK ){
      return Th_SetResultInt(interp, 1);
    }
    return Th_SetResultInt(interp, 0);
  }else if( TH1_LEN(argl[2])==7 && 0==memcmp(argv[2], "tainted", 7) ){
    return Th_SetResultInt(interp, TH1_TAINTED(argl[3]));
  }else{
    Th_ErrorMessage(interp,
        "Expected alnum, double, integer, or list, got:", argv[2], argl[2]);
        "Expected alnum, double, integer, list, or tainted, got:",
        argv[2], TH1_LEN(argl[2]));
    return TH_ERROR;
  }
}

/*
** TH Syntax:
**
**   string last NEEDLE HAYSTACK
*/
static int string_last_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int nNeedle;
  int nHaystack;
  int iRes = -1;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string last needle haystack");
  }

  nNeedle = argl[2];
  nHaystack = argl[3];
  nNeedle = TH1_LEN(argl[2]);
  nHaystack = TH1_LEN(argl[3]);

  if( nNeedle && nHaystack && nNeedle<=nHaystack ){
    const char *zNeedle = argv[2];
    const char *zHaystack = argv[3];
    int i;

    for(i=nHaystack-nNeedle; i>=0; i--){
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
1006
1007


1008
1009


1010

1011
1012
1013
1014
1015
1016
1017
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
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







-
+











-
+






-
-
+
+
















+





-
-
+
+


-
+







-
+

+
+

-
+












+
-
+









-
+
+
+
+

-
-
+
+


+
+
-
+







*/
static int string_length_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "string length string");
  }
  return Th_SetResultInt(interp, argl[2]);
  return Th_SetResultInt(interp, TH1_LEN(argl[2]));
}

/*
** TH Syntax:
**
**   string match PATTERN STRING
**
*/
static int string_match_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  extern char *fossil_strndup(const char*,int);
  extern char *fossil_strndup(const char*,ssize_t);
  extern void fossil_free(void*);
  char *zPat, *zStr;
  int rc;
  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string match pattern string");
  }
  zPat = fossil_strndup(argv[2],argl[2]);
  zStr = fossil_strndup(argv[3],argl[3]);
  zPat = fossil_strndup(argv[2],TH1_LEN(argl[2]));
  zStr = fossil_strndup(argv[3],TH1_LEN(argl[3]));
  rc = sqlite3_strglob(zPat,zStr);
  fossil_free(zPat);
  fossil_free(zStr);
  return Th_SetResultInt(interp, !rc);
}

/*
** TH Syntax:
**
**   string range STRING FIRST LAST
*/
static int string_range_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int iStart;
  int iEnd;
  int sz;

  if( argc!=5 ){
    return Th_WrongNumArgs(interp, "string range string first last");
  }

  if( argl[4]==3 && 0==memcmp("end", argv[4], 3) ){
    iEnd = argl[2];
  if( TH1_LEN(argl[4])==3 && 0==memcmp("end", argv[4], 3) ){
    iEnd = TH1_LEN(argl[2]);
  }else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){
    Th_ErrorMessage(
        interp, "Expected \"end\" or integer, got:", argv[4], argl[4]);
        interp, "Expected \"end\" or integer, got:", argv[4], TH1_LEN(argl[4]));
    return TH_ERROR;
  }
  if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){
    return TH_ERROR;
  }

  if( iStart<0 ) iStart = 0;
  if( iEnd>=argl[2] ) iEnd = argl[2]-1;
  if( iEnd>=TH1_LEN(argl[2]) ) iEnd = TH1_LEN(argl[2])-1;
  if( iStart>iEnd ) iEnd = iStart-1;
  sz = iEnd - iStart + 1;
  TH1_XFER_TAINT(sz, argl[2]);

  return Th_SetResult(interp, &argv[2][iStart], iEnd-iStart+1);
  return Th_SetResult(interp, &argv[2][iStart], sz);
}

/*
** TH Syntax:
**
**   string repeat STRING COUNT
*/
static int string_repeat_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int n;
  int i;
  int sz;
  int nByte;
  long long int nByte;
  char *zByte;

  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "string repeat string n");
  }
  if( Th_ToInt(interp, argv[3], argl[3], &n) ){
    return TH_ERROR;
  }

  nByte = argl[2] * n;
  nByte = n;
  sz = TH1_LEN(argl[2]);
  nByte *= sz;
  TH1_SIZECHECK(nByte+1);
  zByte = Th_Malloc(interp, nByte+1);
  for(i=0; i<nByte; i+=argl[2]){
    memcpy(&zByte[i], argv[2], argl[2]);
  for(i=0; i<nByte; i+=sz){
    memcpy(&zByte[i], argv[2], sz);
  }

  n = nByte;
  TH1_XFER_TAINT(n, argl[2]);
  Th_SetResult(interp, zByte, nByte);
  Th_SetResult(interp, zByte, n);
  Th_Free(interp, zByte);
  return TH_OK;
}

/*
** TH Syntax:
**
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
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







-
-
+
+


-
+


+

















-
+







  int n;
  const char *z;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "string trim string");
  }
  z = argv[2];
  n = argl[2];
  if( argl[1]<5 || argv[1][4]=='l' ){
  n = TH1_LEN(argl[2]);
  if( TH1_LEN(argl[1])<5 || argv[1][4]=='l' ){
    while( n && th_isspace(z[0]) ){ z++; n--; }
  }
  if( argl[1]<5 || argv[1][4]=='r' ){
  if( TH1_LEN(argl[1])<5 || argv[1][4]=='r' ){
    while( n && th_isspace(z[n-1]) ){ n--; }
  }
  TH1_XFER_TAINT(n, argl[2]);
  Th_SetResult(interp, z, n);
  return TH_OK;
}

/*
** TH Syntax:
**
**   info exists VARNAME
*/
static int info_exists_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int rc;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "info exists var");
  }
  rc = Th_ExistsVar(interp, argv[2], argl[2]);
  rc = Th_ExistsVar(interp, argv[2], TH1_LEN(argl[2]));
  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*
** TH Syntax:
**
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
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
1169
1170
1171
1172
1173
1174
1175
1176

1177
1178
1179
1180
1181
1182
1183
1184







-
+



















-
+







  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int rc;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "array exists var");
  }
  rc = Th_ExistsArrayVar(interp, argv[2], argl[2]);
  rc = Th_ExistsArrayVar(interp, argv[2], TH1_LEN(argl[2]));
  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*
** TH Syntax:
**
**   array names VARNAME
*/
static int array_names_command(
  Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl
){
  int rc;
  char *zElem = 0;
  int nElem = 0;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "array names varname");
  }
  rc = Th_ListAppendArray(interp, argv[2], argl[2], &zElem, &nElem);
  rc = Th_ListAppendArray(interp, argv[2], TH1_LEN(argl[2]), &zElem, &nElem);
  if( rc!=TH_OK ){
    return rc;
  }
  Th_SetResult(interp, zElem, nElem);
  if( zElem ) Th_Free(interp, zElem);
  return TH_OK;
}
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
1185
1186
1187


1188
1189


1190
1191
1192
1193
1194
1195
1196
1194
1195
1196
1197
1198
1199
1200

1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215

1216
1217
1218
1219
1220
1221
1222

1223
1224
1225

1226
1227
1228
1229
1230
1231
1232
1233
1234







-
+














-
+
+





-
+
+

-
+
+







  int argc,
  const char **argv,
  int *argl
){
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "unset var");
  }
  return Th_UnsetVar(interp, argv[1], argl[1]);
  return Th_UnsetVar(interp, argv[1], TH1_LEN(argl[1]));
}

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]) ){
      if( th_strlen(zName)==TH1_LEN(argl[1])
       && 0==memcmp(zName, argv[1], TH1_LEN(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]);
    Th_ErrorMessage(interp, "Expected sub-command for",
                            argv[0], TH1_LEN(argl[0]));
  }else{
    Th_ErrorMessage(interp, "Expected sub-command, got:", argv[1], argl[1]);
    Th_ErrorMessage(interp, "Expected sub-command, got:",
                             argv[1], TH1_LEN(argl[1]));
  }
  return TH_ERROR;
}

/*
** TH Syntax:
**
1317
1318
1319
1320
1321
1322
1323
1324

1325
1326
1327
1328
1329
1330
1331
1355
1356
1357
1358
1359
1360
1361

1362
1363
1364
1365
1366
1367
1368
1369







-
+







  int *argl
){
  int iFrame = -1;

  if( argc!=2 && argc!=3 ){
    return Th_WrongNumArgs(interp, "uplevel ?level? script...");
  }
  if( argc==3 && TH_OK!=thToFrame(interp, argv[1], argl[1], &iFrame) ){
  if( argc==3 && TH_OK!=thToFrame(interp, argv[1], TH1_LEN(argl[1]), &iFrame) ){
    return TH_ERROR;
  }
  return Th_Eval(interp, iFrame, argv[argc-1], -1);
}

/*
** TH Syntax:
1340
1341
1342
1343
1344
1345
1346
1347

1348
1349
1350
1351
1352
1353
1354
1355


1356
1357
1358
1359
1360
1361
1362
1378
1379
1380
1381
1382
1383
1384

1385
1386
1387
1388
1389
1390
1391
1392

1393
1394
1395
1396
1397
1398
1399
1400
1401







-
+







-
+
+







  int *argl
){
  int iVar = 1;
  int iFrame = -1;
  int rc = TH_OK;
  int i;

  if( TH_OK==thToFrame(0, argv[1], argl[1], &iFrame) ){
  if( TH_OK==thToFrame(0, argv[1], TH1_LEN(argl[1]), &iFrame) ){
    iVar++;
  }
  if( argc==iVar || (argc-iVar)%2 ){
    return Th_WrongNumArgs(interp,
        "upvar frame othervar myvar ?othervar myvar...?");
  }
  for(i=iVar; rc==TH_OK && i<argc; i=i+2){
    rc = Th_LinkVar(interp, argv[i+1], argl[i+1], iFrame, argv[i], argl[i]);
    rc = Th_LinkVar(interp, argv[i+1], TH1_LEN(argl[i+1]),
                    iFrame, argv[i], TH1_LEN(argl[i]));
  }
  return rc;
}

/*
** TH Syntax:
**
Changes to src/th_main.c.
29
30
31
32
33
34
35
36
37
38

39
40
41
42
43
44
45
29
30
31
32
33
34
35



36
37
38
39
40
41
42
43







-
-
-
+







*/
#define TH_INIT_NONE        ((u32)0x00000000) /* No flags. */
#define TH_INIT_NEED_CONFIG ((u32)0x00000001) /* Open configuration first? */
#define TH_INIT_FORCE_TCL   ((u32)0x00000002) /* Force Tcl to be enabled? */
#define TH_INIT_FORCE_RESET ((u32)0x00000004) /* Force TH1 commands re-added? */
#define TH_INIT_FORCE_SETUP ((u32)0x00000008) /* Force eval of setup script? */
#define TH_INIT_NO_REPO     ((u32)0x00000010) /* Skip opening repository. */
#define TH_INIT_NO_ENCODE   ((u32)0x00000020) /* Do not html-encode sendText()*/
                                              /* output. */
#define TH_INIT_MASK        ((u32)0x0000003F) /* All possible init flags. */
#define TH_INIT_MASK        ((u32)0x0000001F) /* All possible init flags. */

/*
** Useful and/or "well-known" combinations of flag values.
*/
#define TH_INIT_DEFAULT     (TH_INIT_NONE)      /* Default flags. */
#define TH_INIT_HOOK        (TH_INIT_NEED_CONFIG | TH_INIT_FORCE_SETUP)
#define TH_INIT_FORBID_MASK (TH_INIT_FORCE_TCL) /* Illegal from a script. */
74
75
76
77
78
79
80
81

82
83
84
85
86
87
88
72
73
74
75
76
77
78

79
80
81
82
83
84
85
86







-
+







/*
** When memory debugging is enabled, use our custom memory allocator.
*/
#if defined(TH_MEMDEBUG)
/*
** Global variable counting the number of outstanding calls to malloc()
** made by the th1 implementation. This is used to catch memory leaks
** in the interpreter. Obviously, it also means th1 is not threadsafe.
** in the interpreter. Obviously, it also means th1 is not thread-safe.
*/
static int nOutstandingMalloc = 0;

/*
** Implementations of malloc() and free() to pass to the interpreter.
*/
static void *xMalloc(unsigned int n){
151
152
153
154
155
156
157
158
159
160



161
162
163
164
165
166
167
149
150
151
152
153
154
155



156
157
158
159
160
161
162
163
164
165







-
-
-
+
+
+







    fossil_print("\n------------------ BEGIN TRACE LOG ------------------\n");
    fossil_print("%s", blob_str(&g.thLog));
    fossil_print("\n------------------- END TRACE LOG -------------------\n");
  }
}

/*
** - adopted from ls_cmd_rev in checkin.c
** - adopted commands/error handling for usage within th1
** - interface adopted to allow result creation as TH1 List
** - adapted from ls_cmd_rev in checkin.c
** - adapted commands/error handling for usage within th1
** - interface adapted to allow result creation as TH1 List
**
** Takes a check-in identifier in zRev and an optiona glob pattern in zGLOB
** as parameter returns a TH list in pzList,pnList with filenames matching
** glob pattern with the checking
*/
static void dir_cmd_rev(
  Th_Interp *interp,
260
261
262
263
264
265
266
267

268
269
270
271
272
273
274
258
259
260
261
262
263
264

265
266
267
268
269
270
271
272







-
+







  const char **argv,
  int *argl
){
  char *zOut;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "httpize STRING");
  }
  zOut = httpize((char*)argv[1], argl[1]);
  zOut = httpize((char*)argv[1], TH1_LEN(argl[1]));
  Th_SetResult(interp, zOut, -1);
  free(zOut);
  return TH_OK;
}

/*
** True if output is enabled.  False if disabled.
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
287
288
289
290
291
292
293

294




295




































296
297
298
299
300
301
302







-
+
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







){
  int rc;
  if( argc<2 || argc>3 ){
    return Th_WrongNumArgs(interp, "enable_output [LABEL] BOOLEAN");
  }
  rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &enableOutput);
  if( g.thTrace ){
    Th_Trace("enable_output {%.*s} -> %d<br>\n", argl[1],argv[1],enableOutput);
    Th_Trace("enable_output {%.*s} -> %d<br>\n",
  }
  return rc;
}

             TH1_LEN(argl[1]),argv[1],enableOutput);
/*
** TH1 command: enable_htmlify ?BOOLEAN?
**
** Enable or disable the HTML escaping done by all output which
** originates from TH1 (via sendText()).
**
** If passed no arguments it instead returns 0 or 1 to indicate the
** current state.
*/
static int enableHtmlifyCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  int rc = 0, buul;
  if( argc>3 ){
    return Th_WrongNumArgs(interp,
                           "enable_htmlify [TRACE_LABEL] ?BOOLEAN?");
  }
  buul = (TH_INIT_NO_ENCODE & g.th1Flags) ? 0 : 1;
  Th_SetResultInt(g.interp, buul);
  if(argc>1){
    if( g.thTrace ){
      Th_Trace("enable_htmlify {%.*s} -> %d<br>\n",
               argl[1],argv[1],buul);
    }
    rc = Th_ToInt(interp, argv[argc-1], argl[argc-1], &buul);
    if(!rc){
      if(buul){
        g.th1Flags &= ~TH_INIT_NO_ENCODE;
      }else{
        g.th1Flags |= TH_INIT_NO_ENCODE;
      }
    }
  }
  return rc;
}

/*
** Returns a name for a TH1 return code.
*/
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
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







-
+
-




-
+



-
-
-

-
+
+
+
+
+







  return tmp;
}

/*
** Send text to the appropriate output: If pOut is not NULL, it is
** appended there, else to the console or to the CGI reply buffer.
** Escape all characters with special meaning to HTML if the encode
** parameter is true, with the exception that that flag is ignored if
** parameter is true.
** g.th1Flags has the TH_INIT_NO_ENCODE flag.
**
** If pOut is NULL and the global pThOut is not then that blob
** is used for output.
*/
static void sendText(Blob * pOut, const char *z, int n, int encode){
static void sendText(Blob *pOut, const char *z, int n, int encode){
  if(0==pOut && pThOut!=0){
    pOut = pThOut;
  }
  if(TH_INIT_NO_ENCODE & g.th1Flags){
    encode = 0;
  }
  if( enableOutput && n ){
    if( n<0 ) n = strlen(z);
    if( n<0 ){
      n = strlen(z);
    }else{
      n = TH1_LEN(n);
    }
    if( encode ){
      z = htmlize(z, n);
      n = strlen(z);
    }
    if(pOut!=0){
      blob_append(pOut, z, n);
    }else if( g.cgiOutput ){
523
524
525
526
527
528
529


530
531
532






533

534
535
536
537
538
539
540
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







+
+



+
+
+
+
+
+
-
+







static int putsCmd(
  Th_Interp *interp,
  void *pConvert,
  int argc,
  const char **argv,
  int *argl
){
  int encode = *(unsigned int*)pConvert;
  int n;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "puts STRING");
  }
  n = argl[1];
  if( encode==0 && n>0 && TH1_TAINTED(n) ){
    if( Th_ReportTaint(interp, "output string", argv[1], n) ){
      return TH_ERROR;
    }
  }
  sendText(0,(char*)argv[1], argl[1], *(unsigned int*)pConvert);
  sendText(0,(char*)argv[1], TH1_LEN(n), encode);
  return TH_OK;
}

/*
** TH1 command: redirect URL ?withMethod?
**
** Issues an HTTP redirect to the specified URL and then exits the process.
555
556
557
558
559
560
561





562
563
564
565
566
567
568
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540







+
+
+
+
+







  if( argc!=2 && argc!=3 ){
    return Th_WrongNumArgs(interp, "redirect URL ?withMethod?");
  }
  if( argc==3 ){
    if( Th_ToInt(interp, argv[2], argl[2], &withMethod) ){
      return TH_ERROR;
    }
  }
  if( TH1_TAINTED(argl[1])
   && Th_ReportTaint(interp,"redirect URL",argv[1],argl[1])
  ){
    return TH_ERROR;
  }
  if( withMethod ){
    cgi_redirect_with_method(argv[1]);
  }else{
    cgi_redirect(argv[1]);
  }
  Th_SetResult(interp, argv[1], argl[1]); /* NOT REACHED */
658
659
660
661
662
663
664
665

666
667
668
669
670
671
672
630
631
632
633
634
635
636

637
638
639
640
641
642
643
644







-
+







  Blob src, title, body;
  char *zValue = 0;
  int nValue = 0;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "markdown STRING");
  }
  blob_zero(&src);
  blob_init(&src, (char*)argv[1], argl[1]);
  blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
  blob_zero(&title); blob_zero(&body);
  markdown_to_html(&src, &title, &body);
  Th_ListAppend(interp, &zValue, &nValue, blob_str(&title), blob_size(&title));
  Th_ListAppend(interp, &zValue, &nValue, blob_str(&body), blob_size(&body));
  Th_SetResult(interp, zValue, nValue);
  Th_Free(interp, zValue);
  return TH_OK;
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
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







-
+





+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


















-
+







){
  int flags = WIKI_INLINE | WIKI_NOBADLINKS | *(unsigned int*)p;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "wiki STRING");
  }
  if( enableOutput ){
    Blob src;
    blob_init(&src, (char*)argv[1], argl[1]);
    blob_init(&src, (char*)argv[1], TH1_LEN(argl[1]));
    wiki_convert(&src, 0, flags);
    blob_reset(&src);
  }
  return TH_OK;
}

/*
** TH1 command: wiki_assoc STRING STRING
**
** Render an associated wiki page.  The first string is the namespace
** (e.g. "checkin", "branch", "ticket"). The second is the ID of the
** associated object. See wiki_render_associated().
*/
static int wikiAssocCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "wiki_assoc STRING STRING");
  }
  wiki_render_associated((char*)argv[1], (char*)argv[2], WIKIASSOC_FULL_TITLE);
  return TH_OK;
}

/*
** TH1 command: htmlize STRING
**
** Escape all characters of STRING which have special meaning in HTML.
** Return a new string result.
*/
static int htmlizeCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  char *zOut;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "htmlize STRING");
  }
  zOut = htmlize((char*)argv[1], argl[1]);
  zOut = htmlize((char*)argv[1], TH1_LEN(argl[1]));
  Th_SetResult(interp, zOut, -1);
  free(zOut);
  return TH_OK;
}

/*
** TH1 command: encode64 STRING
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
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







-
+




















-
+







  const char **argv,
  int *argl
){
  char *zOut;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "encode64 STRING");
  }
  zOut = encode64((char*)argv[1], argl[1]);
  zOut = encode64((char*)argv[1], TH1_LEN(argl[1]));
  Th_SetResult(interp, zOut, -1);
  free(zOut);
  return TH_OK;
}

/*
** TH1 command: date
**
** Return a string which is the current time and date.  If the
** -local option is used, the date appears using localtime instead
** of UTC.
*/
static int dateCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  char *zOut;
  if( argc>=2 && argl[1]==6 && memcmp(argv[1],"-local",6)==0 ){
  if( argc>=2 && TH1_LEN(argl[1])==6 && memcmp(argv[1],"-local",6)==0 ){
    zOut = db_text("??", "SELECT datetime('now',toLocal())");
  }else{
    zOut = db_text("??", "SELECT datetime('now')");
  }
  Th_SetResult(interp, zOut, -1);
  free(zOut);
  return TH_OK;
787
788
789
790
791
792
793
794

795
796

797
798
799
800
801
802
803
780
781
782
783
784
785
786

787
788

789
790
791
792
793
794
795
796







-
+

-
+







  char *zCapList = 0;
  int nCapList = 0;
  if( argc<2 ){
    return Th_WrongNumArgs(interp, "hascap STRING ...");
  }
  for(i=1; rc==1 && i<argc; i++){
    if( g.thTrace ){
      Th_ListAppend(interp, &zCapList, &nCapList, argv[i], argl[i]);
      Th_ListAppend(interp, &zCapList, &nCapList, argv[i], TH1_LEN(argl[i]));
    }
    rc = login_has_capability((char*)argv[i],argl[i],*(int*)p);
    rc = login_has_capability((char*)argv[i],TH1_LEN(argl[i]),*(int*)p);
  }
  if( g.thTrace ){
    Th_Trace("[%s %#h] => %d<br>\n", argv[0], nCapList, zCapList, rc);
    Th_Free(interp, zCapList);
  }
  Th_SetResultInt(interp, rc);
  return TH_OK;
835
836
837
838
839
840
841
842

843
844
845
846
847
848
849
828
829
830
831
832
833
834

835
836
837
838
839
840
841
842







-
+







  int nCap;
  int rc;
  int i;

  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "capexpr EXPR");
  }
  rc = Th_SplitList(interp, argv[1], argl[1], &azCap, &anCap, &nCap);
  rc = Th_SplitList(interp, argv[1], TH1_LEN(argl[1]), &azCap, &anCap, &nCap);
  if( rc ) return rc;
  rc = 0;
  for(i=0; i<nCap; i++){
    if( azCap[i][0]=='!' ){
      rc = !login_has_capability(azCap[i]+1, anCap[i]-1, 0);
    }else if( azCap[i][0]=='@' ){
      rc = login_has_capability(azCap[i]+1, anCap[i]-1, LOGIN_ANON);
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
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







+
-
+










-
+







  int rc = 1, i, j;
  unsigned int searchCap = search_restrict(SRCH_ALL);
  if( argc<2 ){
    return Th_WrongNumArgs(interp, "hascap STRING ...");
  }
  for(i=1; i<argc && rc; i++){
    int match = 0;
    int nn = TH1_LEN(argl[i]);
    for(j=0; j<argl[i]; j++){
    for(j=0; j<nn; j++){
      switch( argv[i][j] ){
        case 'c':  match |= searchCap & SRCH_CKIN;  break;
        case 'd':  match |= searchCap & SRCH_DOC;   break;
        case 't':  match |= searchCap & SRCH_TKT;   break;
        case 'w':  match |= searchCap & SRCH_WIKI;  break;
      }
    }
    if( !match ) rc = 0;
  }
  if( g.thTrace ){
    Th_Trace("[searchable %#h] => %d<br>\n", argl[1], argv[1], rc);
    Th_Trace("[searchable %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
  }
  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*
** TH1 command: hasfeature STRING
1028
1029
1030
1031
1032
1033
1034
1035

1036
1037
1038
1039
1040
1041
1042
1022
1023
1024
1025
1026
1027
1028

1029
1030
1031
1032
1033
1034
1035
1036







-
+







    rc = 1;
  }
#endif
  else if( 0 == fossil_strnicmp( zArg, "markdown\0", 9 ) ){
    rc = 1;
  }
  if( g.thTrace ){
    Th_Trace("[hasfeature %#h] => %d<br>\n", argl[1], zArg, rc);
    Th_Trace("[hasfeature %#h] => %d<br>\n", TH1_LEN(argl[1]), zArg, rc);
  }
  Th_SetResultInt(interp, rc);
  return TH_OK;
}


/*
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
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







-
+











+



+
-
+



-
+







  return TH_OK;
}


/*
** TH1 command: anycap STRING
**
** Return true if the current user user
** Return true if the current user
** has any one of the capabilities listed in STRING.
*/
static int anycapCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  int rc = 0;
  int i;
  int nn;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "anycap STRING");
  }
  nn = TH1_LEN(argl[1]);
  for(i=0; rc==0 && i<argl[1]; i++){
  for(i=0; rc==0 && i<nn; i++){
    rc = login_has_capability((char*)&argv[1][i],1,0);
  }
  if( g.thTrace ){
    Th_Trace("[anycap %#h] => %d<br>\n", argl[1], argv[1], rc);
    Th_Trace("[anycap %#h] => %d<br>\n", TH1_LEN(argl[1]), argv[1], rc);
  }
  Th_SetResultInt(interp, rc);
  return TH_OK;
}

/*
** TH1 command: combobox NAME TEXT-LIST NUMLINES
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
1142
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







-
+








-
-
+
+

+







){
  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
  }
  if( enableOutput ){
    int height;
    Blob name;
    int nValue;
    int nValue = 0;
    const char *zValue;
    char *z, *zH;
    int nElem;
    int *aszElem;
    char **azElem;
    int i;

    if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR;
    Th_SplitList(interp, argv[2], argl[2], &azElem, &aszElem, &nElem);
    blob_init(&name, (char*)argv[1], argl[1]);
    Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), &azElem, &aszElem, &nElem);
    blob_init(&name, (char*)argv[1], TH1_LEN(argl[1]));
    zValue = Th_Fetch(blob_str(&name), &nValue);
    nValue = TH1_LEN(nValue);
    zH = htmlize(blob_buffer(&name), blob_size(&name));
    z = mprintf("<select id=\"%s\" name=\"%s\" size=\"%d\">", zH, zH, height);
    free(zH);
    sendText(0,z, -1, 0);
    free(z);
    blob_reset(&name);
    for(i=0; i<nElem; i++){
1224
1225
1226
1227
1228
1229
1230
1231

1232
1233
1234
1235
1236
1237
1238
1221
1222
1223
1224
1225
1226
1227

1228
1229
1230
1231
1232
1233
1234
1235







-
+







  int iMin, iMax;
  if( argc!=4 ){
    return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
  }
  if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
  if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
  z = argv[1];
  size = argl[1];
  size = TH1_LEN(argl[1]);
  for(n=1, i=0; i<size; i++){
    if( z[i]=='\n' ){
      n++;
      if( n>=iMax ) break;
    }
  }
  if( n<iMin ) n = iMin;
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
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435

1436
1437
1438
1439
1440
1441
1442
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
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436

1437
1438
1439
1440
1441
1442
1443
1444







-
+
+


















+
+






-
+
+
+


















-
+







  }else if( fossil_strnicmp(argv[1], "user\0", 5)==0 ){
    Th_SetResult(interp, g.zLogin ? g.zLogin : zDefault, -1);
    return TH_OK;
  }else if( fossil_strnicmp(argv[1], "vfs\0", 4)==0 ){
    Th_SetResult(interp, g.zVfsName ? g.zVfsName : zDefault, -1);
    return TH_OK;
  }else{
    Th_ErrorMessage(interp, "unsupported global state:", argv[1], argl[1]);
    Th_ErrorMessage(interp, "unsupported global state:",
                            argv[1], TH1_LEN(argl[1]));
    return TH_ERROR;
  }
}

/*
** TH1 command: getParameter NAME ?DEFAULT?
**
** Return the value of the specified query parameter or the specified default
** value when there is no matching query parameter.
*/
static int getParameterCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  const char *zDefault = 0;
  const char *zVal;
  int sz;
  if( argc!=2 && argc!=3 ){
    return Th_WrongNumArgs(interp, "getParameter NAME ?DEFAULT?");
  }
  if( argc==3 ){
    zDefault = argv[2];
  }
  Th_SetResult(interp, cgi_parameter(argv[1], zDefault), -1);
  zVal = cgi_parameter(argv[1], zDefault);
  sz = th_strlen(zVal);
  Th_SetResult(interp, zVal, TH1_ADD_TAINT(sz));
  return TH_OK;
}

/*
** TH1 command: setParameter NAME VALUE
**
** Sets the value of the specified query parameter.
*/
static int setParameterCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "setParameter NAME VALUE");
  }
  cgi_replace_parameter(mprintf("%s", argv[1]), mprintf("%s", argv[2]));
  cgi_replace_parameter(fossil_strdup(argv[1]), fossil_strdup(argv[2]));
  return TH_OK;
}

/*
** TH1 command: reinitialize ?FLAGS?
**
** Reinitializes the TH1 interpreter using the specified flags.
1825
1826
1827
1828
1829
1830
1831





































1832
1833
1834
1835
1836
1837
1838
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
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  char zUTime[50];
  fossil_cpu_times(0, &x);
  sqlite3_snprintf(sizeof(zUTime), zUTime, "%llu", x);
  Th_SetResult(interp, zUTime, -1);
  return TH_OK;
}

/*
** TH1 command: taint STRING
**
** Return a copy of STRING that is marked as tainted.
*/
static int taintCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "STRING");
  }
  Th_SetResult(interp, argv[1], TH1_ADD_TAINT(argl[1]));
  return TH_OK;
}

/*
** TH1 command: untaint STRING
**
** Return a copy of STRING that is marked as untainted.
*/
static int untaintCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "STRING");
  }
  Th_SetResult(interp, argv[1], TH1_LEN(argl[1]));
  return TH_OK;
}

/*
** TH1 command: randhex  N
**
** Return N*2 random hexadecimal digits with N<50.  If N is omitted,
** use a value of 10.
*/
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
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

1964
1965
1966
1967
1968

1969
1970
1971
1972
1973
1974
1975
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
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
1994
1995

1996
1997
1998
1999
2000
2001
2002
2003
2004
2005

2006
2007
2008

2009
2010

2011
2012
2013
2014
2015

2016
2017
2018
2019
2020
2021
2022
2023







+
-
+
+















+
+
+
+
+
+
+




-
+




















-
+









-
+


-
+

-
+




-
+







  const char *zTail;
  int n, i;
  int res = TH_OK;
  int nVar;
  char *zErr = 0;
  int noComplain = 0;

  if( argc>3 && TH1_LEN(argl[1])==11
  if( argc>3 && argl[1]==11 && strncmp(argv[1], "-nocomplain", 11)==0 ){
   && strncmp(argv[1], "-nocomplain", 11)==0
  ){
    argc--;
    argv++;
    argl++;
    noComplain = 1;
  }
  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "query SQL CODE");
  }
  if( g.db==0 ){
    if( noComplain ) return TH_OK;
    Th_ErrorMessage(interp, "database is not open", 0, 0);
    return TH_ERROR;
  }
  zSql = argv[1];
  nSql = argl[1];
  if( TH1_TAINTED(nSql) ){
    if( Th_ReportTaint(interp,"query SQL",zSql,nSql) ){
      return TH_ERROR;
    }
    nSql = TH1_LEN(nSql);
  }

  while( res==TH_OK && nSql>0 ){
    zErr = 0;
    report_restrict_sql(&zErr);
    g.dbIgnoreErrors++;
    rc = sqlite3_prepare_v2(g.db, argv[1], argl[1], &pStmt, &zTail);
    rc = sqlite3_prepare_v2(g.db, argv[1], TH1_LEN(argl[1]), &pStmt, &zTail);
    g.dbIgnoreErrors--;
    report_unrestrict_sql();
    if( rc!=0 || zErr!=0 ){
      if( noComplain ) return TH_OK;
      Th_ErrorMessage(interp, "SQL error: ",
                      zErr ? zErr : sqlite3_errmsg(g.db), -1);
      return TH_ERROR;
    }
    n = (int)(zTail - zSql);
    zSql += n;
    nSql -= n;
    if( pStmt==0 ) continue;
    nVar = sqlite3_bind_parameter_count(pStmt);
    for(i=1; i<=nVar; i++){
      const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
      int szVar = zVar ? th_strlen(zVar) : 0;
      if( szVar>1 && zVar[0]=='$'
       && Th_GetVar(interp, zVar+1, szVar-1)==TH_OK ){
        int nVal;
        const char *zVal = Th_GetResult(interp, &nVal);
        sqlite3_bind_text(pStmt, i, zVal, nVal, SQLITE_TRANSIENT);
        sqlite3_bind_text(pStmt, i, zVal, TH1_LEN(nVal), SQLITE_TRANSIENT);
      }
    }
    while( res==TH_OK && ignore_errors_step(pStmt)==SQLITE_ROW ){
      int nCol = sqlite3_column_count(pStmt);
      for(i=0; i<nCol; i++){
        const char *zCol = sqlite3_column_name(pStmt, i);
        int szCol = th_strlen(zCol);
        const char *zVal = (const char*)sqlite3_column_text(pStmt, i);
        int szVal = sqlite3_column_bytes(pStmt, i);
        Th_SetVar(interp, zCol, szCol, zVal, szVal);
        Th_SetVar(interp, zCol, szCol, zVal, TH1_ADD_TAINT(szVal));
      }
      if( g.thTrace ){
        Th_Trace("query_eval {<pre>%#h</pre>}<br>\n", argl[2], argv[2]);
        Th_Trace("query_eval {<pre>%#h</pre>}<br>\n",TH1_LEN(argl[2]),argv[2]);
      }
      res = Th_Eval(interp, 0, argv[2], argl[2]);
      res = Th_Eval(interp, 0, argv[2], TH1_LEN(argl[2]));
      if( g.thTrace ){
        int nTrRes;
        char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
        Th_Trace("[query_eval] => %h {%#h}<br>\n",
                 Th_ReturnCodeName(res, 0), nTrRes, zTrRes);
                 Th_ReturnCodeName(res, 0), TH1_LEN(nTrRes), zTrRes);
      }
      if( res==TH_BREAK || res==TH_CONTINUE ) res = TH_OK;
    }
    rc = sqlite3_finalize(pStmt);
    if( rc!=SQLITE_OK ){
      if( noComplain ) return TH_OK;
      Th_ErrorMessage(interp, "SQL error: ", sqlite3_errmsg(g.db), -1);
2015
2016
2017
2018
2019
2020
2021
2022

2023
2024
2025
2026
2027
2028
2029
2063
2064
2065
2066
2067
2068
2069

2070
2071
2072
2073
2074
2075
2076
2077







-
+







    rc = TH_ERROR;
  }else{
    Th_SetResult(interp, 0, 0);
    rc = TH_OK;
  }
  if( g.thTrace ){
    Th_Trace("[setting %s%#h] => %d<br>\n", strict ? "strict " : "",
             argl[nArg], argv[nArg], rc);
             TH1_LEN(argl[nArg]), argv[nArg], rc);
  }
  return rc;
}

/*
** TH1 command: glob_match ?-one? ?--? patternList string
**
2095
2096
2097
2098
2099
2100
2101
2102

2103
2104
2105

2106
2107
2108
2109
2110
2111
2112
2143
2144
2145
2146
2147
2148
2149

2150
2151
2152

2153
2154
2155
2156
2157
2158
2159
2160







-
+


-
+







  if( fossil_strcmp(argv[nArg], "-nocase")==0 ){
    noCase = 1; nArg++;
  }
  if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
  if( nArg+2!=argc ){
    return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
  }
  zErr = re_compile(&pRe, argv[nArg], noCase);
  zErr = fossil_re_compile(&pRe, argv[nArg], noCase);
  if( !zErr ){
    Th_SetResultInt(interp, re_match(pRe,
        (const unsigned char *)argv[nArg+1], argl[nArg+1]));
        (const unsigned char *)argv[nArg+1], TH1_LEN(argl[nArg+1])));
    rc = TH_OK;
  }else{
    Th_SetResult(interp, zErr, -1);
    rc = TH_ERROR;
  }
  re_free(pRe);
  return rc;
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
2185
2186
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







-
+














-
+













-
+







  Blob payload;
  ReCompiled *pRe = 0;
  UrlData urlData;

  if( argc<2 || argc>5 ){
    return Th_WrongNumArgs(interp, HTTP_WRONGNUMARGS);
  }
  if( fossil_strnicmp(argv[nArg], "-asynchronous", argl[nArg])==0 ){
  if( fossil_strnicmp(argv[nArg], "-asynchronous", TH1_LEN(argl[nArg]))==0 ){
    fAsynchronous = 1; nArg++;
  }
  if( fossil_strcmp(argv[nArg], "--")==0 ) nArg++;
  if( nArg+1!=argc && nArg+2!=argc ){
    return Th_WrongNumArgs(interp, REGEXP_WRONGNUMARGS);
  }
  memset(&urlData, '\0', sizeof(urlData));
  url_parse_local(argv[nArg], 0, &urlData);
  if( urlData.isSsh || urlData.isFile ){
    Th_ErrorMessage(interp, "url must be http:// or https://", 0, 0);
    return TH_ERROR;
  }
  zRegexp = db_get("th1-uri-regexp", 0);
  if( zRegexp && zRegexp[0] ){
    const char *zErr = re_compile(&pRe, zRegexp, 0);
    const char *zErr = fossil_re_compile(&pRe, zRegexp, 0);
    if( zErr ){
      Th_SetResult(interp, zErr, -1);
      return TH_ERROR;
    }
  }
  if( !pRe || !re_match(pRe, (const unsigned char *)urlData.canonical, -1) ){
    Th_SetResult(interp, "url not allowed", -1);
    re_free(pRe);
    return TH_ERROR;
  }
  re_free(pRe);
  blob_zero(&payload);
  if( nArg+2==argc ){
    blob_append(&payload, argv[nArg+1], argl[nArg+1]);
    blob_append(&payload, argv[nArg+1], TH1_LEN(argl[nArg+1]));
    zType = "POST";
  }else{
    zType = "GET";
  }
  if( fAsynchronous ){
    const char *zSep, *zParams;
    Blob hdr;
2245
2246
2247
2248
2249
2250
2251
2252

2253
2254
2255
2256
2257
2258
2259
2293
2294
2295
2296
2297
2298
2299

2300
2301
2302
2303
2304
2305
2306
2307







-
+







  const char * zStr;
  int nStr, rc;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "captureTh1 STRING");
  }
  pOrig = Th_SetOutputBlob(&out);
  zStr = argv[1];
  nStr = argl[1];
  nStr = TH1_LEN(argl[1]);
  rc = Th_Eval(g.interp, 0, zStr, nStr);
  Th_SetOutputBlob(pOrig);
  if(0==rc){
    Th_SetResult(g.interp, blob_str(&out), blob_size(&out));
  }
  blob_reset(&out);
  return rc;
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2381
2382
2383
2384
2385
2386
2387

2388
2389
2390
2391
2392
2393
2394







-







    {"checkout",      checkoutCmd,          0},
    {"combobox",      comboboxCmd,          0},
    {"copybtn",       copybtnCmd,           0},
    {"date",          dateCmd,              0},
    {"decorate",      wikiCmd,              (void*)&aFlags[2]},
    {"defHeader",     defHeaderCmd,         0},
    {"dir",           dirCmd,               0},
    {"enable_htmlify",enableHtmlifyCmd,     0},
    {"enable_output", enableOutputCmd,      0},
    {"encode64",      encode64Cmd,          0},
    {"getParameter",  getParameterCmd,      0},
    {"glob_match",    globMatchCmd,         0},
    {"globalState",   globalStateCmd,       0},
    {"httpize",       httpizeCmd,           0},
    {"hascap",        hascapCmd,            (void*)&zeroInt},
2364
2365
2366
2367
2368
2369
2370

2371
2372
2373

2374
2375
2376
2377
2378

2379
2380
2381
2382
2383
2384
2385
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







+



+





+







    {"searchable",    searchableCmd,        0},
    {"setParameter",  setParameterCmd,      0},
    {"setting",       settingCmd,           0},
    {"styleFooter",   styleFooterCmd,       0},
    {"styleHeader",   styleHeaderCmd,       0},
    {"styleScript",   styleScriptCmd,       0},
    {"submenu",       submenuCmd,           0},
    {"taint",         taintCmd,             0},
    {"tclReady",      tclReadyCmd,          0},
    {"trace",         traceCmd,             0},
    {"stime",         stimeCmd,             0},
    {"untaint",       untaintCmd,           0},
    {"unversioned",   unversionedCmd,       0},
    {"utime",         utimeCmd,             0},
    {"verifyCsrf",    verifyCsrfCmd,        0},
    {"verifyLogin",   verifyLoginCmd,       0},
    {"wiki",          wikiCmd,              (void*)&aFlags[0]},
    {"wiki_assoc",    wikiAssocCmd,         0},
    {0, 0, 0}
  };
  if( g.thTrace ){
    Th_Trace("th1-init 0x%x => 0x%x<br>\n", g.th1Flags, flags);
  }
  if( needConfig ){
    /*
2470
2471
2472
2473
2474
2475
2476
















2477
2478
2479
2480
2481
2482
2483
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  if( zValue ){
    if( g.thTrace ){
      Th_Trace("set %h {%h}<br>\n", zName, zValue);
    }
    Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
  }
}

/*
** Store a string value in a variable in the interpreter
** with the "taint" marking, so that TH1 knows that this
** variable contains content under the control of the remote
** user and presents a risk of XSS or SQL-injection attacks.
*/
void Th_StoreUnsafe(const char *zName, const char *zValue){
  Th_FossilInit(TH_INIT_DEFAULT);
  if( zValue ){
    if( g.thTrace ){
      Th_Trace("set %h [taint {%h}]<br>\n", zName, zValue);
    }
    Th_SetVar(g.interp, zName, -1, zValue, TH1_ADD_TAINT(strlen(zValue)));
  }
}

/*
** Appends an element to a TH1 list value.  This function is called by the
** transfer subsystem; therefore, it must be very careful to avoid doing
** any unnecessary work.  To that end, the TH1 subsystem will not be called
** or initialized if the list pointer is zero (i.e. which will be the case
** when TH1 transfer hooks are disabled).
2656
2657
2658
2659
2660
2661
2662

2663
2664
2665
2666
2667
2668
2669
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736







+







  if( rc==TH_ERROR ){
    int nResult = 0;
    char *zResult = (char*)Th_GetResult(g.interp, &nResult);
    /*
    ** Make sure that the TH1 script error was not caused by a "missing"
    ** command hook handler as that is not actually an error condition.
    */
    nResult = TH1_LEN(nResult);
    if( memcmp(zResult, NO_COMMAND_HOOK_ERROR, nResult)!=0 ){
      sendError(0,zResult, nResult, 0);
    }else{
      /*
      ** There is no command hook handler "installed".  This situation
      ** is NOT actually an error.
      */
2743
2744
2745
2746
2747
2748
2749

2750
2751
2752
2753
2754
2755
2756
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824







+







  if( rc==TH_ERROR ){
    int nResult = 0;
    char *zResult = (char*)Th_GetResult(g.interp, &nResult);
    /*
    ** Make sure that the TH1 script error was not caused by a "missing"
    ** webpage hook handler as that is not actually an error condition.
    */
    nResult = TH1_LEN(nResult);
    if( memcmp(zResult, NO_WEBPAGE_HOOK_ERROR, nResult)!=0 ){
      sendError(0,zResult, nResult, 1);
    }else{
      /*
      ** There is no webpage hook handler "installed".  This situation
      ** is NOT actually an error.
      */
2870
2871
2872
2873
2874
2875
2876




2877


2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890

2891
2892
2893
2894
2895
2896
2897

2898
2899
2900
2901
2902
2903
2904
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948

2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962

2963
2964
2965
2966
2967
2968
2969

2970
2971
2972
2973
2974
2975
2976
2977







+
+
+
+
-
+
+












-
+






-
+







        nVar = n;
        encode = 0;
      }
      rc = Th_GetVar(g.interp, (char*)zVar, nVar);
      z += i+1+n;
      i = 0;
      zResult = (char*)Th_GetResult(g.interp, &n);
      if( !TH1_TAINTED(n)
       || encode 
       || Th_ReportTaint(g.interp, "inline variable", zVar, nVar)==TH_OK
      ){
      sendText(pOut,(char*)zResult, n, encode);
        sendText(pOut,(char*)zResult, n, encode);
      }
    }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
      sendText(pOut,z, i, 0);
      z += i+5;
      for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
      if( g.thTrace ){
        Th_Trace("render_eval {<pre>%#h</pre>}<br>\n", i, z);
      }
      rc = Th_Eval(g.interp, 0, (const char*)z, i);
      if( g.thTrace ){
        int nTrRes;
        char *zTrRes = (char*)Th_GetResult(g.interp, &nTrRes);
        Th_Trace("[render_eval] => %h {%#h}<br>\n",
                 Th_ReturnCodeName(rc, 0), nTrRes, zTrRes);
                 Th_ReturnCodeName(rc, 0), TH1_LEN(nTrRes), zTrRes);
      }
      if( rc!=TH_OK ) break;
      z += i;
      if( z[0] ){ z += 6; }
      i = 0;
    }else{
      i++;
      i += strcspn(&z[i+1], "<$") + 1;
    }
  }
  if( rc==TH_ERROR ){
    zResult = (char*)Th_GetResult(g.interp, &n);
    sendError(pOut,zResult, n, 1);
  }else{
    sendText(pOut,z, i, 0);
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935







































































2936
2937
2938
2939
2940
2941
2942
2998
2999
3000
3001
3002
3003
3004




3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082







-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    ** Th_RenderToBlob() will output directly to the CGI buffer (in
    ** CGI mode) or stdout (in CLI mode). Recursive calls, however,
    ** e.g. via the "render" script function binding, need to use the
    ** pThOut blob in order to avoid out-of-order output if
    ** Th_SetOutputBlob() has been called. If it has not been called,
    ** pThOut will be 0, which will redirect the output to CGI/stdout,
    ** as appropriate. We need to pass on g.th1Flags for the case of
    ** recursive calls, so that, e.g., TH_INIT_NO_ENCODE does not get
    ** inadvertently toggled off by a recursive call.
    */;
}
    ** recursive calls.
    */;
}

/*
** SETTING: vuln-report           width=8 default=log
**
** This setting controls Fossil's behavior when it encounters a potential
** XSS or SQL-injection vulnerability due to misuse of TH1 configuration
** scripts.  Choices are:
**
**    off            Do nothing.  Ignore the vulnerability.
**
**    log            Write a report of the problem into the error log.
**
**    block          Like "log" but also prevent the offending TH1 command
**                   from running.
**
**    fatal          Render an error message page instead of the requested
**                   page.
*/

/*
** Report misuse of a tainted string in TH1.
**
** The behavior depends on the vuln-report setting.  If "off", this routine
** is a no-op.  Otherwise, right a message into the error log.  If
** vuln-report is "log", that is all that happens.  But for any other
** value of vuln-report, a fatal error is raised.
*/
int Th_ReportTaint(
  Th_Interp *interp,       /* Report error here, if an error is reported */
  const char *zWhere,      /* Where the tainted string appears */
  const char *zStr,        /* The tainted string */
  int nStr                 /* Length of the tainted string */
){
  static const char *zDisp = 0;   /* Dispensation; what to do with the error */
  const char *zVulnType;          /* Type of vulnerability */

  if( zDisp==0 ) zDisp = db_get("vuln-report","log");
  if( is_false(zDisp) ) return 0;
  if( strstr(zWhere,"SQL")!=0 ){
    zVulnType = "SQL-injection";
  }else{
    zVulnType = "XSS";
  }
  nStr = TH1_LEN(nStr);
  fossil_errorlog("possible TH1 %s vulnerability due to tainted %s: \"%.*s\"",
                  zVulnType, zWhere, nStr, zStr);
  if( strcmp(zDisp,"log")==0 ){
    return 0;
  }
  if( strcmp(zDisp,"block")==0 ){
    char *z = mprintf("tainted %s: \"", zWhere);
    Th_ErrorMessage(interp, z, zStr, nStr);
    fossil_free(z);
  }else{
    char *z = mprintf("%#h", nStr, zStr);
    zDisp = "off";
    cgi_reset_content();
    style_submenu_enable(0);
    style_set_current_feature("error");
    style_header("Configuration Error");
    @ <p>Error in a TH1 configuration script: 
    @ tainted %h(zWhere): "%z(z)"
    style_finish_page();
    cgi_reply();
    fossil_exit(1);
  }
  return 1;
}

/*
** COMMAND: test-th-render
**
** Usage: %fossil test-th-render FILE
**
** Read the content of the file named "FILE" as if it were a header or
2968
2969
2970
2971
2972
2973
2974

2975
2976
2977
2978
2979
2980
2981
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122







+







    g.useLocalauth = 1;
  }
  if( find_option("set-user-caps", 0, 0)!=0 ){
    const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
    login_set_capabilities(zCap ? zCap : "sx", 0);
    g.useLocalauth = 1;
  }
  db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
  verify_all_options();
  if( g.argc<3 ){
    usage("FILE");
  }
  blob_zero(&in);
  blob_read_from_file(&in, g.argv[2], ExtFILE);
  Th_Render(blob_str(&in));
3020
3021
3022
3023
3024
3025
3026

3027
3028
3029
3030
3031
3032
3033
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175







+







    g.useLocalauth = 1;
  }
  if( find_option("set-user-caps", 0, 0)!=0 ){
    const char *zCap = fossil_getenv("TH1_TEST_USER_CAPS");
    login_set_capabilities(zCap ? zCap : "sx", 0);
    g.useLocalauth = 1;
  }
  db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
  verify_all_options();
  if( g.argc!=3 ){
    usage("script");
  }
  if(file_isfile(g.argv[2], ExtFILE)){
    blob_read_from_file(&code, g.argv[2], ExtFILE);
    zCode = blob_str(&code);
Changes to src/th_tcl.c.
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
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







+
+
+
+

















-
-
-
-
-
-
+
+
+
+
+
+








#ifdef FOSSIL_ENABLE_TCL

#include "sqlite3.h"
#include "th.h"
#include "tcl.h"

#if TCL_MAJOR_VERSION<9 && !defined(Tcl_Size)
# define Tcl_Size int
#endif

/*
** This macro is used to verify that the header version of Tcl meets some
** minimum requirement.
*/
#define MINIMUM_TCL_VERSION(major, minor) \
  ((TCL_MAJOR_VERSION > (major)) || \
   ((TCL_MAJOR_VERSION == (major)) && (TCL_MINOR_VERSION >= (minor))))

/*
** These macros are designed to reduce the redundant code required to marshal
** arguments from TH1 to Tcl.
*/
#define USE_ARGV_TO_OBJV() \
  int objc;                \
  Tcl_Obj **objv;          \
  int obji;

#define COPY_ARGV_TO_OBJV()                                         \
  objc = argc-1;                                                    \
  objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *))); \
  for(obji=1; obji<argc; obji++){                                   \
    objv[obji-1] = Tcl_NewStringObj(argv[obji], argl[obji]);        \
    Tcl_IncrRefCount(objv[obji-1]);                                 \
#define COPY_ARGV_TO_OBJV()                                           \
  objc = argc-1;                                                      \
  objv = (Tcl_Obj **)ckalloc((unsigned)(objc * sizeof(Tcl_Obj *)));   \
  for(obji=1; obji<argc; obji++){                                     \
    objv[obji-1] = Tcl_NewStringObj(argv[obji], TH1_LEN(argl[obji])); \
    Tcl_IncrRefCount(objv[obji-1]);                                   \
  }

#define FREE_ARGV_TO_OBJV()         \
  for(obji=1; obji<argc; obji++){   \
    Tcl_DecrRefCount(objv[obji-1]); \
    objv[obji-1] = 0;               \
  }                                 \
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
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







-
+


















-
+

-
+






-
+






-
+






-
+









+
+
+







#      define _WIN32_WINNT 0x0502 /* SetDllDirectory, Windows XP SP2 */
#    endif
#    include <windows.h>
#    ifndef TCL_DIRECTORY_SEP
#      define TCL_DIRECTORY_SEP '\\'
#    endif
#    ifndef TCL_LIBRARY_NAME
#      define TCL_LIBRARY_NAME "tcl87.dll\0"
#      define TCL_LIBRARY_NAME "tcl91.dll\0"
#    endif
#    ifndef TCL_MINOR_OFFSET
#      define TCL_MINOR_OFFSET (4)
#    endif
#    ifndef dlopen
#      define dlopen(a,b) (void *)LoadLibrary((a))
#    endif
#    ifndef dlsym
#      define dlsym(a,b) GetProcAddress((HANDLE)(a),(b))
#    endif
#    ifndef dlclose
#      define dlclose(a) FreeLibrary((HANDLE)(a))
#    endif
#  else
#    include <dlfcn.h>
#    ifndef TCL_DIRECTORY_SEP
#      define TCL_DIRECTORY_SEP '/'
#    endif
#    if defined(__CYGWIN__)
#    if defined(__CYGWIN__) && (TCL_MAJOR_VERSION > 8)
#      ifndef TCL_LIBRARY_NAME
#        define TCL_LIBRARY_NAME "libtcl8.7.dll\0"
#        define TCL_LIBRARY_NAME "cygtcl9.1.dll\0"
#      endif
#      ifndef TCL_MINOR_OFFSET
#        define TCL_MINOR_OFFSET (8)
#      endif
#    elif defined(__APPLE__)
#      ifndef TCL_LIBRARY_NAME
#        define TCL_LIBRARY_NAME "libtcl8.7.dylib\0"
#        define TCL_LIBRARY_NAME "libtcl9.1.dylib\0"
#      endif
#      ifndef TCL_MINOR_OFFSET
#        define TCL_MINOR_OFFSET (8)
#      endif
#    elif defined(__FreeBSD__)
#      ifndef TCL_LIBRARY_NAME
#        define TCL_LIBRARY_NAME "libtcl87.so\0"
#        define TCL_LIBRARY_NAME "libtcl91.so\0"
#      endif
#      ifndef TCL_MINOR_OFFSET
#        define TCL_MINOR_OFFSET (7)
#      endif
#    else
#      ifndef TCL_LIBRARY_NAME
#        define TCL_LIBRARY_NAME "libtcl8.7.so\0"
#        define TCL_LIBRARY_NAME "libtcl9.1.so\0"
#      endif
#      ifndef TCL_MINOR_OFFSET
#        define TCL_MINOR_OFFSET (8)
#      endif
#    endif /* defined(__CYGWIN__) */
#  endif /* defined(_WIN32) */
#  ifndef TCL_FINDEXECUTABLE_NAME
#    define TCL_FINDEXECUTABLE_NAME "_Tcl_FindExecutable\0"
#  endif
#  ifndef TCL_ZIPFSAPPHOOK_NAME
#    define TCL_ZIPFSAPPHOOK_NAME "_TclZipfs_AppHook\0"
#  endif
#  ifndef TCL_CREATEINTERP_NAME
#    define TCL_CREATEINTERP_NAME "_Tcl_CreateInterp\0"
#  endif
#  ifndef TCL_DELETEINTERP_NAME
#    define TCL_DELETEINTERP_NAME "_Tcl_DeleteInterp\0"
#  endif
#  ifndef TCL_FINALIZE_NAME
181
182
183
184
185
186
187




188

189
190
191
192
193
194
195
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207







+
+
+
+

+







** when the Tcl library is being loaded dynamically by a stubs-enabled
** application (i.e. the inverse of using a stubs-enabled package).  These are
** the only Tcl API functions that MUST be called prior to being able to call
** Tcl_InitStubs (i.e. because it requires a Tcl interpreter).  For complete
** cleanup if the Tcl stubs initialization fails somehow, the Tcl_DeleteInterp
** and Tcl_Finalize function types are also required.
*/
#if TCL_MAJOR_VERSION>=9
typedef const char *(tcl_FindExecutableProc) (const char *);
typedef const char *(tcl_ZipfsAppHookProc) (int *, char ***);
#else
typedef void (tcl_FindExecutableProc) (const char *);
#endif
typedef Tcl_Interp *(tcl_CreateInterpProc) (void);
typedef void (tcl_DeleteInterpProc) (Tcl_Interp *);
typedef void (tcl_FinalizeProc) (void);

/*
** The function types for the "hook" functions to be called before and after a
** TH1 command makes a call to evaluate a Tcl script.  If the "pre" function
250
251
252
253
254
255
256
257

258
259
260
261
262
263
264
262
263
264
265
266
267
268

269
270
271
272
273
274
275
276







-
+







  if( !tclStubsPtr || (tclStubsPtr->magic!=TCL_STUB_MAGIC) ){
    Th_ErrorMessage(interp,
        "could not initialize Tcl stubs: incompatible mechanism",
        (const char *)"", 0);
    return TH_ERROR;
  }
  /* NOTE: At this point, the Tcl API functions should be available. */
  if( Tcl_PkgRequireEx(tclInterp, "Tcl", "8.4", 0, (void *)&tclStubsPtr)==0 ){
  if( Tcl_PkgRequireEx(tclInterp, "Tcl", "8.5-", 0, (void *)&tclStubsPtr)==0 ){
    Th_ErrorMessage(interp,
        "could not initialize Tcl stubs: incompatible version",
        (const char *)"", 0);
    return TH_ERROR;
  }
  return TH_OK;
}
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
331
332
333
334
335
336
337

















338
339
340
341
342
343
344







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







** Creates and initializes a Tcl interpreter for use with the specified TH1
** interpreter.  Stores the created Tcl interpreter in the Tcl context supplied
** by the caller.  This must be declared here because quite a few functions in
** this file need to use it before it can be defined.
*/
static int createTclInterp(Th_Interp *interp, void *pContext);

/*
** Returns the TH1 return code corresponding to the specified Tcl
** return code.
*/
static int getTh1ReturnCode(
  int rc /* The Tcl return code value to convert. */
){
  switch( rc ){
    case /*0*/ TCL_OK:       return /*0*/ TH_OK;
    case /*1*/ TCL_ERROR:    return /*1*/ TH_ERROR;
    case /*2*/ TCL_RETURN:   return /*3*/ TH_RETURN;
    case /*3*/ TCL_BREAK:    return /*2*/ TH_BREAK;
    case /*4*/ TCL_CONTINUE: return /*4*/ TH_CONTINUE;
    default /*?*/:           return /*?*/ rc;
  }
}

/*
** Returns the Tcl return code corresponding to the specified TH1
** return code.
*/
static int getTclReturnCode(
  int rc /* The TH1 return code value to convert. */
){
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
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
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
421
422




423
424
































425
426
427
428
429
430
431







+
+










-
+
+
+











+
+
+







-
-
-
-


-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







** If the length pointer is NULL, the length will not be stored.
*/
static char *getTclResult(
  Tcl_Interp *pInterp,
  int *pN
){
  Tcl_Obj *resultPtr;
  Tcl_Size n;
  char *zRes;

  if( !pInterp ){ /* This should not happen. */
    if( pN ) *pN = 0;
    return 0;
  }
  resultPtr = Tcl_GetObjResult(pInterp);
  if( !resultPtr ){ /* This should not happen either? */
    if( pN ) *pN = 0;
    return 0;
  }
  return Tcl_GetStringFromObj(resultPtr, pN);
  zRes = Tcl_GetStringFromObj(resultPtr, &n);
  *pN = (int)n;
  return zRes;
}

/*
** Tcl context information used by TH1.  This structure definition has been
** copied from and should be kept in sync with the one in "main.c".
*/
struct TclContext {
  int argc;           /* Number of original arguments. */
  char **argv;        /* Full copy of the original arguments. */
  void *hLibrary;     /* The Tcl library module handle. */
  tcl_FindExecutableProc *xFindExecutable; /* Tcl_FindExecutable() pointer. */
#if TCL_MAJOR_VERSION>=9
  tcl_ZipfsAppHookProc  *xZipfsAppHook;    /* TclZipfsAppHook() pointer. */
#endif
  tcl_CreateInterpProc *xCreateInterp;     /* Tcl_CreateInterp() pointer. */
  tcl_DeleteInterpProc *xDeleteInterp;     /* Tcl_DeleteInterp() pointer. */
  tcl_FinalizeProc *xFinalize;             /* Tcl_Finalize() pointer. */
  Tcl_Interp *interp; /* The on-demand created Tcl interpreter. */
  int useObjProc;     /* Non-zero if an objProc can be called directly. */
  int useTip285;      /* Non-zero if TIP #285 is available. */
  const char *setup;  /* The optional Tcl setup script. */
  tcl_NotifyProc *xPreEval;  /* Optional, called before Tcl_Eval*(). */
  void *pPreContext;         /* Optional, provided to xPreEval(). */
  tcl_NotifyProc *xPostEval; /* Optional, called after Tcl_Eval*(). */
  void *pPostContext;        /* Optional, provided to xPostEval(). */
};

/*
** This function calls the configured xPreEval or xPostEval functions, if any.
** May have arbitrary side-effects.  This function returns the result of the
** called notification function or the value of the rc argument if there is no
** notification function configured.
*/
static int notifyPreOrPostEval(
  int bIsPost,
  Th_Interp *interp,
  void *ctx,
  int argc,
  const char **argv,
  int *argl,
  int rc
){
  struct TclContext *tclContext = (struct TclContext *)ctx;
  tcl_NotifyProc *xNotifyProc;

  if( !tclContext ){
    Th_ErrorMessage(interp,
        "invalid Tcl context", (const char *)"", 0);
    return TH_ERROR;
  }
  xNotifyProc = bIsPost ? tclContext->xPostEval : tclContext->xPreEval;
  if( xNotifyProc ){
    rc = xNotifyProc(bIsPost ?
        tclContext->pPostContext : tclContext->pPreContext,
        interp, ctx, argc, argv, argl, rc);
  }
  return rc;
}

/*
** TH1 command: tclEval arg ?arg ...?
**
** Evaluates the Tcl script and returns its result verbatim.  If a Tcl script
** error is generated, it will be transformed into a TH1 script error.  The
** Tcl interpreter will be created automatically if it has not been already.
*/
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
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







-
-
-
-


-
+















-
-







    return Th_WrongNumArgs(interp, "tclEval arg ?arg ...?");
  }
  tclInterp = GET_CTX_TCL_INTERP(ctx);
  if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
    Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
    return TH_ERROR;
  }
  rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
  if( rc!=TH_OK ){
    return rc;
  }
  Tcl_Preserve((ClientData)tclInterp);
  if( argc==2 ){
    objPtr = Tcl_NewStringObj(argv[1], argl[1]);
    objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
    Tcl_IncrRefCount(objPtr);
    rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
    Tcl_DecrRefCount(objPtr); objPtr = 0;
  }else{
    USE_ARGV_TO_OBJV();
    COPY_ARGV_TO_OBJV();
    objPtr = Tcl_ConcatObj(objc, objv);
    Tcl_IncrRefCount(objPtr);
    rc = Tcl_EvalObjEx(tclInterp, objPtr, 0);
    Tcl_DecrRefCount(objPtr); objPtr = 0;
    FREE_ARGV_TO_OBJV();
  }
  zResult = getTclResult(tclInterp, &nResult);
  Th_SetResult(interp, zResult, nResult);
  Tcl_Release((ClientData)tclInterp);
  rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
                           getTh1ReturnCode(rc));
  return rc;
}

/*
** TH1 command: tclExpr arg ?arg ...?
**
** Evaluates the Tcl expression and returns its result verbatim.  If a Tcl
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
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







-
-
-
-


-
+













+
-
+
+



-
+




-
-







    return Th_WrongNumArgs(interp, "tclExpr arg ?arg ...?");
  }
  tclInterp = GET_CTX_TCL_INTERP(ctx);
  if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
    Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
    return TH_ERROR;
  }
  rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
  if( rc!=TH_OK ){
    return rc;
  }
  Tcl_Preserve((ClientData)tclInterp);
  if( argc==2 ){
    objPtr = Tcl_NewStringObj(argv[1], argl[1]);
    objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
    Tcl_IncrRefCount(objPtr);
    rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
    Tcl_DecrRefCount(objPtr); objPtr = 0;
  }else{
    USE_ARGV_TO_OBJV();
    COPY_ARGV_TO_OBJV();
    objPtr = Tcl_ConcatObj(objc, objv);
    Tcl_IncrRefCount(objPtr);
    rc = Tcl_ExprObj(tclInterp, objPtr, &resultObjPtr);
    Tcl_DecrRefCount(objPtr); objPtr = 0;
    FREE_ARGV_TO_OBJV();
  }
  if( rc==TCL_OK ){
    Tcl_Size szResult = 0;
    zResult = Tcl_GetStringFromObj(resultObjPtr, &nResult);
    zResult = Tcl_GetStringFromObj(resultObjPtr, &szResult);
    nResult = (int)szResult;
  }else{
    zResult = getTclResult(tclInterp, &nResult);
  }
  Th_SetResult(interp, zResult, nResult);
  Th_SetResult(interp, zResult, (int)nResult);
  if( rc==TCL_OK ){
    Tcl_DecrRefCount(resultObjPtr); resultObjPtr = 0;
  }
  Tcl_Release((ClientData)tclInterp);
  rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
                           getTh1ReturnCode(rc));
  return rc;
}

/*
** TH1 command: tclInvoke command ?arg ...?
**
** Invokes the Tcl command using the supplied arguments.  No additional
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624

625
626
627
628
629
630
631
564
565
566
567
568
569
570




571
572
573
574
575

576
577
578
579
580
581
582
583







-
-
-
-





-
+







    return Th_WrongNumArgs(interp, "tclInvoke command ?arg ...?");
  }
  tclInterp = GET_CTX_TCL_INTERP(ctx);
  if( !tclInterp || Tcl_InterpDeleted(tclInterp) ){
    Th_ErrorMessage(interp, "invalid Tcl interpreter", (const char *)"", 0);
    return TH_ERROR;
  }
  rc = notifyPreOrPostEval(0, interp, ctx, argc, argv, argl, rc);
  if( rc!=TH_OK ){
    return rc;
  }
  Tcl_Preserve((ClientData)tclInterp);
#if !defined(USE_TCL_EVALOBJV) || !USE_TCL_EVALOBJV
  if( GET_CTX_TCL_USEOBJPROC(ctx) ){
    Tcl_Command command;
    Tcl_CmdInfo cmdInfo;
    Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], argl[1]);
    Tcl_Obj *objPtr = Tcl_NewStringObj(argv[1], TH1_LEN(argl[1]));
    Tcl_IncrRefCount(objPtr);
    command = Tcl_GetCommandFromObj(tclInterp, objPtr);
    if( !command || Tcl_GetCommandInfoFromToken(command, &cmdInfo)==0 ){
      Th_ErrorMessage(interp, "Tcl command not found:", argv[1], argl[1]);
      Tcl_DecrRefCount(objPtr); objPtr = 0;
      Tcl_Release((ClientData)tclInterp);
      return TH_ERROR;
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
599
600
601
602
603
604
605


606
607
608
609
610
611
612







-
-







    COPY_ARGV_TO_OBJV();
    rc = Tcl_EvalObjv(tclInterp, objc, objv, 0);
    FREE_ARGV_TO_OBJV();
  }
  zResult = getTclResult(tclInterp, &nResult);
  Th_SetResult(interp, zResult, nResult);
  Tcl_Release((ClientData)tclInterp);
  rc = notifyPreOrPostEval(1, interp, ctx, argc, argv, argl,
                           getTh1ReturnCode(rc));
  return rc;
}

/*
** TH1 command: tclIsSafe
**
** Returns non-zero if the Tcl interpreter is "safe".  The Tcl interpreter
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
809
810
811
812
813
814
815
816
817
818


819
820

821
822
823
824
825
826
827
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







+












-
+
+


-
+

















+












-
-
+
+

-
+







  ClientData clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *const objv[]
){
  Th_Interp *th1Interp;
  int nArg;
  Tcl_Size szArg;
  const char *arg;
  int rc;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "arg");
    return TCL_ERROR;
  }
  th1Interp = (Th_Interp *)clientData;
  if( !th1Interp ){
    Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
    return TCL_ERROR;
  }
  arg = Tcl_GetStringFromObj(objv[1], &nArg);
  arg = Tcl_GetStringFromObj(objv[1], &szArg);
  nArg = (int)szArg;
  rc = Th_Eval(th1Interp, 0, arg, nArg);
  arg = Th_GetResult(th1Interp, &nArg);
  Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
  Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
  return getTclReturnCode(rc);
}

/*
** Tcl command: th1Expr arg
**
** Evaluates the TH1 expression and returns its result verbatim.  If a TH1
** script error is generated, it will be transformed into a Tcl script error.
*/
static int Th1ExprObjCmd(
  ClientData clientData,
  Tcl_Interp *interp,
  int objc,
  Tcl_Obj *const objv[]
){
  Th_Interp *th1Interp;
  int nArg;
  Tcl_Size szArg;
  const char *arg;
  int rc;

  if( objc!=2 ){
    Tcl_WrongNumArgs(interp, 1, objv, "arg");
    return TCL_ERROR;
  }
  th1Interp = (Th_Interp *)clientData;
  if( !th1Interp ){
    Tcl_AppendResult(interp, "invalid TH1 interpreter", NULL);
    return TCL_ERROR;
  }
  arg = Tcl_GetStringFromObj(objv[1], &nArg);
  rc = Th_Expr(th1Interp, arg, nArg);
  arg = Tcl_GetStringFromObj(objv[1], &szArg);
  rc = Th_Expr(th1Interp, arg, (int)szArg);
  arg = Th_GetResult(th1Interp, &nArg);
  Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, nArg));
  Tcl_SetObjResult(interp, Tcl_NewStringObj(arg, TH1_LEN(nArg)));
  return getTclReturnCode(rc);
}

/*
** Array of Tcl integration commands.  Used when adding or removing the Tcl
** integration commands from TH1.
*/
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
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







+
+
+
















+
+
+
+
+
+
+
+







char *file_dirname(const char *zPath);  /* file.h */
void fossil_free(void *p);              /* util.h */

static int loadTcl(
  Th_Interp *interp,
  void **phLibrary,
  tcl_FindExecutableProc **pxFindExecutable,
#if TCL_MAJOR_VERSION>=9
  tcl_ZipfsAppHookProc **pxZipfsAppHook,
#endif
  tcl_CreateInterpProc **pxCreateInterp,
  tcl_DeleteInterpProc **pxDeleteInterp,
  tcl_FinalizeProc **pxFinalize
){
#if defined(USE_TCL_STUBS)
  const char *zEnvPath = fossil_getenv(TCL_PATH_ENV_VAR_NAME);
  char aFileName[] = TCL_LIBRARY_NAME;
#endif /* defined(USE_TCL_STUBS) */

  if( !phLibrary || !pxFindExecutable || !pxCreateInterp ||
      !pxDeleteInterp || !pxFinalize ){
    Th_ErrorMessage(interp,
        "invalid Tcl loader argument(s)", (const char *)"", 0);
    return TH_ERROR;
  }
#if defined(USE_TCL_STUBS)
#if TCL_MAJOR_VERSION<9
#if defined(_WIN32) || defined(__FreeBSD__)
  aFileName[TCL_MINOR_OFFSET-1] = '0' + TCL_MAJOR_VERSION;
#else
  aFileName[TCL_MINOR_OFFSET-2] = '0' + TCL_MAJOR_VERSION;
#endif
  aFileName[TCL_MINOR_OFFSET] = '0' + TCL_MINOR_VERSION;
#endif
  do {
    char *zFileName;
    void *hLibrary;
    if( !zEnvPath ){
      zFileName = aFileName; /* NOTE: Assume present in PATH. */
    }else if( file_isdir(zEnvPath, ExtFILE)==1 ){
#if TCL_USE_SET_DLL_DIRECTORY
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
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







+
+
+














+
+
+
+
+
+
+







    hLibrary = dlopen(zFileName, RTLD_NOW | RTLD_GLOBAL);
    /* NOTE: If the file name was allocated, free it now. */
    if( zFileName!=aFileName ){
      sqlite3_free(zFileName); zFileName = 0;
    }
    if( hLibrary ){
      tcl_FindExecutableProc *xFindExecutable;
#if TCL_MAJOR_VERSION>=9
      tcl_ZipfsAppHookProc *xZipfsAppHook;
#endif
      tcl_CreateInterpProc *xCreateInterp;
      tcl_DeleteInterpProc *xDeleteInterp;
      tcl_FinalizeProc *xFinalize;
      const char *procName = TCL_FINDEXECUTABLE_NAME;
      xFindExecutable = (tcl_FindExecutableProc *)dlsym(hLibrary, procName+1);
      if( !xFindExecutable ){
        xFindExecutable = (tcl_FindExecutableProc *)dlsym(hLibrary, procName);
      }
      if( !xFindExecutable ){
        Th_ErrorMessage(interp,
            "could not locate Tcl_FindExecutable", (const char *)"", 0);
        dlclose(hLibrary); hLibrary = 0;
        return TH_ERROR;
      }
#if TCL_MAJOR_VERSION>=9
      procName = TCL_ZIPFSAPPHOOK_NAME;
      xZipfsAppHook = (tcl_ZipfsAppHookProc *)dlsym(hLibrary, procName+1);
      if( !xZipfsAppHook ){
        xZipfsAppHook = (tcl_ZipfsAppHookProc *)dlsym(hLibrary, procName);
      }
#endif
      procName = TCL_CREATEINTERP_NAME;
      xCreateInterp = (tcl_CreateInterpProc *)dlsym(hLibrary, procName+1);
      if( !xCreateInterp ){
        xCreateInterp = (tcl_CreateInterpProc *)dlsym(hLibrary, procName);
      }
      if( !xCreateInterp ){
        Th_ErrorMessage(interp,
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
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







+
+
+





-
+


-
+





+
+
+







        Th_ErrorMessage(interp,
            "could not locate Tcl_Finalize", (const char *)"", 0);
        dlclose(hLibrary); hLibrary = 0;
        return TH_ERROR;
      }
      *phLibrary = hLibrary;
      *pxFindExecutable = xFindExecutable;
#if TCL_MAJOR_VERSION>=9
      *pxZipfsAppHook = xZipfsAppHook;
#endif
      *pxCreateInterp = xCreateInterp;
      *pxDeleteInterp = xDeleteInterp;
      *pxFinalize = xFinalize;
      return TH_OK;
    }
  } while( --aFileName[TCL_MINOR_OFFSET]>'3' ); /* Tcl 8.4+ */
  } while( --aFileName[TCL_MINOR_OFFSET]!='6' && aFileName[TCL_MINOR_OFFSET]>='0'); /* Tcl 8.6+ */
  aFileName[TCL_MINOR_OFFSET] = 'x';
  Th_ErrorMessage(interp,
      "could not load any supported Tcl 8.x shared library \"",
      "could not load any supported Tcl shared library \"",
      aFileName, -1);
  return TH_ERROR;
#else
  *phLibrary = 0;
  *pxFindExecutable = Tcl_FindExecutable;
#if TCL_MAJOR_VERSION>=9
  *pxZipfsAppHook = (tcl_ZipfsAppHookProc *)(void *)TclZipfs_AppHook;
#endif
  *pxCreateInterp = Tcl_CreateInterp;
  *pxDeleteInterp = Tcl_DeleteInterp;
  *pxFinalize = Tcl_Finalize;
  return TH_OK;
#endif /* defined(USE_TCL_STUBS) */
}

1128
1129
1130
1131
1132
1133
1134



1135
1136
1137
1138
1139
1140
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
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
1142
1143
1144
1145
1146

1147
1148
1149
1150
1151
1152
1153
1154







+
+
+









+
+
+
+
+















-
+







        "invalid Tcl context", (const char *)"", 0);
    return TH_ERROR;
  }
  if( tclContext->interp ){
    return TH_OK;
  }
  if( loadTcl(interp, &tclContext->hLibrary, &tclContext->xFindExecutable,
  #if TCL_MAJOR_VERSION >= 9
              &tclContext->xZipfsAppHook,
  #endif
              &tclContext->xCreateInterp, &tclContext->xDeleteInterp,
              &tclContext->xFinalize)!=TH_OK ){
    return TH_ERROR;
  }
  argc = tclContext->argc;
  argv = tclContext->argv;
  if( argc>0 && argv ){
    argv0 = argv[0];
  }
#if TCL_MAJOR_VERSION>=9
  if (tclContext->xZipfsAppHook) {
    tclContext->xZipfsAppHook(&tclContext->argc, &tclContext->argv);
  }
#endif
  tclContext->xFindExecutable(argv0);
  tclInterp = tclContext->xCreateInterp();
  if( !tclInterp ){
    Th_ErrorMessage(interp,
        "could not create Tcl interpreter", (const char *)"", 0);
    return TH_ERROR;
  }
#if defined(USE_TCL_STUBS)
#if defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS)
  if( initTclStubs(interp, tclInterp)!=TH_OK ){
    tclContext->xDeleteInterp(tclInterp);
    tclInterp = 0;
    return TH_ERROR;
  }
#else
  if( !Tcl_InitStubs(tclInterp, "8.4", 0) ){
  if( !Tcl_InitStubs(tclInterp, "8.5-", 0) ){
    Th_ErrorMessage(interp,
        "could not initialize Tcl stubs", (const char *)"", 0);
    tclContext->xDeleteInterp(tclInterp);
    tclInterp = 0;
    return TH_ERROR;
  }
#endif /* defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS) */
Changes to src/timeline.c.
20
21
22
23
24
25
26
27

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

27
28
29
30
31
32
33
34







-
+







*/
#include "config.h"
#include <string.h>
#include <time.h>
#include "timeline.h"

/*
** The value of one second in julianday notation
** The value of one second in Julian day notation
*/
#define ONE_SECOND (1.0/86400.0)

/*
** timeline mode options
*/
#define TIMELINE_MODE_NONE      0
113
114
115
116
117
118
119


120

121
122
123
124
125
126
127
113
114
115
116
117
118
119
120
121

122
123
124
125
126
127
128
129







+
+
-
+







#define TIMELINE_SHOWRID  0x0000400 /* Show RID values in addition to hashes */
#define TIMELINE_BISECT   0x0000800 /* Show supplemental bisect information */
#define TIMELINE_COMPACT  0x0001000 /* Use the "compact" view style */
#define TIMELINE_VERBOSE  0x0002000 /* Use the "detailed" view style */
#define TIMELINE_MODERN   0x0004000 /* Use the "modern" view style */
#define TIMELINE_COLUMNAR 0x0008000 /* Use the "columns" view style */
#define TIMELINE_CLASSIC  0x0010000 /* Use the "classic" view style */
#define TIMELINE_SIMPLE   0x0020000 /* Use the "simple" view style */
#define TIMELINE_INLINE   0x0033000 /* Mask for views with in-line display */
#define TIMELINE_VIEWS    0x001f000 /* Mask for all of the view styles */
#define TIMELINE_VIEWS    0x003f000 /* Mask for all of the view styles */
#define TIMELINE_NOSCROLL 0x0100000 /* Don't scroll to the selection */
#define TIMELINE_FILEDIFF 0x0200000 /* Show File differences, not ckin diffs */
#define TIMELINE_CHPICK   0x0400000 /* Show cherrypick merges */
#define TIMELINE_FILLGAPS 0x0800000 /* Dotted lines for missing nodes */
#define TIMELINE_XMERGE   0x1000000 /* Omit merges from off-graph nodes */
#define TIMELINE_NOTKT    0x2000000 /* Omit extra ticket classes */
#define TIMELINE_FORUMTXT 0x4000000 /* Render all forum messages */
168
169
170
171
172
173
174












































































































































































175
176
177
178
179
180
181
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  if( pPost ){
    sqlite3_result_text(context, pPost->zWiki, -1, SQLITE_TRANSIENT);
    manifest_destroy(pPost);
  }
}


/*
** This routine generates the default "extra" text after the description
** in a timeline.
**
** Example:  "(check-in: [abcdefg], user: drh, tags: trunk)"
**
** This routine is used if no xExtra argument is supplied to
** www_print_timeline().
*/
void timeline_extra(
  Stmt *pQuery,               /* Current row of the timeline query */
  int tmFlags,                /* Flags to www_print_timeline() */
  const char *zThisUser,      /* Suppress links to this user */
  const char *zThisTag        /* Suppress links to this tag */
){
  int rid = db_column_int(pQuery, 0);
  const char *zUuid = db_column_text(pQuery, 1);
  const char *zDate = db_column_text(pQuery, 2);
  const char *zType = db_column_text(pQuery, 7);
  const char *zUser = db_column_text(pQuery, 4);
  const char *zTagList = db_column_text(pQuery, 8);
  int tagid = db_column_int(pQuery, 9);
  const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";

  if( (tmFlags & TIMELINE_INLINE)!=0 ){
    cgi_printf("(");
  }

  if( (tmFlags & TIMELINE_CLASSIC)==0 ){
    if( zType[0]=='c' ){
      const char *zPrefix = 0;
      static int markLeaves = -1;
      if( markLeaves<0 ){
        markLeaves = db_get_int("timeline-mark-leaves",1);
        if( markLeaves<0 ) markLeaves = 1;
      }
      if( strcmp(zUuid, MANIFEST_UUID)==0 ){
        /* This will only ever happen when Fossil is drawing a timeline for
        ** its own self-host repository.  If the timeline shows the specific
        ** check-in corresponding to the current executable, then tag that
        ** check-in with "self" */
        zPrefix = "self&nbsp;";
      }else if( markLeaves && db_column_int(pQuery,5) ){
        if( markLeaves==1 ){
          zPrefix = has_closed_tag(rid) ? "closed&nbsp;" : "leaf&nbsp;";
        }else{
          zPrefix = has_closed_tag(rid) ?
               "<span class='timelineLeaf'>Closed-Leaf</span>\n" :
               "<span class='timelineLeaf'>Leaf</span>\n";
        }
      }
      cgi_printf("%scheck-in:&nbsp;%z<span class='timelineHash'>"
                 "%S</span></a> ",
                  zPrefix, href("%R/info/%!S",zUuid),zUuid);
    }else if( zType[0]=='e' && tagid ){
      cgi_printf("technote:&nbsp;");
      hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
    }else{
      cgi_printf("artifact:&nbsp;%z%S</a> ",
                 href("%R/info/%!S",zUuid),zUuid);
    }
  }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t'
            || zType[0]=='n' || zType[0]=='f'){
    cgi_printf("artifact:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
  }

  if( (tmFlags & TIMELINE_SIMPLE)!=0 ){
    @ <span class='timelineEllipsis' id='ellipsis-%d(rid)' \
    @ data-id='%d(rid)'>...</span>
    @ <span class='clutter' id='detail-%d(rid)'>
  }

  if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){
    char *zLink;
    if( zType[0]!='f' || (tmFlags & TIMELINE_FORUMTXT)==0 ){
      zLink = mprintf("%R/timeline?u=%h&c=%t&y=a", zDispUser, zDate);
    }else{
      zLink = mprintf("%R/timeline?u=%h&c=%t&y=a&vfx", zDispUser, zDate);
    }
    cgi_printf("user:&nbsp;%z%h</a>", href("%z",zLink), zDispUser);
  }else{
    cgi_printf("user:&nbsp;%h", zDispUser);
  }

  /* Generate the "tags: TAGLIST" at the end of the comment, together
  ** with hyperlinks to the tag list.
  */
  if( zTagList && zTagList[0]==0 ) zTagList = 0;
  if( zTagList ){
    if( g.perm.Hyperlink ){
      int i;
      const char *z = zTagList;
      Blob links;
      blob_zero(&links);
      while( z && z[0] ){
        for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){}
        if( zThisTag==0 || memcmp(z, zThisTag, i)!=0 || zThisTag[i]!=0 ){
          blob_appendf(&links,
                "%z%#h</a>%.2s",
                href("%R/timeline?r=%#t&c=%t",i,z,zDate), i,z, &z[i]
          );
        }else{
          blob_appendf(&links, "%#h", i+2, z);
        }
        if( z[i]==0 ) break;
        z += i+2;
      }
      cgi_printf(" tags:&nbsp;%s", blob_str(&links));
      blob_reset(&links);
    }else{
      cgi_printf(" tags:&nbsp;%h", zTagList);
    }
  }

  if( tmFlags & TIMELINE_SHOWRID ){
    int srcId = delta_source_rid(rid);
    if( srcId ){
      cgi_printf(" id:&nbsp;%z%d&larr;%d</a>",
                 href("%R/deltachain/%d",rid), rid, srcId);
    }else{
      cgi_printf(" id:&nbsp;%z%d</a>",
                 href("%R/deltachain/%d",rid), rid);
    }
  }
  tag_private_status(rid);

  if( (tmFlags & TIMELINE_SIMPLE)!=0 ){
    cgi_printf("</span>");  /* End of the declutter span */
  }

  /* End timelineDetail */
  if( (tmFlags & TIMELINE_INLINE)!=0 ){
    cgi_printf(")");
  }
}


/*
** SETTING: timeline-truncate-at-blank  boolean default=off
**
** If enabled, check-in comments displayed on the timeline are truncated
** at the first blank line of the comment text.  The comment text after
** the first blank line is only seen in the /info or similar pages that
** show details about the check-in.
*/
/*
** SETTING: timeline-tslink-info       boolean default=off
**
** The hyperlink on the timestamp associated with each timeline entry,
** on the far left-hand side of the screen, normally targets another
** /timeline page that shows the entry in context.  However, if this
** option is turned on, that hyperlink targets the /info page showing
** the details of the entry.
*/
/*
** SETTING: timeline-mark-leaves       width=5 default=1
**
** Determine whether or not leaf check-ins are marked as such in the
** details section of the timeline.  The value is an integer between 0
** and 2:
**
**    0   Do not show any special marking for leaf check-ins.
** 
**    1   Show just "leaf" or "closed"
**
**    2   Show "Leaf" or "Closed-Leaf" with emphasis
**
** The default is currently 1.  Prior to 2025-10-19, the default was 2.
** This setting has no effect on the "Classic" view, which always behaves
** as if the setting were 2.
*/

/*
** Output a timeline in the web format given a query.  The query
** should return these columns:
**
**    0.  rid
**    1.  artifact hash
**    2.  Date/Time
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
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
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
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
447
448







-
+


-
+










-









+
+




+












+
+









-
-
+
-
-
-
-
-
















-

-
-
+







**   10.  Short comment to user for repeated tickets and wiki
*/
void www_print_timeline(
  Stmt *pQuery,            /* Query to implement the timeline */
  int tmFlags,             /* Flags controlling display behavior */
  const char *zThisUser,   /* Suppress links to this user */
  const char *zThisTag,    /* Suppress links to this tag */
  const char *zLeftBranch, /* Strive to put this branch on the left margin */
  Matcher *pLeftBranch,    /* Comparison function to use for zLeftBranch */
  int selectedRid,         /* Highlight the line with this RID value or zero */
  int secondRid,           /* Secondary highlight (or zero) */
  void (*xExtra)(int)      /* Routine to call on each line of display */
  void (*xExtra)(Stmt*,int,const char*,const char*)  /* generate "extra" text */
){
  int mxWikiLen;
  Blob comment;
  int prevTagid = 0;
  int suppressCnt = 0;
  char zPrevDate[20];
  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 check-out version */
  int dateFormat = 0;         /* 0: HH:MM (default) */
  int bCommentGitStyle = 0;   /* Only show comments through first blank line */
  const char *zStyle;         /* Sub-name for classes for the style */
  const char *zDateFmt;
  int iTableId = timeline_tableid();
  int bTimestampLinksToInfo;  /* True if timestamp hyperlinks go to the /info
                              ** page rather than the /timeline page */
  const char *zMainBranch = db_main_branch();


  if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){
    vid = db_lget_int("checkout", 0);
  }
  if( xExtra==0 ) xExtra = timeline_extra;
  zPrevDate[0] = 0;
  mxWikiLen = db_get_int("timeline-max-comment", 0);
  dateFormat = db_get_int("timeline-date-format", 0);
  bCommentGitStyle = db_get_int("timeline-truncate-at-blank", 0);
  bTimestampLinksToInfo = db_get_boolean("timeline-tslink-info", 0);
  if( (tmFlags & TIMELINE_VIEWS)==0 ){
    tmFlags |= timeline_ss_cookie();
  }
  if( tmFlags & TIMELINE_COLUMNAR ){
    zStyle = "Columnar";
  }else if( tmFlags & TIMELINE_COMPACT ){
    zStyle = "Compact";
  }else if( tmFlags & TIMELINE_SIMPLE ){
    zStyle = "Simple";
  }else if( tmFlags & TIMELINE_VERBOSE ){
    zStyle = "Verbose";
  }else if( tmFlags & TIMELINE_CLASSIC ){
    zStyle = "Classic";
  }else{
    zStyle = "Modern";
  }
  zDateFmt = P("datefmt");
  if( zDateFmt ) dateFormat = atoi(zDateFmt);
  if( tmFlags & TIMELINE_GRAPH ){
    pGraph = graph_init();
  pGraph = graph_init();
  }
  db_static_prepare(&qbranch,
    "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
    TAG_BRANCH
  );
  if( (tmFlags & TIMELINE_CHPICK)!=0
   && !db_table_exists("repository","cherrypick")
  ){
    tmFlags &= ~TIMELINE_CHPICK;
  }
  @ <table id="timelineTable%d(iTableId)" class="timelineTable"> \
  @ <!-- tmFlags: 0x%x(tmFlags) -->
  blob_zero(&comment);
  while( db_step(pQuery)==SQLITE_ROW ){
    int rid = db_column_int(pQuery, 0);
    const char *zUuid = db_column_text(pQuery, 1);
    int isLeaf = db_column_int(pQuery, 5);
    const char *zBgClr = db_column_text(pQuery, 6);
    const char *zDate = db_column_text(pQuery, 2);
    const char *zType = db_column_text(pQuery, 7);
    const char *zUser = db_column_text(pQuery, 4);
    const char *zTagList = db_column_text(pQuery, 8);
    int tagid = db_column_int(pQuery, 9);
    const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
    const char *zBr = 0;      /* Branch */
    char *zBr = 0;            /* Branch */
    int commentColumn = 3;    /* Column containing comment text */
    int modPending;           /* Pending moderation */
    char *zDateLink;          /* URL for the link on the timestamp */
    int drawDetailEllipsis;   /* True to show ellipsis in place of detail */
    int gidx = 0;             /* Graph row identifier */
    int isSelectedOrCurrent = 0;  /* True if current row is selected */
    const char *zExtraClass = "";
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


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
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
636

637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652







+
+


















+
+
+




-
-
-
-
-
-
+
-

+
+

-
+











+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



-








+







      }
    }else{
      zDateLink = mprintf("<a>");
    }
    @ <td class="timelineTime">%z(zDateLink)%s(zTime)</a></td>
    @ <td class="timelineGraph">
    if( tmFlags & (TIMELINE_UCOLOR|TIMELINE_DELTA|TIMELINE_NOCOLOR) ){
      /* Don't use the requested background color.  Use the background color
      ** override from query parameters instead. */
      if( tmFlags & TIMELINE_UCOLOR ){
        zBgClr = zUser ? user_color(zUser) : 0;
      }else if( tmFlags & TIMELINE_NOCOLOR ){
        zBgClr = 0;
      }else if( zType[0]=='c' ){
        static Stmt qdelta;
        db_static_prepare(&qdelta, "SELECT baseid IS NULL FROM plink"
                                   " WHERE cid=:rid");
        db_bind_int(&qdelta, ":rid", rid);
        if( db_step(&qdelta)!=SQLITE_ROW ){
          zBgClr = 0; /* Not a check-in */
        }else if( db_column_int(&qdelta, 0) ){
          zBgClr = hash_color("b");  /* baseline manifest */
        }else{
          zBgClr = hash_color("f");  /* delta manifest */
        }
        db_reset(&qdelta);
      }
    }else{
      /* Make sure the user-specified background color is reasonable */
      zBgClr = reasonable_bg_color(zBgClr, 0);
    }
    if( zType[0]=='c'
    && (pGraph || zBgClr==0 || (tmFlags & (TIMELINE_BRCOLOR|TIMELINE_DELTA))!=0)
    ){
      db_reset(&qbranch);
      db_bind_int(&qbranch, ":rid", rid);
      if( db_step(&qbranch)==SQLITE_ROW ){
        zBr = db_column_text(&qbranch, 0);
      }else{
        zBr = "trunk";
      zBr = branch_of_rid(rid);
      }
      if( zBgClr==0 || (tmFlags & TIMELINE_BRCOLOR)!=0 ){
        /* If no background color is specified, use a color based on the
        ** branch name */
        if( tmFlags & (TIMELINE_DELTA|TIMELINE_NOCOLOR) ){
        }else if( zBr==0 || strcmp(zBr,"trunk")==0 ){
        }else if( zBr==0 || strcmp(zBr, zMainBranch)==0 ){
          zBgClr = 0;
        }else{
          zBgClr = hash_color(zBr);
        }
      }
    }
    if( zType[0]=='c' && pGraph ){
      int nParent = 0;
      int nCherrypick = 0;
      GraphRowId aParent[GR_MAX_RAIL];
      static Stmt qparent;
      if( tmFlags & TIMELINE_GRAPH ){
      db_static_prepare(&qparent,
        "SELECT pid FROM plink"
        " WHERE cid=:rid AND pid NOT IN phantom"
        " ORDER BY isprim DESC /*sort*/"
      );
      db_bind_int(&qparent, ":rid", rid);
      while( db_step(&qparent)==SQLITE_ROW && nParent<count(aParent) ){
        aParent[nParent++] = db_column_int(&qparent, 0);
      }
      db_reset(&qparent);
      if( (tmFlags & TIMELINE_CHPICK)!=0 && nParent>0 ){
        static Stmt qcherrypick;
        db_static_prepare(&qcherrypick,
          "SELECT parentid FROM cherrypick"
          " WHERE childid=:rid AND parentid NOT IN phantom"
        );
        db_bind_int(&qcherrypick, ":rid", rid);
        while( db_step(&qcherrypick)==SQLITE_ROW && nParent<count(aParent) ){
          aParent[nParent++] = db_column_int(&qcherrypick, 0);
          nCherrypick++;
        }
        db_reset(&qcherrypick);
      }
        db_static_prepare(&qparent,
          "SELECT pid FROM plink"
          " WHERE cid=:rid AND pid NOT IN phantom"
          " ORDER BY isprim DESC /*sort*/"
        );
        db_bind_int(&qparent, ":rid", rid);
        while( db_step(&qparent)==SQLITE_ROW && nParent<count(aParent) ){
          aParent[nParent++] = db_column_int(&qparent, 0);
        }
        db_reset(&qparent);
        if( (tmFlags & TIMELINE_CHPICK)!=0 && nParent>0 ){
          static Stmt qcherrypick;
          db_static_prepare(&qcherrypick,
            "SELECT parentid FROM cherrypick"
            " WHERE childid=:rid AND parentid NOT IN phantom"
          );
          db_bind_int(&qcherrypick, ":rid", rid);
          while( db_step(&qcherrypick)==SQLITE_ROW && nParent<count(aParent) ){
            aParent[nParent++] = db_column_int(&qcherrypick, 0);
            nCherrypick++;
          }
          db_reset(&qcherrypick);
        }
      }
      gidx = graph_add_row(pGraph, rid, nParent, nCherrypick, aParent,
                           zBr, zBgClr, zUuid,
                           isLeaf ? isLeaf + 2 * has_closed_tag(rid) : 0);
      db_reset(&qbranch);
      @ <div id="m%d(gidx)" class="tl-nodemark"></div>
    }else if( zType[0]=='e' && pGraph && zBgClr && zBgClr[0] ){
      /* For technotes, make a graph node with nParent==(-1).  This will
      ** not actually draw anything on the graph, but it will set the
      ** background color of the timeline entry */
      gidx = graph_add_row(pGraph, rid, -1, 0, 0, zBr, zBgClr, zUuid, 0);
      @ <div id="m%d(gidx)" class="tl-nodemark"></div>
    }
    fossil_free(zBr);
    @</td>
    if( !isSelectedOrCurrent ){
      @ <td class="timeline%s(zStyle)Cell%s(zExtraClass)" id='mc%d(gidx)'>
    }else{
      @ <td class="timeline%s(zStyle)Cell%s(zExtraClass)">
    }
    if( pGraph ){
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
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
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







+
+
+
+

















-
+
-
-
-
-
+
+
-





+



-
+

















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-





-
+







          /* Assume this is an attachment message. It _might_ also
          ** be a legacy-format wiki log entry, in which case it
          ** will simply be rendered in the older format. */
          wiki_convert(&comment, 0, WIKI_INLINE);
        }
        wiki_hyperlink_override(0);
      }else{
        if( mxWikiLen>0 && blob_size(&comment)>mxWikiLen ){
          blob_truncate_utf8(&comment, mxWikiLen);
          blob_append(&comment, "...", 3);
        }
        wiki_convert(&comment, 0, WIKI_INLINE);
      }
    }else{
      if( bCommentGitStyle ){
        /* Truncate comment at first blank line */
        int ii, jj;
        int n = blob_size(&comment);
        char *z = blob_str(&comment);
        for(ii=0; ii<n; ii++){
          if( z[ii]=='\n' ){
            for(jj=ii+1; jj<n && z[jj]!='\n' && fossil_isspace(z[jj]); jj++){}
            if( z[jj]=='\n' ) break;
          }
        }
        z[ii] = 0;
        cgi_printf("%W",z);
      }else if( mxWikiLen>0 && (int)blob_size(&comment)>mxWikiLen ){
        Blob truncated;
        blob_truncate_utf8(&comment, mxWikiLen);
        blob_zero(&truncated);
        blob_append(&truncated, blob_buffer(&comment), mxWikiLen);
        blob_append(&truncated, "...", 3);
        @ %W(blob_str(&truncated))
        blob_append(&comment, "...", 3);
        @ %W(blob_str(&comment))
        blob_reset(&truncated);
        drawDetailEllipsis = 0;
      }else{
        cgi_printf("%W",blob_str(&comment));
      }
    }

    @ </span>
    blob_reset(&comment);

    /* Generate extra information and hyperlinks to follow the comment.
    /* Generate extra information and hyperlinks that follow the comment.
    ** Example:  "(check-in: [abcdefg], user: drh, tags: trunk)"
    */
    if( drawDetailEllipsis ){
      @ <span class='timelineEllipsis' id='ellipsis-%d(rid)' \
      @ data-id='%d(rid)'>...</span>
    }
    if( tmFlags & TIMELINE_COLUMNAR ){
      if( !isSelectedOrCurrent ){
        @ <td class="timelineDetailCell%s(zExtraClass)" id='md%d(gidx)'>
      }else{
        @ <td class="timelineDetailCell%s(zExtraClass)">
      }
    }
    if( tmFlags & TIMELINE_COMPACT ){
      cgi_printf("<span class='clutter' id='detail-%d'>",rid);
    }
    cgi_printf("<span class='timeline%sDetail'>", zStyle);
    if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){
      cgi_printf("(");
    }

    if( (tmFlags & TIMELINE_CLASSIC)==0 ){
      if( zType[0]=='c' ){
        if( isLeaf ){
          if( has_closed_tag(rid) ){
            @ <span class='timelineLeaf'>Closed-Leaf</span>
          }else{
            @ <span class='timelineLeaf'>Leaf</span>
          }
        }
        cgi_printf("check-in:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
      }else if( zType[0]=='e' && tagid ){
        cgi_printf("technote:&nbsp;");
        hyperlink_to_event_tagid(tagid<0?-tagid:tagid);
      }else{
        cgi_printf("artifact:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
      }
    }else if( zType[0]=='g' || zType[0]=='w' || zType[0]=='t'
              || zType[0]=='n' || zType[0]=='f'){
      cgi_printf("artifact:&nbsp;%z%S</a> ",href("%R/info/%!S",zUuid),zUuid);
    }

    if( g.perm.Hyperlink && fossil_strcmp(zDispUser, zThisUser)!=0 ){
      char *zLink;
      if( zType[0]!='f' || (tmFlags & TIMELINE_FORUMTXT)==0 ){
        zLink = mprintf("%R/timeline?u=%h&c=%t&y=a", zDispUser, zDate);
      }else{
        zLink = mprintf("%R/timeline?u=%h&c=%t&y=a&vfx", zDispUser, zDate);
      }
      cgi_printf("user:&nbsp;%z%h</a>", href("%z",zLink), zDispUser);
    }else{
      cgi_printf("user:&nbsp;%h", zDispUser);
    }

    /* Generate the "tags: TAGLIST" at the end of the comment, together
    ** with hyperlinks to the tag list.
    */
    if( zTagList && zTagList[0]==0 ) zTagList = 0;
    if( zTagList ){
      if( g.perm.Hyperlink ){
        int i;
        const char *z = zTagList;
        Blob links;
        blob_zero(&links);
        while( z && z[0] ){
          for(i=0; z[i] && (z[i]!=',' || z[i+1]!=' '); i++){}
          if( zThisTag==0 || memcmp(z, zThisTag, i)!=0 || zThisTag[i]!=0 ){
            blob_appendf(&links,
                  "%z%#h</a>%.2s",
                  href("%R/timeline?r=%#t&c=%t",i,z,zDate), i,z, &z[i]
            );
          }else{
            blob_appendf(&links, "%#h", i+2, z);
          }
          if( z[i]==0 ) break;
          z += i+2;
        }
        cgi_printf(" tags:&nbsp;%s", blob_str(&links));
        blob_reset(&links);
      }else{
        cgi_printf(" tags:&nbsp;%h", zTagList);
      }
    }

    if( tmFlags & TIMELINE_SHOWRID ){
      int srcId = delta_source_rid(rid);
      if( srcId ){
        cgi_printf(" id:&nbsp;%z%d&larr;%d</a>",
                   href("%R/deltachain/%d",rid), rid, srcId);
      }else{
        cgi_printf(" id:&nbsp;%z%d</a>",
                   href("%R/deltachain/%d",rid), rid);
      }
    }
    tag_private_status(rid);
    if( xExtra ){
      xExtra(rid);
    xExtra(pQuery, tmFlags, zThisUser, zThisTag);
    }
    /* End timelineDetail */
    if( (tmFlags & (TIMELINE_CLASSIC|TIMELINE_VERBOSE|TIMELINE_COMPACT))!=0 ){
      cgi_printf(")");
    }
    if( tmFlags & TIMELINE_COMPACT ){
      @ </span></span>
    }else{
      @ </span>
    }

  
    /* Generate the file-change list if requested */
    if( (tmFlags & (TIMELINE_FCHANGES|TIMELINE_FRENAMES))!=0
     && zType[0]=='c' && g.perm.Hyperlink
    ){
      int inUl = 0;
      if( !fchngQueryInit ){
        db_prepare(&fchngQuery,
811
812
813
814
815
816
817
818

819
820
821
822
823
824
825
902
903
904
905
906
907
908

909
910
911
912
913
914
915
916







-
+







    @ event%s(suppressCnt>1?"s":"") omitted.</span>
    suppressCnt = 0;
  }
  if( pendingEndTr ){
    @ </td></tr>
  }
  if( pGraph ){
    graph_finish(pGraph, zLeftBranch, tmFlags);
    graph_finish(pGraph, pLeftBranch, tmFlags);
    if( pGraph->nErr ){
      graph_free(pGraph);
      pGraph = 0;
    }else{
      @ <tr class="timelineBottom" id="btm-%d(iTableId)">\
      @ <td></td><td></td><td></td></tr>
    }
884
885
886
887
888
889
890

891

892
893
894
895
896
897
898
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991







+

+







    int iTopRow;         /* Index of the top row of the graph */
    int fileDiff;        /* True for file diff.  False for check-in diff */
    int omitDescenders;  /* True to omit descenders */
    int scrollToSelect;  /* True to scroll to the selection */
    int dwellTimeout;    /* Milliseconds to wait for tooltips to show */
    int closeTimeout;    /* Milliseconds to wait for tooltips to close */
    u8 *aiMap;           /* The rail map */
    u8 bNoGraph;         /* True to show a minimal graph */

    bNoGraph = (tmFlags & TIMELINE_GRAPH)==0;
    iRailPitch = atoi(PD("railpitch","0"));
    showArrowheads = skin_detail_boolean("timeline-arrowheads");
    circleNodes = skin_detail_boolean("timeline-circle-nodes");
    colorGraph = skin_detail_boolean("timeline-color-graph-lines");
    iTopRow = pGraph->pFirst ? pGraph->pFirst->idx : 0;
    omitDescenders = (tmFlags & TIMELINE_DISJOINT)!=0;
    fileDiff = (tmFlags & TIMELINE_FILEDIFF)!=0;
906
907
908
909
910
911
912
913

914
915
916
917
918
919
920
999
1000
1001
1002
1003
1004
1005

1006
1007
1008
1009
1010
1011
1012
1013







-
+







    @   "iRailPitch": %d(iRailPitch),
    @   "colorGraph": %d(colorGraph),
    @   "nomo": %d(PB("nomo")),
    @   "iTopRow": %d(iTopRow),
    @   "omitDescenders": %d(omitDescenders),
    @   "fileDiff": %d(fileDiff),
    @   "scrollToSelect": %d(scrollToSelect),
    @   "nrail": %d(pGraph->mxRail+1),
    @   "nrail": %d(bNoGraph?1:pGraph->mxRail+1),
    @   "baseUrl": "%R",
    @   "dwellTimeout": %d(dwellTimeout),
    @   "closeTimeout": %d(closeTimeout),
    @   "hashDigits": %d(hash_digits(1)),
    @   "bottomRowId": "btm-%d(iTableId)",
    if( pGraph->nRow==0 ){
      @   "rowinfo": null
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
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







-
+
















+
+
+
-
-
+
+
+










+
+
-
+







    **        node with the id equal to the value.  This is like "u" except
    **        that the line is dotted instead of solid and has no arrow.
    **        Mnemonic: "Same Branch".
    **    f:  0x01: a leaf node, 0x02: a closed leaf node.
    **   au:  An array of integers that define thick-line risers for branches.
    **        The integers are in pairs.  For each pair, the first integer is
    **        is the rail on which the riser should run and the second integer
    **        is the id of the node upto which the riser should run. If there
    **        is the id of the node up to which the riser should run. If there
    **        are no risers, this array does not exist.
    **   mi:  "merge-in".  An array of integer rail positions from which
    **        merge arrows should be drawn into this node.  If the value is
    **        negative, then the rail position is -1-mi[] and a thin merge-arrow
    **        descender is drawn to the bottom of the screen. This array is
    **        omitted if there are no inbound merges.
    **   ci:  "cherrypick-in". Like "mi" except for cherrypick merges.
    **        omitted if there are no cherrypick merges.
    **    h:  The artifact hash of the object being graphed
    *    br:  The branch to which the artifact belongs
    */
    aiMap = pGraph->aiRailMap;
    for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
      int k = 0;
      cgi_printf("{\"id\":%d,",     pRow->idx);
      cgi_printf("\"bg\":\"%s\",",  pRow->zBgClr);
      if( bNoGraph ){
        cgi_printf("\"r\":0,");  /* Chng to ":-1" to omit node circles */
      }else{
      cgi_printf("\"r\":%d,",       pRow->iRail>=0 ? aiMap[pRow->iRail] : -1);
      if( pRow->bDescender ){
        cgi_printf("\"r\":%d,",       pRow->iRail>=0 ? aiMap[pRow->iRail] : -1);
      }
      if( pRow->bDescender && !bNoGraph ){
        cgi_printf("\"d\":%d,",       pRow->bDescender);
      }
      if( pRow->mergeOut>=0 ){
        cgi_printf("\"mo\":%d,",      aiMap[pRow->mergeOut]);
        if( pRow->mergeUpto==0 ) pRow->mergeUpto = pRow->idx;
        cgi_printf("\"mu\":%d,",      pRow->mergeUpto);
        if( pRow->cherrypickUpto>0 && pRow->cherrypickUpto<=pRow->mergeUpto ){
          cgi_printf("\"cu\":%d,",    pRow->cherrypickUpto);
        }
      }
      if( bNoGraph ){
        cgi_printf("\"u\":-1,");
      if( pRow->isStepParent ){
      }else if( pRow->isStepParent ){
        cgi_printf("\"sb\":%d,",      pRow->aiRiser[pRow->iRail]);
      }else{
        cgi_printf("\"u\":%d,",       pRow->aiRiser[pRow->iRail]);
      }
      k = 0;
      if( pRow->isLeaf ) k |= 1;
      if( pRow->isLeaf & 2) k |= 2;
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
1142
1143
1144
1145
1146
1147
1148
1197
1198
1199
1200
1201
1202
1203





































1204
1205
1206
1207
1208
1209
1210







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







    @   event.mtime AS mtime
    @  FROM event CROSS JOIN blob
    @ WHERE blob.rid=event.objid
  ;
  return zBase;
}

/*
** Convert a symbolic name used as an argument to the a=, b=, or c=
** query parameters of timeline into a julianday mtime value.
*/
double symbolic_name_to_mtime(const char *z, const char **pzDisplay){
  double mtime;
  int rid;
  const char *zDate;
  if( z==0 ) return -1.0;
  if( fossil_isdate(z) ){
    mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())", z);
    if( mtime>0.0 ) return mtime;
  }
  zDate = fossil_expand_datetime(z, 1);
  if( zDate!=0 ){
    mtime = db_double(0.0, "SELECT julianday(%Q,fromLocal())",
                      fossil_roundup_date(zDate));
    if( mtime>0.0 ){
      if( pzDisplay ) *pzDisplay = fossil_strdup(zDate);
      return mtime;
    }
  }
  rid = symbolic_name_to_rid(z, "*");
  if( rid ){
    mtime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
  }else{
    mtime = db_double(-1.0,
        "SELECT max(event.mtime) FROM event, tag, tagxref"
        " WHERE tag.tagname GLOB 'event-%q*'"
        "   AND tagxref.tagid=tag.tagid AND tagxref.tagtype"
        "   AND event.objid=tagxref.rid",
        z
    );
  }
  return mtime;
}

/*
** zDate is a localtime date.  Insert records into the
** "timeline" table to cause <hr> to be inserted on zDate.
*/
static int timeline_add_divider(double rDate){
  int rid = db_int(-1,
    "SELECT rid FROM timeline ORDER BY abs(sortby-%.16g) LIMIT 1", rDate
1219
1220
1221
1222
1223
1224
1225
















1226
1227
1228
1229
1230
1231
1232
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    assert( i<=count(az) );
  }
  if( i>2 ){
    style_submenu_multichoice("y", i/2, az, isDisabled);
  }
}

/*
** SETTING: timeline-default-style            width=5 default=m
**
** This setting determines the default "view style" for timelines.
** The setting should be a single character, one of the following:
**
**    c     Compact
**    j     Columnar
**    m     Modern
**    s     Simple
**    v     Verbose
**    x     Classic
**
** The default value is m (Modern).
*/

/*
** Return the default value for the "ss" cookie or query parameter.
** The "ss" cookie determines the graph style.  See the
** timeline_view_styles[] global constant for a list of choices.
*/
const char *timeline_default_ss(void){
  static const char *zSs = 0;
1243
1244
1245
1246
1247
1248
1249

1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261

1262
1263
1264
1265
1266

1267
1268
1269
1270
1271
1272
1273
1321
1322
1323
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







+












+




-
+







  const char *v = cookie_value("ss",0);
  if( v==0 ) v = timeline_default_ss();
  switch( v[0] ){
    case 'c':  tmFlags = TIMELINE_COMPACT;  break;
    case 'v':  tmFlags = TIMELINE_VERBOSE;  break;
    case 'j':  tmFlags = TIMELINE_COLUMNAR; break;
    case 'x':  tmFlags = TIMELINE_CLASSIC;  break;
    case 's':  tmFlags = TIMELINE_SIMPLE;   break;
    default:   tmFlags = TIMELINE_MODERN;   break;
  }
  return tmFlags;
}

/* Available timeline display styles, together with their y= query
** parameter names.
*/
const char *const timeline_view_styles[] = {
  "m", "Modern View",
  "j", "Columnar View",
  "c", "Compact View",
  "s", "Simple View",
  "v", "Verbose View",
  "x", "Classic View",
};
#if INTERFACE
# define N_TIMELINE_VIEW_STYLE 5
# define N_TIMELINE_VIEW_STYLE 6
#endif

/*
** Add the select/option box to the timeline submenu that is used to
** set the ss= parameter that determines the viewing mode.
**
** Return the TIMELINE_* value appropriate for the view-style.
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
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
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
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
1423
1424
1425
1426
1427
1428
1429
1430
1431

1432
1433
1434
1435
1436
1437
1438
1439

1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
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
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
1517
1518
1519
1520
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
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
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






















































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
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435

1436
1437
1438
1439

1440


1441

1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
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
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
1517
1518
1519
1520
1521
1522

1523
1524
1525
1526
1527
1528
1529
1530
1531







-
+

-
-
+
+
+






-
+





-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

-
+
-
-
-
-
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-










-
-
+
+
-



-
+
+




+
+
+
+
+
+



-
+



-
+
-
-
+
-







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





-
+





-
-
+
+











+














-
+
+







** for the SQL statement under construction that excludes any check-in that
** does not modify one or more files matching the globs.
*/
static void addFileGlobExclusion(
  const char *zChng,        /* The filename GLOB list */
  Blob *pSql                /* The SELECT statement under construction */
){
  if( zChng==0 || zChng[0]==0 ) return;
  if( zChng==0 ) return;
  blob_append_sql(pSql," AND event.objid IN ("
      "SELECT mlink.mid FROM mlink, filename"
      " WHERE mlink.fnid=filename.fnid AND %s)",
      "SELECT mlink.mid FROM mlink, filename\n"
      " WHERE mlink.fnid=filename.fnid\n"
      "   AND %s)",
      glob_expr("filename.name", mprintf("\"%s\"", zChng)));
}
static void addFileGlobDescription(
  const char *zChng,        /* The filename GLOB list */
  Blob *pDescription        /* Result description */
){
  if( zChng==0 || zChng[0]==0 ) return;
  if( zChng==0 ) return;
  blob_appendf(pDescription, " that include changes to files matching '%h'",
               zChng);
}

/*
** Tag match expression type code.
*/
typedef enum {
  MS_EXACT,   /* Matches a single tag by exact string comparison. */
  MS_GLOB,    /* Matches tags against a list of GLOB patterns. */
** If zChng is not NULL, then use it as a comma-separated list of
  MS_LIKE,    /* Matches tags against a list of LIKE patterns. */
  MS_REGEXP,  /* Matches tags against a list of regular expressions. */
  MS_BRLIST,  /* Same as REGEXP, except the regular expression is a list
              ** of branch names */
} MatchStyle;

** glob patterns for filenames, and remove from the "ok" table any
/*
** Quote a tag string by surrounding it with double quotes and preceding
** internal double quotes and backslashes with backslashes.
*/
static const char *tagQuote(
   int len,         /* Maximum length of zTag, or negative for unlimited */
   const char *zTag /* Tag string */
){
  Blob blob = BLOB_INITIALIZER;
  int i, j;
  blob_zero(&blob);
  blob_append(&blob, "\"", 1);
  for( i=j=0; zTag[j] && (len<0 || j<len); ++j ){
    if( zTag[j]=='\"' || zTag[j]=='\\' ){
      if( j>i ){
        blob_append(&blob, zTag+i, j-i);
      }
      blob_append(&blob, "\\", 1);
      i = j;
    }
  }
  if( j>i ){
    blob_append(&blob, zTag+i, j-i);
  }
  blob_append(&blob, "\"", 1);
  return blob_str(&blob);
}

** check-ins that do not modify one or more of the files identified
/*
** Construct the tag match SQL expression.
**
** by zChng.
** This function is adapted from glob_expr() to support the MS_EXACT, MS_GLOB,
** MS_LIKE, MS_REGEXP, and MS_BRLIST match styles.
**
** For MS_EXACT, the returned expression
** checks for integer match against the tag ID which is looked up directly by
** this function.  For the other modes, the returned SQL expression performs
** string comparisons against the tag names, so it is necessary to join against
** the tag table to access the "tagname" column.
**
** Each pattern is adjusted to to start with "sym-" and be anchored at end.
**
** In MS_REGEXP mode, backslash can be used to protect delimiter characters.
** The backslashes are not removed from the regular expression.
**
** In addition to assembling and returning an SQL expression, this function
** makes an English-language description of the patterns being matched, suitable
** for display in the web interface.
**
** If any errors arise during processing, *zError is set to an error message.
** Otherwise it is set to NULL.
*/
static const char *tagMatchExpression(
static void removeFileGlobFromOk(
  MatchStyle matchStyle,        /* Match style code */
  const char *zTag,             /* Tag name, match pattern, or pattern list */
  const char **zDesc,           /* Output expression description string */
  const char **zError           /* Output error string */
  const char *zChng         /* The filename GLOB list */
){
  Blob expr = BLOB_INITIALIZER; /* SQL expression string assembly buffer */
  Blob desc = BLOB_INITIALIZER; /* English description of match patterns */
  Blob err = BLOB_INITIALIZER;  /* Error text assembly buffer */
  const char *zStart;           /* Text at start of expression */
  const char *zDelimiter;       /* Text between expression terms */
  const char *zEnd;             /* Text at end of expression */
  const char *zPrefix;          /* Text before each match pattern */
  const char *zSuffix;          /* Text after each match pattern */
  const char *zIntro;           /* Text introducing pattern description */
  const char *zPattern = 0;     /* Previous quoted pattern */
  const char *zFail = 0;        /* Current failure message or NULL if okay */
  const char *zOr = " or ";     /* Text before final quoted pattern */
  char cDel;                    /* Input delimiter character */
  int i;                        /* Input match pattern length counter */

  /* Optimize exact matches by looking up the ID in advance to create a simple
   * numeric comparison.  Bypass the remainder of this function. */
  if( matchStyle==MS_EXACT ){
    *zDesc = tagQuote(-1, zTag);
    return mprintf("(tagid=%d)", db_int(-1,
        "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag));
  }

  /* Decide pattern prefix and suffix strings according to match style. */
  if( matchStyle==MS_GLOB ){
    zStart = "(";
    zDelimiter = " OR ";
    zEnd = ")";
    zPrefix = "tagname GLOB 'sym-";
    zSuffix = "'";
    zIntro = "glob pattern ";
  }else if( matchStyle==MS_LIKE ){
    zStart = "(";
    zDelimiter = " OR ";
    zEnd = ")";
    zPrefix = "tagname LIKE 'sym-";
    zSuffix = "'";
    zIntro = "SQL LIKE pattern ";
  }else if( matchStyle==MS_REGEXP ){
    zStart = "(tagname REGEXP '^sym-(";
    zDelimiter = "|";
    zEnd = ")$')";
    zPrefix = "";
    zSuffix = "";
    zIntro = "regular expression ";
  }else/* if( matchStyle==MS_BRLIST )*/{
    zStart = "tagname IN ('sym-";
    zDelimiter = "','sym-";
    zEnd = "')";
    zPrefix = "";
    zSuffix = "";
    zIntro = "";
  }

  if( zChng==0 ) return;
  /* Convert the list of matches into an SQL expression and text description. */
  blob_zero(&expr);
  blob_zero(&desc);
  blob_zero(&err);
  while( 1 ){
    /* Skip leading delimiters. */
    for( ; fossil_isspace(*zTag) || *zTag==','; ++zTag );

  db_multi_exec(
    /* Next non-delimiter character determines quoting. */
    if( !*zTag ){
      /* Terminate loop at end of string. */
      break;
    }else if( *zTag=='\'' || *zTag=='"' ){
      /* If word is quoted, prepare to stop at end quote. */
      cDel = *zTag;
      ++zTag;
    }else{
      /* If word is not quoted, prepare to stop at delimiter. */
      cDel = ',';
    }

    "DELETE FROM ok WHERE rid NOT IN (\n"
    /* Find the next delimiter character or end of string. */
    for( i=0; zTag[i] && zTag[i]!=cDel; ++i ){
      /* If delimiter is comma, also recognize spaces as delimiters. */
      if( cDel==',' && fossil_isspace(zTag[i]) ){
        break;
      }

    "  SELECT mlink.mid FROM mlink, filename\n"
      /* In regexp mode, ignore delimiters following backslashes. */
      if( matchStyle==MS_REGEXP && zTag[i]=='\\' && zTag[i+1] ){
        ++i;
      }
    }

    "   WHERE mlink.fnid=filename.fnid\n"
    /* Check for regular expression syntax errors. */
    if( matchStyle==MS_REGEXP ){
      ReCompiled *regexp;
      char *zTagDup = fossil_strndup(zTag, i);
      zFail = re_compile(&regexp, zTagDup, 0);
      re_free(regexp);
      fossil_free(zTagDup);
    }

    "     AND %z);\n",
    /* Process success and error results. */
    if( !zFail ){
      /* Incorporate the match word into the output expression.  %q is used to
       * protect against SQL injection attacks by replacing ' with ''. */
      blob_appendf(&expr, "%s%s%#q%s", blob_size(&expr) ? zDelimiter : zStart,
          zPrefix, i, zTag, zSuffix);

    glob_expr("filename.name", zChng)
      /* Build up the description string. */
      if( !blob_size(&desc) ){
        /* First tag: start with intro followed by first quoted tag. */
        blob_append(&desc, zIntro, -1);
        blob_append(&desc, tagQuote(i, zTag), -1);
      }else{
        if( zPattern ){
          /* Third and subsequent tags: append comma then previous tag. */
          blob_append(&desc, ", ", 2);
          blob_append(&desc, zPattern, -1);
          zOr = ", or ";
        }

  );
        /* Second and subsequent tags: store quoted tag for next iteration. */
        zPattern = tagQuote(i, zTag);
      }
    }else{
      /* On error, skip the match word and build up the error message buffer. */
      if( !blob_size(&err) ){
        blob_append(&err, "Error: ", 7);
      }else{
        blob_append(&err, ", ", 2);
      }
      blob_appendf(&err, "(%s%s: %s)", zIntro, tagQuote(i, zTag), zFail);
    }

    /* Advance past all consumed input characters. */
    zTag += i;
    if( cDel!=',' && *zTag==cDel ){
      ++zTag;
    }
  }

  /* Finalize and extract the pattern description. */
  if( zPattern ){
    blob_append(&desc, zOr, -1);
    blob_append(&desc, zPattern, -1);
  }
  *zDesc = blob_str(&desc);

  /* Finalize and extract the error text. */
  *zError = blob_size(&err) ? blob_str(&err) : 0;

  /* Finalize and extract the SQL expression. */
  if( blob_size(&expr) ){
    blob_append(&expr, zEnd, -1);
    return blob_str(&expr);
  }

  /* If execution reaches this point, the pattern was empty.  Return NULL. */
  return 0;
}

/*
** Similar to fossil_expand_datetime()
**
** Add missing "-" characters into a date/time.  Examples:
**
**       20190419  =>  2019-04-19
**       201904    =>  2019-04
*/
const char *timeline_expand_datetime(const char *zIn){
  static char zEDate[20];
const char *timeline_expand_datetime(const char *zIn, int *pbZulu){
  static char zEDate[16];
  static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' };
  int n = (int)strlen(zIn);
  int i, j;

  /* Only three forms allowed:
  /* These forms are recognized:
  **
  **   (1)  YYYYMMDD
  **   (2)  YYYYMM
  **   (3)  YYYYWW
  */
  if( n && (zIn[n-1]=='Z' || zIn[n-1]=='z') ){
    n--;
    if( pbZulu ) *pbZulu = 1;
  }else{
    if( pbZulu ) *pbZulu = 0;
  }
  if( n!=8 && n!=6 ) return zIn;

  /* Every character must be a digit */
  for(i=0; fossil_isdigit(zIn[i]); i++){}
  for(i=0; i<n && fossil_isdigit(zIn[i]); i++){}
  if( i!=n ) return zIn;

  /* Expand the date */
  for(i=j=0; zIn[i]; i++){
  for(i=j=0; i<n; i++){
    if( i>=4 && (i%2)==0 ){
      zEDate[j++] = aPunct[i/2];
    if( j==4 || j==7 ) zEDate[j++] = '-';
    }
    zEDate[j++] = zIn[i];
  }
  zEDate[j] = 0;

  /* It looks like this may be a date.  Return it with punctuation added. */
  return zEDate;
}

/*
** Check to see if the argument is a date-span for the ymd= query
** parameter.  A valid date-span is of the form:
**
**       0123456789 123456  <-- index
**       YYYYMMDD-YYYYMMDD
**
** with an optional "Z" timeline modifier at the end.  Return true if
** the input is a valid date space and false if not.
*/
static int timeline_is_datespan(const char *zDay){
  size_t n = strlen(zDay);
  int i, d, m;

  if( n<17 || n>18 ) return 0;
  if( n==18 ){
    if( zDay[17]!='Z' && zDay[17]!='z' ) return 0;
    n--;
  }
  if( zDay[8]!='-' ) return 0;
  for(i=0; i<17 && (fossil_isdigit(zDay[i]) || i==8); i++){}
  if( i!=17 ) return 0;
  i = atoi(zDay);
  d = i%100;
  if( d<1 || d>31 ) return 0;
  m = (i/100)%100;
  if( m<1 || m>12 ) return 0;
  i = atoi(zDay+9);
  d = i%100;
  if( d<1 || d>31 ) return 0;
  m = (i/100)%100;
  if( m<1 || m>12 ) return 0;
  return 1;
}

/*
** Find the first check-in encountered with a particular tag
** when moving either forwards are backwards in time from a
** particular starting point (iFrom).  Return the rid of that
** first check-in.  If there are no check-ins in the decendent
** first check-in.  If there are no check-ins in the descendant
** or ancestor set of check-in iFrom that match the tag, then
** return 0.
*/
static int timeline_endpoint(
  int iFrom,         /* Starting point */
  const char *zEnd,  /* Tag we are searching for */   
  int bForward       /* 1: forwards in time (descendents) 0: backwards */
  const char *zEnd,  /* Tag we are searching for */
  int bForward       /* 1: forwards in time (descendants) 0: backwards */
){
  int tagId;
  int endId = 0;
  Stmt q;
  int ans = 0;

  tagId = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zEnd);
  if( tagId==0 ){
    endId = symbolic_name_to_rid(zEnd, "ci");
    if( endId==0 ) return 0;
  }
  db_pause_dml_log();
  if( bForward ){
    if( tagId ){
      db_prepare(&q,
        "WITH RECURSIVE dx(id,mtime) AS ("
        "  SELECT %d, event.mtime FROM event WHERE objid=%d"
        "  UNION"
        "  SELECT plink.cid, plink.mtime"
        "    FROM dx, plink"
        "   WHERE plink.pid=dx.id"
        "     AND plink.mtime<=(SELECT max(event.mtime) FROM tagxref, event"
                               " WHERE tagxref.tagid=%d AND tagxref.tagtype>0"
                               " AND event.objid=tagxref.rid)"
        "   ORDER BY plink.mtime)"
        "SELECT id FROM dx, tagxref"
        " WHERE tagid=%d AND tagtype>0 AND rid=id LIMIT 1",
        " WHERE tagid=%d AND tagtype>0 AND rid=id"
        " ORDER BY dx.mtime LIMIT 1",
        iFrom, iFrom, tagId, tagId
      );
    }else{
      db_prepare(&q,
        "WITH RECURSIVE dx(id,mtime) AS ("
        "  SELECT %d, event.mtime FROM event WHERE objid=%d"
        "  UNION"
1634
1635
1636
1637
1638
1639
1640
1641


1642
1643
1644
1645
1646
1647
1648
1548
1549
1550
1551
1552
1553
1554

1555
1556
1557
1558
1559
1560
1561
1562
1563







-
+
+







        "    FROM dx, plink, event"
        "   WHERE plink.cid=dx.id AND event.objid=plink.pid"
        "     AND event.mtime>=(SELECT min(event.mtime) FROM tagxref, event"
                               " WHERE tagxref.tagid=%d AND tagxref.tagtype>0"
                               " AND event.objid=tagxref.rid)"
        "   ORDER BY event.mtime DESC)"
        "SELECT id FROM dx, tagxref"
        " WHERE tagid=%d AND tagtype>0 AND rid=id LIMIT 1",
        " WHERE tagid=%d AND tagtype>0 AND rid=id"
        " ORDER BY dx.mtime DESC LIMIT 1",
        iFrom, iFrom, tagId, tagId
      );
    }else{
      db_prepare(&q,
        "WITH RECURSIVE dx(id,mtime) AS ("
        "  SELECT %d, event.mtime FROM event WHERE objid=%d"
        "  UNION"
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
1571
1572
1573
1574
1575
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
1632
1633

1634
1635
1636
1637
1638
1639
1640
1641







+


+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+






-
-
-
+
+
+



-
+







      );
    }
  }
  if( db_step(&q)==SQLITE_ROW ){
    ans = db_column_int(&q, 0);
  }
  db_finalize(&q);
  db_unpause_dml_log();
  return ans;
}

/*
** Add to the (temp) table zTab, RID values for every check-in
** identifier found on the zExtra string.  Check-in names can be separated
** by commas or by whitespace.
*/
static void add_extra_rids(const char *zTab, const char *zExtra){
  int ii;
  int rid;
  int cnt;
  Blob sql;
  char *zX;
  char *zToDel;
  if( zExtra==0 ) return;
  cnt = 0;
  blob_init(&sql, 0, 0);
  zX = zToDel = fossil_strdup(zExtra);
  blob_append_sql(&sql, "INSERT OR IGNORE INTO \"%w\" VALUES", zTab);
  while( zX[0] ){
    char c;
    if( zX[0]==',' || zX[0]==' ' ){ zX++; continue; }
    for(ii=1; zX[ii] && zX[ii]!=',' && zX[ii]!=' '; ii++){}
    c = zX[ii];
    zX[ii] = 0;
    rid = name_to_rid(zX);
    if( rid>0 ){
      if( (cnt%10)==4 ){
        blob_append_sql(&sql,",\n ");
      }else if( cnt>0 ){
        blob_append_sql(&sql,",");
      }
      blob_append_sql(&sql, "(%d)", rid);
      cnt++;
    }
    zX[ii] = c;
    zX += ii;
  }
  if( cnt ) db_exec_sql(blob_sql_text(&sql));
  blob_reset(&sql);
  fossil_free(zToDel);
}

/*
** COMMAND: test-endpoint
**
** Usage: fossil test-endpoint BASE TAG ?OPTIONS?
**
** Show the first check-in with TAG that is a descendent or ancestor
** of BASE.  The first descendent checkin is shown by default.  Use
** the --backto to see the first ancestor checkin.
** Show the first check-in with TAG that is a descendant or ancestor
** of BASE.  The first descendant check-in is shown by default.  Use
** the --backto to see the first ancestor check-in.
**
** Options:
**
**      --backto            Show ancestor.  Others defaults to descendents.
**      --backto            Show ancestor.  Others defaults to descendants.
*/
void timeline_test_endpoint(void){
  int bForward = find_option("backto",0,0)==0;
  int from_rid;
  int ans;
  db_find_and_open_repository(0, 0);
  verify_all_options();
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
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
1776
1777
1778
1779
1780







1781
1782
1783
1784
1785
1786
1787
1788
1789

1790
1791
1792
1793
1794
1795
1796
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
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
1746
1747
1748
1749
1750
1751
1752
1753
1754

1755
1756
1757
1758
1759
1760
1761
1762







-
-
+
+


-
+




-
-
+
+
















-
+

-
-
+
+

-
+

-
-
-
+
+
+
+
+

-
+

-
+
+












-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+

-
-
+
+

-
+







-
-
-
-
-
+
+
+
+
+
+
+








-
+









/*
** WEBPAGE: timeline
**
** Query parameters:
**
**    a=TIMEORTAG     Show events after TIMEORTAG
**    b=TIMEORTAG     Show events before TIMEORTAG
**    a=TIMEORTAG     Show events after TIMEORTAG.
**    b=TIMEORTAG     Show events before TIMEORTAG.
**    c=TIMEORTAG     Show events that happen "circa" TIMEORTAG
**    cf=FILEHASH     Show events around the time of the first use of
**                    the file with FILEHASH
**                    the file with FILEHASH.
**    m=TIMEORTAG     Highlight the event at TIMEORTAG, or the closest available
**                    event if TIMEORTAG is not part of the timeline.  If
**                    the t= or r= is used, the m event is added to the timeline
**                    if it isn't there already.
**    x=HASHLIST      Show all check-ins in the comma-separated HASHLIST
**                    in addition to check-ins specified by t= or r=
**    x=LIST          Show check-ins in the comma- or space-separated LIST
**                    in addition to check-ins specified by other parameters.
**    sel1=TIMEORTAG  Highlight the check-in at TIMEORTAG if it is part of
**                    the timeline.  Similar to m= except TIMEORTAG must
**                    match a check-in that is already in the timeline.
**    sel2=TIMEORTAG  Like sel1= but use the secondary highlight.
**    n=COUNT         Maximum number of events. "all" for no limit
**    n1=COUNT        Same as "n" but doesn't set the display-preference cookie
**                       Use "n1=COUNT" for a one-time display change
**    p=CHECKIN       Parents and ancestors of CHECKIN
**                       bt=PRIOR   ... going back to PRIOR
**                       p2=CKIN2   ... use CKIN2 if CHECKIN is not found
**    d=CHECKIN       Children and descendants of CHECKIN
**                       d2=CKIN2        ... Use CKIN2 if CHECKIN is not found
**                       ft=DESCENDANT   ... going forward to DESCENDANT
**    dp=CHECKIN      Same as 'd=CHECKIN&p=CHECKIN'
**    dp2=CKIN2       Same as 'd2=CKIN2&p2=CKIN2'
**    df=CHECKIN      Same as 'd=CHECKIN&n1=all&nd'.  Mnemonic: "Derived From"
**    bt=CHECKIN      "Back To".  Show ancenstors going back to CHECKIN
**    bt=CHECKIN      "Back To".  Show ancestors going back to CHECKIN
**                       p=CX       ... from CX back to time of CHECKIN
**                       from=CX    ... shortest path from CX back to CHECKIN
**    ft=CHECKIN      "Forward To":  Show decendents forward to CHECKIN
**                       from=CX    ... path from CX back to CHECKIN
**    ft=CHECKIN      "Forward To":  Show descendants forward to CHECKIN
**                       d=CX       ... from CX up to the time of CHECKIN
**                       from=CX    ... shortest path from CX up to CHECKIN
**                       from=CX    ... path from CX up to CHECKIN
**    t=TAG           Show only check-ins with the given TAG
**    r=TAG           Show check-ins related to TAG, equivalent to t=TAG&rel
**    tl=TAGLIST      Shorthand for t=TAGLIST&ms=brlist
**    rl=TAGLIST      Shorthand for r=TAGLIST&ms=brlist
**    r=TAG           Same as 't=TAG&rel'.  Mnemonic: "Related"
**    tl=TAGLIST      Same as 't=TAGLIST&ms=brlist'.  Mnemonic: "Tag List"
**    rl=TAGLIST      Same as 'r=TAGLIST&ms=brlist'.  Mnemonic: "Related List"
**    ml=TAGLIST      Same as 'tl=TAGLIST&mionly'.  Mnemonic: "Merge-in List"
**    sl=TAGLIST      "Sort List". Draw TAGLIST branches ordered left to right.
**    rel             Show related check-ins as well as those matching t=TAG
**    mionly          Limit rel to show ancestors but not descendants
**    mionly          Show related parents but not related children.
**    nowiki          Do not show wiki associated with branch or tag
**    ms=MATCHSTYLE   Set tag match style to EXACT, GLOB, LIKE, REGEXP
**    ms=MATCHSTYLE   Set tag name match algorithm.  One of "exact", "glob",
**                    "like", or "regexp".
**    u=USER          Only show items associated with USER
**    y=TYPE          'ci', 'w', 't', 'n', 'e', 'f', or 'all'.
**    ss=VIEWSTYLE    c: "Compact", v: "Verbose", m: "Modern", j: "Columnar",
**                    x: "Classic".
**    advm            Use the "Advanced" or "Busy" menu design.
**    ng              No Graph.
**    ncp             Omit cherrypick merges
**    nd              Do not highlight the focus check-in
**    nsm             Omit the submenu
**    nc              Omit all graph colors other than highlights
**    v               Show details of files changed
**    vfx             Show complete text of forum messages
**    f=CHECKIN       Show family (immediate parents and children) of CHECKIN
**    from=CHECKIN    Path from...
**                       to=CHECKIN      ... to this
**                       to2=CHECKIN     ... backup name if to= doesn't resolve
**                       shortest        ... show only the shortest path
**                       rel             ... also show related checkins
**                       bt=PRIOR        ... path from CHECKIN back to PRIOR
**                       ft=LATER        ... path from CHECKIN forward to LATER
**    f=CHECKIN       Family (immediate parents and children) of CHECKIN
**    from=CHECKIN    Path through common ancestor from CHECKIN...
**                       to=CHECKIN   ... to this
**                       to2=CHECKIN  ... backup name if to= doesn't resolve
**                       shortest     ... pick path with least number of nodes
**                       rel          ... also show related checkins
**                       min          ... hide long sequences along same branch
**                       bt=PRIOR     ... path from CHECKIN back to PRIOR
**                       ft=LATER     ... path from CHECKIN forward to LATER
**    me=CHECKIN      Most direct path from CHECKIN...
**                       you=CHECKIN  ... to this
**                       rel          ... also show related checkins
**    uf=FILE_HASH    Show only check-ins that contain the given file version
**                       All qualifying check-ins are shown unless there is
**                       also an n= or n1= query parameter.
**                    All qualifying check-ins are shown unless there is
**                    also an n= or n1= query parameter.
**    chng=GLOBLIST   Show only check-ins that involve changes to a file whose
**                       name matches one of the comma-separate GLOBLIST
**                    name matches one of the comma-separated GLOBLIST
**    brbg            Background color determined by branch name
**    ubg             Background color determined by user
**    deltabg         Background color red for delta manifests or green
**                    for baseline manifests
**    namechng        Show only check-ins that have filename changes
**    forks           Show only forks and their children
**    cherrypicks     Show all cherrypicks
**    ym=YYYY-MM      Show only events for the given year/month
**    yw=YYYY-WW      Show only events for the given week of the given year
**    yw=YYYY-MM-DD   Show events for the week that includes the given day
**    ymd=YYYY-MM-DD  Show only events on the given day. The use "ymd=now"
**                    to see all changes for the current week.
**    ym=YYYYMM       Show only events for the given year/month
**    yw=YYYYWW       Show only events for the given week of the given year
**    yw=YYYYMMDD     Show events for the week that includes the given day
**    ymd=YYYYMMDD    Show only events on the given day. The use "ymd=now"
**                    to see all changes for the current week.  Add "z" at end
**                    to divide days at UTC instead of localtime days.
**                    Use ymd=YYYYMMDD-YYYYMMDD (with optional "z") for a range.
**    year=YYYY       Show only events on the given year. The use "year=0"
**                    to see all changes for the current year.
**    days=N          Show events over the previous N days
**    datefmt=N       Override the date format:  0=HH:MM, 1=HH:MM:SS,
**                    2=YYYY-MM-DD HH:MM:SS, 3=YYMMDD HH:MM, and 4 means "off".
**    bisect          Show the check-ins that are in the current bisect
**    oldestfirst     Show events oldest first.
**    showid          Show RIDs
**    showsql         Show the SQL text
**    showsql         Show the SQL used to generate the report
**
** p= and d= can appear individually or together.  If either p= or d=
** appear, then u=, y=, a=, and b= are ignored.
**
** If both a= and b= appear then both upper and lower bounds are honored.
**
** When multiple time-related filters are used, e.g. ym, yw, and ymd,
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
1862
1863
1864
1865
1866



1867

1868
1869




1870
1871
1872
1873
1874
1875
1876
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







-
+







-
+



-
+














+
+
+

+


+
+
+
+







  int nDays = 0;                     /* Numeric value for zNDays */
  const char *zChng = P("chng");     /* List of GLOBs for files that changed */
  int useDividers = P("nd")==0;      /* Show dividers if "nd" is missing */
  int renameOnly = P("namechng")!=0; /* Show only check-ins that rename files */
  int forkOnly = PB("forks");        /* Show only forks and their children */
  int bisectLocal = PB("bisect");    /* Show the check-ins of the bisect */
  const char *zBisect = P("bid");    /* Bisect description */
  int cpOnly = PB("cherrypicks");    /* Show all cherrypick checkins */
  int cpOnly = PB("cherrypicks");    /* Show all cherrypick check-ins */
  int tmFlags = 0;                   /* Timeline flags */
  const char *zThisTag = 0;          /* Suppress links to this tag */
  const char *zThisUser = 0;         /* Suppress links to this user */
  HQuery url;                        /* URL for various branch links */
  int from_rid = name_to_typed_rid(P("from"),"ci"); /* from= for paths */
  const char *zTo2 = 0;
  int to_rid = name_choice("to","to2",&zTo2);    /* to= for path timelines */
  int noMerge = P("shortest")==0;           /* Follow merge links if shorter */
  int bShort = P("shortest")!=0;                 /* shortest possible path */
  int me_rid = name_to_typed_rid(P("me"),"ci");  /* me= for common ancestory */
  int you_rid = name_to_typed_rid(P("you"),"ci");/* you= for common ancst */
  int pd_rid;
  const char *zDPName;                /* Value of p=, d=, or dp= params */
  const char *zDPNameP, *zDPNameD;    /* Value of p=, d=, or dp= params */
  double rBefore, rAfter, rCirca;     /* Boundary times */
  const char *z;
  char *zOlderButton = 0;             /* URL for Older button at the bottom */
  char *zOlderButtonLabel = 0;        /* Label for the Older Button */
  char *zNewerButton = 0;             /* URL for Newer button at the top */
  char *zNewerButtonLabel = 0;        /* Label for the Newer button */
  int selectedRid = 0;                /* Show a highlight on this RID */
  int secondaryRid = 0;               /* Show secondary highlight */
  int disableY = 0;                   /* Disable type selector on submenu */
  int advancedMenu = 0;               /* Use the advanced menu design */
  char *zPlural;                      /* Ending for plural forms */
  int showCherrypicks = 1;            /* True to show cherrypick merges */
  int haveParameterN;                 /* True if n= query parameter present */
  int from_to_mode = 0;               /* 0: from,to. 1: from,ft 2: from,bt */
  int showSql = PB("showsql");        /* True to show the SQL */
  Blob allSql;                        /* Copy of all SQL text */
  int bMin = P("min")!=0;             /* True if "min" query parameter used */

  login_check_credentials();
  url_initialize(&url, "timeline");
  cgi_query_parameters_to_url(&url);
  blob_init(&allSql, 0, 0);

  /* The "mionly" query parameter is like "rel", but shows merge-ins only */
  if( P("mionly")!=0 ) related = 2;

  (void)P_NoBot("ss")
    /* "ss" is processed via the udc but at least one spider likes to
    ** try to SQL inject via this argument, so let's catch that. */;

  /* Set number of rows to display */
  z = P("n");
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
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







-
-
+
+







-
+

+







      }
    }
  }else{
    nEntry = 50;
  }

  /* Query parameters d=, p=, and f= and variants */
  p_rid = name_choice("p","p2", &zDPName);
  d_rid = name_choice("d","d2", &zDPName);
  p_rid = name_choice("p","p2", &zDPNameP);
  d_rid = name_choice("d","d2", &zDPNameD);
  z = P("f");
  f_rid = z ? name_to_typed_rid(z,"ci") : 0;
  z = P("df");
  if( z && (d_rid = name_to_typed_rid(z,"ci"))!=0 ){
    nEntry = 0;
    useDividers = 0;
    cgi_replace_query_parameter("d",fossil_strdup(z));
    zDPName = z;
    zDPNameD = zDPNameP = z;
  }
  if( zChng && zChng[0]==0 ) zChng = 0;

  /* Undocumented query parameter to set JS mode */
  builtin_set_js_delivery_mode(P("jsmode"),1);

  secondaryRid = name_to_typed_rid(P("sel2"),"ci");
  selectedRid = name_to_typed_rid(P("sel1"),"ci");
  if( from_rid!=0 && to_rid!=0 ){
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
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
1939







-
+


+

-






+
+
+







  ** present or if this repository lacks a "cherrypick" table. */
  if( PB("ncp") || !db_table_exists("repository","cherrypick") ){
    showCherrypicks = 0;
  }

  /* To view the timeline, must have permission to read project data.
  */
  pd_rid = name_choice("dp","dp2",&zDPName);
  pd_rid = name_choice("dp","dp2",&zDPNameP);
  if( pd_rid ){
    p_rid = d_rid = pd_rid;
    zDPNameD = zDPNameP;
  }
  login_check_credentials();
  if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum)
   || (bisectLocal && !g.perm.Setup)
  ){
    login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
    return;
  }
  if( zBefore || zCirca ){
    if( robot_restrict("timelineX") ) return;
  }
  if( !bisectLocal ){
    etag_check(ETAG_QUERY|ETAG_COOKIE|ETAG_DATA|ETAG_CONFIG, 0);
  }
  cookie_read_parameter("y","y");
  zType = P("y");
  if( zType==0 ){
    zType = g.perm.Read ? "ci" : "all";
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
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
2032
2033
2034
2035
2036
2037
2038

2039
2040
2041
2042
2043
2044
2045
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
1994

1995
1996
1997
1998
1999
2000
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







+

-
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




-
+




-
+









+


-
-
-
+
-
-
-
-
-
-
-
-
+

-
+








-
+







    );
  }

  /* Check for tl=TAGLIST and rl=TAGLIST which are abbreviations for
  ** t=TAGLIST&ms=brlist and r=TAGLIST&ms=brlist repectively. */
  if( zBrName==0 && zTagName==0 ){
    const char *z;
    const char *zPattern = 0;
    if( (z = P("tl"))!=0 ){
      zTagName = z;
      zPattern = zTagName = z;
      zMatchStyle = "brlist";
    }
    if( (z = P("rl"))!=0 ){
      zBrName = z;
      zMatchStyle = "brlist";
    }else if( (z = P("rl"))!=0 ){
      zPattern = zBrName = z;
      if( related==0 ) related = 1;
    }else if( (z = P("ml"))!=0 ){
      zPattern = zBrName = z;
      if( related==0 ) related = 2;
    }
    if( zPattern!=0 && zMatchStyle==0 ){
      /* If there was no ms= query parameter, set the match style to
      ** "glob" if the pattern appears to contain GLOB character, or
      ** "brlist" if it does not. */
      if( strpbrk(zPattern,"*[?") ){
        zMatchStyle = "glob";
      }else{
        zMatchStyle = "brlist";
      }
    }
  }

  /* Convert r=TAG to t=TAG&rel in order to populate the UI style widgets. */
  if( zBrName && !related ){
  if( zBrName ){
    cgi_delete_query_parameter("r");
    cgi_set_query_parameter("t", zBrName);  (void)P("t");
    cgi_set_query_parameter("rel", "1");
    zTagName = zBrName;
    related = 1;
    if( related==0 ) related = 1;
    zType = "ci";
  }

  /* Ignore empty tag query strings. */
  if( zTagName && !*zTagName ){
    zTagName = 0;
  }

  /* Finish preliminary processing of tag match queries. */
  matchStyle = match_style(zMatchStyle, MS_EXACT);
  if( zTagName ){
    zType = "ci";
    /* Interpet the tag style string. */
    if( fossil_stricmp(zMatchStyle, "glob")==0 ){
      matchStyle = MS_GLOB;
    if( matchStyle==MS_EXACT ){
    }else if( fossil_stricmp(zMatchStyle, "like")==0 ){
      matchStyle = MS_LIKE;
    }else if( fossil_stricmp(zMatchStyle, "regexp")==0 ){
      matchStyle = MS_REGEXP;
    }else if( fossil_stricmp(zMatchStyle, "brlist")==0 ){
      matchStyle = MS_BRLIST;
    }else{
      /* For exact maching, inhibit links to the selected tag. */
      /* For exact matching, inhibit links to the selected tag. */
      zThisTag = zTagName;
      Th_Store("current_checkin", zTagName);
      Th_StoreUnsafe("current_checkin", zTagName);
    }

    /* Display a checkbox to enable/disable display of related check-ins. */
    if( advancedMenu ){
      style_submenu_checkbox("rel", "Related", 0, 0);
    }

    /* Construct the tag match expression. */
    zTagSql = tagMatchExpression(matchStyle, zTagName, &zMatchDesc, &zError);
    zTagSql = match_tag_sqlexpr(matchStyle, zTagName, &zMatchDesc, &zError);
  }

  if( zMark && zMark[0]==0 ){
    if( zAfter ) zMark = zAfter;
    if( zBefore ) zMark = zBefore;
    if( zCirca ) zMark = zCirca;
  }
2079
2080
2081
2082
2083
2084
2085

2086
2087
2088

2089
2090
2091
2092
2093
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
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
2088
2089
2090
2091
2092
2093
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







+



+














-
+








-
-
-
+
+
+

-
+

-
-
-
+
+
+






-
-
-
-
+
+
+
+

-
-
-
+
+
+







  if( PB("deltabg") ){
    tmFlags |= TIMELINE_DELTA;
  }
  if( PB("nc") ){
    tmFlags &= ~(TIMELINE_DELTA|TIMELINE_BRCOLOR|TIMELINE_UCOLOR);
    tmFlags |= TIMELINE_NOCOLOR;
  }
  if( showSql ) db_append_dml_to_blob(&allSql);
  if( zUses!=0 ){
    int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses);
    if( ufid ){
      if( robot_restrict("timelineX") ) return;
      zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
      db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
      compute_uses_file("usesfile", ufid, 0);
      zType = "ci";
      disableY = 1;
      if( !haveParameterN ) nEntry = 0;
    }else{
      zUses = 0;
    }
  }
  if( renameOnly ){
    db_multi_exec(
      "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);"
      "INSERT OR IGNORE INTO rnfile"
      "  SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;"
      " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;"
    );
    disableY = 1;
  }
  if( forkOnly ){
    db_multi_exec(
      "CREATE TEMP TABLE rnfork(rid INTEGER PRIMARY KEY);\n"
      "INSERT OR IGNORE INTO rnfork(rid)\n"
      "  SELECT pid FROM plink\n"
      "   WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
      "           (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
      "   GROUP BY pid"
      "   WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n"
      "         (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
      "   GROUP BY pid\n"
      "   HAVING count(*)>1;\n"
      "INSERT OR IGNORE INTO rnfork(rid)"
      "INSERT OR IGNORE INTO rnfork(rid)\n"
      "  SELECT cid FROM plink\n"
      "   WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
      "           (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
      "   GROUP BY cid"
      "   WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n"
      "         (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
      "   GROUP BY cid\n"
      "   HAVING count(*)>1;\n",
      TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
    );
    db_multi_exec(
      "INSERT OR IGNORE INTO rnfork(rid)\n"
      "  SELECT cid FROM plink\n"
      "   WHERE pid IN rnfork"
      "     AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
      "           (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
      " UNION "
      "   WHERE pid IN rnfork\n"
      "     AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n"
      "         (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
      "  UNION\n"
      "  SELECT pid FROM plink\n"
      "   WHERE cid IN rnfork"
      "     AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
      "           (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n",
      "   WHERE cid IN rnfork\n"
      "     AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n"
      "         (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n",
      TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
    );
    tmFlags |= TIMELINE_UNHIDE;
    zType = "ci";
    disableY = 1;
  }
  if( bisectLocal && cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){
2155
2156
2157
2158
2159
2160
2161
2162

2163
2164
2165
2166
2167
2168
2169
2139
2140
2141
2142
2143
2144
2145

2146
2147
2148
2149
2150
2151
2152
2153







-
+







    disableY = 1;
  }else{
    zBisect = 0;
  }

  style_header("Timeline");
  if( advancedMenu ){
    style_submenu_element("Help", "%R/help?cmd=/timeline");
    style_submenu_element("Help", "%R/help/www/timeline");
  }
  login_anonymous_available();
  timeline_temp_table();
  blob_zero(&sql);
  blob_zero(&desc);
  blob_append(&sql, "INSERT OR IGNORE INTO timeline ", -1);
  blob_append(&sql, timeline_query_for_www(), -1);
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
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
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







+
+
+
+



-
+

-
+
+
+

-
+
+
+




-
+
+


+
+
+
+
+
+
-
-
+
+
+
+
+



-
+


+


+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+





-
+

-
+

-
+

-
+


-
+





-
+

-
+


-
+


+
+
+
+
+
+
+
+
+
+
+
+



+
+
+

-
+

-
-
-
-
+
+
+
+
+











-
-
+
+
+







    /* If from= and to= are present, display all nodes on a path connecting
    ** the two */
    PathNode *p = 0;
    const char *zFrom = 0;
    const char *zTo = 0;
    Blob ins;
    int nNodeOnPath = 0;
    int commonAncs = 0;    /* Common ancestors of me_rid and you_rid. */
    int earlierRid = 0, laterRid = 0;
    int cost = bShort ? 0 : 1;
    int nSkip = 0;

    if( from_rid && to_rid ){
      if( from_to_mode==0 ){
        p = path_shortest(from_rid, to_rid, noMerge, 0, 0);
        p = path_shortest(from_rid, to_rid, 0, 0, 0, cost);
      }else if( from_to_mode==1 ){
        p = path_shortest(from_rid, to_rid, 0, 1, 0);
        p = path_shortest(from_rid, to_rid, 0, 1, 0, cost);
        earlierRid = commonAncs = from_rid;
        laterRid = to_rid;
      }else{
        p = path_shortest(to_rid, from_rid, 0, 1, 0);
        p = path_shortest(to_rid, from_rid, 0, 1, 0, cost);
        earlierRid = commonAncs = to_rid;
        laterRid = from_rid;
      }
      zFrom = P("from");
      zTo = zTo2 ? zTo2 : P("to");
    }else{
      if( path_common_ancestor(me_rid, you_rid) ){
      commonAncs = path_common_ancestor(me_rid, you_rid);
      if( commonAncs!=0 ){
        p = path_first();
      }
      if( commonAncs==you_rid ){
        zFrom = P("you");
        zTo = P("me");
        earlierRid = you_rid;
        laterRid = me_rid;
      }else{
      zFrom = P("me");
      zTo = P("you");
        zFrom = P("me");
        zTo = P("you");
        earlierRid = me_rid;
        laterRid = you_rid;
      }
    }
    blob_init(&ins, 0, 0);
    db_multi_exec(
      "CREATE TABLE IF NOT EXISTS temp.pathnode(x INTEGER PRIMARY KEY);"
      "CREATE TEMP TABLE IF NOT EXISTS pathnode(x INTEGER PRIMARY KEY);"
    );
    if( p ){
      int cnt = 4;
      blob_init(&ins, 0, 0);
      blob_append_sql(&ins, "INSERT INTO pathnode(x) VALUES(%d)", p->rid);
      if( p->u.pTo==0 ) bMin = 0;
      for(p=p->u.pTo; p; p=p->u.pTo){
        if( bMin
      p = p->u.pTo;
      while( p ){
        blob_append_sql(&ins, ",(%d)", p->rid);
        p = p->u.pTo;
         && p->u.pTo!=0
         && fossil_strcmp(path_branch(p->pFrom),path_branch(p))==0
         && fossil_strcmp(path_branch(p),path_branch(p->u.pTo))==0
        ){
          nSkip++;
        }else if( cnt==8 ){
          blob_append_sql(&ins, ",\n  (%d)", p->rid);
          cnt = 0;
        }else{
          cnt++;
          blob_append_sql(&ins, ",(%d)", p->rid);
        }
      }
    }
    path_reset();
    db_multi_exec("%s", blob_str(&ins)/*safe-for-%s*/);
    blob_reset(&ins);
    if( related || P("mionly") ){
    if( related ){
      db_multi_exec(
        "CREATE TABLE IF NOT EXISTS temp.related(x INTEGER PRIMARY KEY);"
        "CREATE TEMP TABLE IF NOT EXISTS related(x INTEGER PRIMARY KEY);"
        "INSERT OR IGNORE INTO related(x)"
        "  SELECT pid FROM plink WHERE cid IN pathnode AND NOT isprim;"
        " SELECT pid FROM plink WHERE cid IN pathnode AND NOT isprim;"
      );
      if( P("mionly")==0 ){
      if( related==1 ){
        db_multi_exec(
          "INSERT OR IGNORE INTO related(x)"
          "  SELECT cid FROM plink WHERE pid IN pathnode;"
          " SELECT cid FROM plink WHERE pid IN pathnode;"
        );
      }
      if( showCherrypicks ){
        db_multi_exec(
          "INSERT OR IGNORE INTO related(x)"
          "  SELECT parentid FROM cherrypick WHERE childid IN pathnode;"
          " SELECT parentid FROM cherrypick WHERE childid IN pathnode;"
        );
        if( P("mionly")==0 ){
        if( related==1 ){
          db_multi_exec(
            "INSERT OR IGNORE INTO related(x)"
            "  SELECT childid FROM cherrypick WHERE parentid IN pathnode;"
            " SELECT childid FROM cherrypick WHERE parentid IN pathnode;"
          );
        }
      }
      if( earlierRid && laterRid && commonAncs==earlierRid ){
        /* On a query with me=XXX, you=YYY, and rel, omit all nodes that
        ** are not ancestors of either XXX or YYY, as those nodes tend to
        ** be extraneous */
        db_multi_exec(
          "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
        );
        compute_ancestors(laterRid, 0, 0, earlierRid);
        db_multi_exec(
          "DELETE FROM related WHERE x NOT IN ok;"
        );
      }
      db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related");
    }
    add_extra_rids("pathnode",P("x"));
    add_extra_rids("pathnode",P("sel1"));
    add_extra_rids("pathnode",P("sel2"));
    blob_append_sql(&sql, " AND event.objid IN pathnode");
    if( zChng && zChng[0] ){
    if( zChng ){
      db_multi_exec(
        "DELETE FROM pathnode "
        " WHERE NOT EXISTS(SELECT 1 FROM mlink, filename"
                          " WHERE mlink.mid=x"
                          "   AND mlink.fnid=filename.fnid AND %s)",
        "DELETE FROM pathnode\n"
        " WHERE NOT EXISTS(SELECT 1 FROM mlink, filename\n"
        "                   WHERE mlink.mid=x\n"
        "                     AND mlink.fnid=filename.fnid\n"
        "                     AND %s)",
        glob_expr("filename.name", zChng)
      );
    }
    tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
    db_multi_exec("%s", blob_sql_text(&sql));
    if( advancedMenu ){
      style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
    }
    nNodeOnPath = db_int(0, "SELECT count(*) FROM temp.pathnode");
    if( nNodeOnPath==1 && from_to_mode>0 ){
      blob_appendf(&desc,"Check-in ");
    }else if( from_to_mode>0 ){
      blob_appendf(&desc, "%d check-ins on the shorted path from ",nNodeOnPath);
    }else if( bMin ){
      blob_appendf(&desc, "%d of %d check-ins along the path from ",
                   nNodeOnPath, nNodeOnPath+nSkip);
    }else{
      blob_appendf(&desc, "%d check-ins going from ", nNodeOnPath);
    }
    if( from_rid==selectedRid ){
      blob_appendf(&desc, "<span class='timelineSelected'>");
    }
    blob_appendf(&desc, "%z%h</a>", href("%R/info/%h", zFrom), zFrom);
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
2444
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
2444
2445
2446
2447

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
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
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
2519
2520


2521
2522
2523
2524
2525


2526
2527


2528

2529
2530
2531
2532
2533
2534
2535







-
-
-
+
+
+





+
+
+


-
-
+
+
+
+
+
+
+
-


-
+

-
-
+
-
-
+
+



-


+
+
+
+
+
+
+

-
+
-






-
-
-
+
+
+
+
+
+
+
+

-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+

-
+

-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+



+
+
+
+
+
+
+

-
+
-





+
+
+


+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




-
-
+
+


-
-
+
+
+

-
-
+
+
+






-
-
+
+


-
-
+
+
+


-
-
+
+
-
-
+
-







          blob_appendf(&desc, " and %d related check-in%s", nRelated,
                       nRelated>1 ? "s" : "");
        }
      }
    }
    addFileGlobDescription(zChng, &desc);
  }else if( (p_rid || d_rid) && g.perm.Read && zTagSql==0 ){
    /* If p= or d= is present, ignore all other parameters other than n= */
    char *zUuid;
    const char *zCiName;
    /* If either p= or d= or both are present, ignore all other parameters
    ** other than n=, ft=, and bt= */
    const char *zBaseName = 0;
    int np = 0, nd;
    const char *zBackTo = 0;
    const char *zFwdTo = 0;
    int ridBackTo = 0;
    int ridFwdTo = 0;
    int bBackAdded = 0;       /* True if the zBackTo node was added */
    int bFwdAdded = 0;        /* True if the zBackTo node was added */
    int bSeparateDandP = 0;   /* p_rid & d_rid both exist and are distinct */

    tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
    if( p_rid && d_rid ){
      if( p_rid!=d_rid ) p_rid = d_rid;
    if( p_rid && d_rid && p_rid!=d_rid ){
      bSeparateDandP = 1;
      db_multi_exec(
        "CREATE TEMP TABLE IF NOT EXISTS ok_d(rid INTEGER PRIMARY KEY)"
      );
    }else{
      zBaseName = p_rid ? zDPNameP : zDPNameD;
      if( !haveParameterN ) nEntry = 10;
    }
    db_multi_exec(
       "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
      "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
    );
    zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d",
                         p_rid ? p_rid : d_rid);
    add_extra_rids("ok", P("x"));
    zCiName = zDPName;
    if( zCiName==0 ) zCiName = zUuid;
    add_extra_rids("ok", P("sel1"));
    add_extra_rids("ok", P("sel2"));
    blob_append_sql(&sql, " AND event.objid IN ok");
    nd = 0;
    if( d_rid ){
      Stmt s;
      double rStopTime = 9e99;
      zFwdTo = P("ft");
      if( zFwdTo && bSeparateDandP ){
        if( zError==0 ){
          zError = "Cannot use the ft= query parameter when both p= and d= "
                   "are used and have distinct values.";
        }
        zFwdTo = 0;
      }
      if( zFwdTo ){
        double rStartDate = db_double(0.0,
        double rStartDate = mtime_of_rid(d_rid, 0.0);
           "SELECT mtime FROM event WHERE objid=%d", d_rid);
        ridFwdTo = first_checkin_with_tag_after_date(zFwdTo, rStartDate);
        if( ridFwdTo==0 ){
          ridFwdTo = name_to_typed_rid(zBackTo,"ci");
        }
        if( ridFwdTo ){
          if( !haveParameterN ) nEntry = 0;
          rStopTime = db_double(9e99,
            "SELECT mtime FROM event WHERE objid=%d", ridFwdTo);
        }
          rStopTime = mtime_of_rid(ridFwdTo, 9e99);
        }
      }else if( bSeparateDandP ){
        rStopTime = mtime_of_rid(p_rid, 9e99);
        nEntry = 0;
      }
      if( rStopTime<9e99 ){
        rStopTime += 5.8e-6;  /* Round up by 1/2 second */
      }
      db_prepare(&s,
        "WITH RECURSIVE"
      db_multi_exec(
        "WITH RECURSIVE dx(rid,mtime) AS (\n"
        "  dx(rid,mtime) AS ("
        "     SELECT %d, 0"
        "     UNION"
        "     SELECT plink.cid, plink.mtime FROM dx, plink"
        "      WHERE plink.pid=dx.rid"
        "        AND (:stop>=8e99 OR plink.mtime<=:stop)"
        "      ORDER BY 2"
        "  )"
        "  SELECT %d, 0\n"
        "  UNION\n"
        "  SELECT plink.cid, plink.mtime FROM dx, plink\n"
        "   WHERE plink.pid=dx.rid\n"
        "     AND plink.mtime<=%.*g\n"
        "   ORDER BY 2\n"
        ")\n"
        "INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d",
        d_rid, nEntry<=0 ? -1 : nEntry+1
        d_rid, rStopTime<8e99 ? 17 : 2, rStopTime, nEntry<=0 ? -1 : nEntry+1
      );
      db_bind_double(&s, ":stop", rStopTime);
      db_step(&s);
      db_finalize(&s);
      if( ridFwdTo && !db_exists("SELECT 1 FROM ok WHERE rid=%d",ridFwdTo) ){
        db_multi_exec("INSERT OR IGNORE INTO ok VALUES(%d)", ridFwdTo);
        bFwdAdded = 1;
      }
      if( bSeparateDandP ){
        db_multi_exec(
           "INSERT INTO ok_d SELECT rid FROM ok;"
           "DELETE FROM ok;"
        );
      /* compute_descendants(d_rid, nEntry==0 ? 0 : nEntry+1); */
      nd = db_int(0, "SELECT count(*)-1 FROM ok");
      if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql));
      if( nd>0 || p_rid==0 ){
        blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s");
      }
      if( useDividers && !selectedRid ) selectedRid = d_rid;
      db_multi_exec("DELETE FROM ok");
      }else{
        removeFileGlobFromOk(zChng);
        nd = db_int(0, "SELECT count(*)-1 FROM ok");
        if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql));
        if( nd>0 || p_rid==0 ){
          blob_appendf(&desc, "%d descendant%s",
                       nd>=0 ? nd : 0,(1==nd)?"":"s");
        }
        if( useDividers && !selectedRid ) selectedRid = d_rid;
        db_multi_exec("DELETE FROM ok");
      }
    }
    if( p_rid ){
      zBackTo = P("bt");
      if( zBackTo && bSeparateDandP ){
        if( zError==0 ){
          zError = "Cannot use the bt= query parameter when both p= and d= "
                   "are used and have distinct values.";
        }
        zBackTo = 0;
      }
      if( zBackTo ){
        double rDateLimit = db_double(0.0,
        double rDateLimit = mtime_of_rid(p_rid, 0.0);
           "SELECT mtime FROM event WHERE objid=%d", p_rid);
        ridBackTo = last_checkin_with_tag_before_date(zBackTo, rDateLimit);
        if( ridBackTo==0 ){
          ridBackTo = name_to_typed_rid(zBackTo,"ci");
        }
        if( ridBackTo && !haveParameterN ) nEntry = 0;
      }else if( bSeparateDandP ){
        ridBackTo = d_rid;
        nEntry = 0;
      }
      compute_ancestors(p_rid, nEntry==0 ? 0 : nEntry+1, 0, ridBackTo);
      if( ridBackTo && !db_exists("SELECT 1 FROM ok WHERE rid=%d",ridBackTo) ){
        db_multi_exec("INSERT OR IGNORE INTO ok VALUES(%d)", ridBackTo);
        bBackAdded = 1;
      }
      if( bSeparateDandP ){
        db_multi_exec("DELETE FROM ok WHERE rid NOT IN ok_d;");
        removeFileGlobFromOk(zChng);
        db_multi_exec("%s", blob_sql_text(&sql));
      }else{
        removeFileGlobFromOk(zChng);
      np = db_int(0, "SELECT count(*)-1 FROM ok");
      if( np>0 || nd==0 ){
        if( nd>0 ) blob_appendf(&desc, " and ");
        blob_appendf(&desc, "%d ancestor%s", np, (1==np)?"":"s");
        db_multi_exec("%s", blob_sql_text(&sql));
      }
      if( useDividers && !selectedRid ) selectedRid = p_rid;
    }

    blob_appendf(&desc, " of %z%h</a>",
                   href("%R/info?name=%h", zCiName), zCiName);
        np = db_int(0, "SELECT count(*)-1 FROM ok");
        if( np>0 || nd==0 ){
          if( nd>0 ) blob_appendf(&desc, " and ");
          blob_appendf(&desc, "%d ancestor%s",
                       np>=0 ? np : 0, (1==np)?"":"s");
          db_multi_exec("%s", blob_sql_text(&sql));
        }
        if( useDividers && !selectedRid ) selectedRid = p_rid;
      }
    }

    if( bSeparateDandP ){
      int n = db_int(0, "SELECT count(*) FROM ok");
      blob_reset(&desc);
      blob_appendf(&desc,
          "%d check-ins that are derived from %z%h</a>"
          " and contribute to %z%h</a>",
          n,
          href("%R/info?name=%h",zDPNameD),zDPNameD,
          href("%R/info?name=%h",zDPNameP),zDPNameP
      );
      ridBackTo = 0;
      ridFwdTo = 0;
    }else{
      blob_appendf(&desc, " of %z%h</a>",
                   href("%R/info?name=%h", zBaseName), zBaseName);
    }
    if( ridBackTo ){
      if( np==0 ){
        blob_reset(&desc);
        blob_appendf(&desc,
                    "Check-in %z%h</a> only (%z%h</a> is not an ancestor)",
                     href("%R/info?name=%h",zCiName), zCiName,
                    "Check-in %z%h</a> only (%z%h</a> does not precede it)",
                     href("%R/info?name=%h",zBaseName), zBaseName,
                     href("%R/info?name=%h",zBackTo), zBackTo);
      }else{
        blob_appendf(&desc, " back to %z%h</a>",
                     href("%R/info?name=%h",zBackTo), zBackTo);
        blob_appendf(&desc, " back to %z%h</a>%s",
                     href("%R/info?name=%h",zBackTo), zBackTo,
                     bBackAdded ? " (not a direct ancestor)" : "");
        if( ridFwdTo && zFwdTo ){
          blob_appendf(&desc, " and up to %z%h</a>",
                     href("%R/info?name=%h",zFwdTo), zFwdTo);
          blob_appendf(&desc, " and up to %z%h</a>%s",
                     href("%R/info?name=%h",zFwdTo), zFwdTo,
                     bFwdAdded ? " (not a direct descendant)" : "");
        }
      }
    }else if( ridFwdTo ){
      if( nd==0 ){
        blob_reset(&desc);
        blob_appendf(&desc,
                    "Check-in %z%h</a> only (%z%h</a> is not an descendant)",
                     href("%R/info?name=%h",zCiName), zCiName,
                    "Check-in %z%h</a> only (%z%h</a> does not follow it)",
                     href("%R/info?name=%h",zBaseName), zBaseName,
                     href("%R/info?name=%h",zFwdTo), zFwdTo);
      }else{
        blob_appendf(&desc, " up to %z%h</a>",
                     href("%R/info?name=%h",zFwdTo), zFwdTo);
        blob_appendf(&desc, " up to %z%h</a>%s",
                     href("%R/info?name=%h",zFwdTo), zFwdTo,
                     bFwdAdded ? " (not a direct descendant)":"");
      }
    }
    if( d_rid ){
      if( p_rid ){
    if( zChng ){
      if( strstr(blob_str(&desc)," that ") ) blob_appendf(&desc, " and");
        /* If both p= and d= are set, we don't have the uuid of d yet. */
        zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", d_rid);
      blob_appendf(&desc, " that make changes to files matching \"%h\"", zChng);
      }
    }
    if( advancedMenu ){
      style_submenu_checkbox("v", "Files", (zType[0]!='a' && zType[0]!='c'),0);
    }
    style_submenu_entry("n","Max:",4,0);
    timeline_y_submenu(1);
  }else if( f_rid && g.perm.Read ){
2480
2481
2482
2483
2484
2485
2486
2487

2488
2489
2490

2491
2492
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
2519
2520



2521


2522
2523
2524
2525

2526
2527

2528
2529
2530
2531



2532


2533
2534
2535
2536
2537
2538






2539


2540
2541
2542


2543

2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564

2565
2566
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
2595

2596
2597
2598
2599



2600


2601
2602
2603
2604

2605

2606
2607
2608
2609



2610


2611
2612
2613
2614
2615
2616






2617



2618
2619
2620
2621
2622
2623
2624
2571
2572
2573
2574
2575
2576
2577

2578
2579
2580

2581
2582
2583

2584
2585
2586
2587
2588
2589
2590
2591

2592
2593
2594

2595
2596
2597
2598
2599
2600

2601


2602

2603
2604
2605

2606

2607
2608


2609
2610
2611
2612
2613
2614
2615
2616


2617


2618
2619
2620


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
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733

2734


2735
2736
2737


2738
2739
2740
2741
2742
2743
2744
2745


2746
2747




2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767

2768
2769
2770
2771
2772

2773
2774
2775


2776
2777
2778
2779
2780
2781
2782
2783


2784

2785
2786
2787


2788
2789
2790
2791
2792
2793
2794
2795




2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812







-
+


-
+


-
+







-
+


-
+



+
+
-
+
-
-
+
-



-
+
-


-
-
+
+
+

+
+


-
-
+
-
-
+


-
-
+
+
+

+
+


-
-
-
-
+
+
+
+
+
+

+
+



+
+
-
+




















-
+


-
-
+
+
+

+
+


+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+

-
-
+
+
+

+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
+
+

+
+
+
+
+
+



+
+
+
+
-
+




-
+


-
-
+
+
+

+
+


-
-
+
-
+


-
-
+
+
+

+
+


-
-
-
-
+
+
+
+
+
+

+
+
+







    blob_zero(&cond);
    tmFlags |= TIMELINE_FILLGAPS;
    if( zChng && *zChng ){
      addFileGlobExclusion(zChng, &cond);
      tmFlags |= TIMELINE_XMERGE;
    }
    if( zUses ){
      blob_append_sql(&cond, " AND event.objid IN usesfile ");
      blob_append_sql(&cond, " AND event.objid IN usesfile\n");
    }
    if( renameOnly ){
      blob_append_sql(&cond, " AND event.objid IN rnfile ");
      blob_append_sql(&cond, " AND event.objid IN rnfile\n");
    }
    if( forkOnly ){
      blob_append_sql(&cond, " AND event.objid IN rnfork ");
      blob_append_sql(&cond, " AND event.objid IN rnfork\n");
    }
    if( cpOnly && showCherrypicks ){
      db_multi_exec(
        "CREATE TEMP TABLE IF NOT EXISTS cpnodes(rid INTEGER PRIMARY KEY);"
        "INSERT OR IGNORE INTO cpnodes SELECT childid FROM cherrypick;"
        "INSERT OR IGNORE INTO cpnodes SELECT parentid FROM cherrypick;"
      );
      blob_append_sql(&cond, " AND event.objid IN cpnodes ");
      blob_append_sql(&cond, " AND event.objid IN cpnodes\n");
    }
    if( bisectLocal || zBisect!=0 ){
      blob_append_sql(&cond, " AND event.objid IN (SELECT rid FROM bilog) ");
      blob_append_sql(&cond, " AND event.objid IN (SELECT rid FROM bilog)\n");
    }
    if( zYearMonth ){
      char *zNext;
      int bZulu = 0;
      const char *zTZMod;
      zYearMonth = timeline_expand_datetime(zYearMonth);
      zYearMonth = timeline_expand_datetime(zYearMonth, &bZulu);
      if( strlen(zYearMonth)>7 ){
        zYearMonth = mprintf("%.7s", zYearMonth);
      zYearMonth = mprintf("%.7s", zYearMonth);
      }
      if( db_int(0,"SELECT julianday('%q-01') IS NULL", zYearMonth) ){
        zYearMonth = db_text(0, "SELECT strftime('%%Y-%%m','now');");
      }
      zNext = db_text(0, "SELECT strftime('%%Y-%%m','%q-01','+1 month');",
      zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00";
                      zYearMonth);
      if( db_int(0,
          "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
          " WHERE blob.rid=event.objid AND mtime>=julianday('%q-01')%s)",
          zNext, blob_sql_text(&cond))
          " WHERE blob.rid=event.objid"
          "   AND mtime>=julianday('%q-01',%Q)%s)",
          zYearMonth, zTZMod, blob_sql_text(&cond))
      ){
        zNext = db_text(0, "SELECT strftime('%%Y%%m%q','%q-01','+1 month');",
                        &"Z"[!bZulu], zYearMonth);
        zNewerButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0));
        zNewerButtonLabel = "Following month";
      }
      fossil_free(zNext);
        fossil_free(zNext);
      zNext = db_text(0, "SELECT strftime('%%Y-%%m','%q-01','-1 month');",
                      zYearMonth);
      }
      if( db_int(0,
          "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
          " WHERE blob.rid=event.objid AND mtime<julianday('%q-01')%s)",
          zYearMonth, blob_sql_text(&cond))
          " WHERE blob.rid=event.objid"
          "   AND mtime<julianday('%q-01',%Q)%s)",
          zYearMonth, zTZMod, blob_sql_text(&cond))
      ){
        zNext = db_text(0, "SELECT strftime('%%Y%%m%q','%q-01','-1 month');",
                        &"Z"[!bZulu], zYearMonth);
        zOlderButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0));
        zOlderButtonLabel = "Previous month";
      }
      fossil_free(zNext);
      blob_append_sql(&cond, " AND %Q=strftime('%%Y-%%m',event.mtime) ",
                      zYearMonth);
        fossil_free(zNext);
      }
      blob_append_sql(&cond,
         " AND event.mtime>=julianday('%q-01',%Q)"
         " AND event.mtime<julianday('%q-01',%Q,'+1 month')\n",
         zYearMonth, zTZMod, zYearMonth, zTZMod);
      nEntry = -1;
      /* Adjust the zYearMonth for the title */
      zYearMonth = mprintf("%z-01%s", zYearMonth, &"Z"[!bZulu]);
    }
    else if( zYearWeek ){
      char *z, *zNext;
      int bZulu = 0;
      const char *zTZMod;
      zYearWeek = timeline_expand_datetime(zYearWeek);
      zYearWeek = timeline_expand_datetime(zYearWeek, &bZulu);
      z = db_text(0, "SELECT strftime('%%Y-%%W',%Q)", zYearWeek);
      if( z && z[0] ){
        zYearWeekStart = db_text(0, "SELECT date(%Q,'-6 days','weekday 1')",
                                 zYearWeek);
        zYearWeek = z;
      }else{
        if( strlen(zYearWeek)==7 ){
          zYearWeekStart = db_text(0,
             "SELECT date('%.4q-01-01','%+d days','weekday 1')",
             zYearWeek, atoi(zYearWeek+5)*7-6);
        }else{
          zYearWeekStart = 0;
        }
        if( zYearWeekStart==0 || zYearWeekStart[0]==0 ){
          zYearWeekStart = db_text(0,
             "SELECT date('now','-6 days','weekday 1');");
          zYearWeek = db_text(0,
             "SELECT strftime('%%Y-%%W','now','-6 days','weekday 1')");
        }
      }
      zNext = db_text(0, "SELECT date(%Q,'+7 day');", zYearWeekStart);
      zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00";
      if( db_int(0,
          "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
          " WHERE blob.rid=event.objid AND mtime>=julianday(%Q)%s)",
          zNext, blob_sql_text(&cond))
          " WHERE blob.rid=event.objid"
          "   AND mtime>=julianday(%Q,%Q)%s)",
          zYearWeekStart, zTZMod, blob_sql_text(&cond))
      ){
        zNext = db_text(0, "SELECT strftime('%%Y%%W%q',%Q,'+7 day');",
                        &"Z"[!bZulu], zYearWeekStart);
        zNewerButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0));
        zNewerButtonLabel = "Following week";
        fossil_free(zNext);
      }
      if( db_int(0,
          "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
          " WHERE blob.rid=event.objid"
          "   AND mtime<julianday(%Q,%Q)%s)",
          zYearWeekStart, zTZMod, blob_sql_text(&cond))
      ){
        zNext = db_text(0, "SELECT strftime('%%Y%%W%q',%Q,'-7 days');",
                        &"Z"[!bZulu], zYearWeekStart);
        zOlderButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0));
        zOlderButtonLabel = "Previous week";
        fossil_free(zNext);
      }
      blob_append_sql(&cond,
        " AND event.mtime>=julianday(%Q,%Q)"
        " AND event.mtime<julianday(%Q,%Q,'+7 days')\n",
        zYearWeekStart, zTZMod, zYearWeekStart, zTZMod);
      nEntry = -1;
      if( fossil_ui_localtime() && bZulu ){
        zYearWeekStart = mprintf("%zZ", zYearWeekStart);
      }
    }
    else if( zDay && timeline_is_datespan(zDay) ){
      char *zNext;
      char *zStart, *zEnd;
      int nDay;
      int bZulu = 0;
      const char *zTZMod;
      zEnd = db_text(0, "SELECT date(%Q)",
                     timeline_expand_datetime(zDay+9, &bZulu));
      zStart = db_text(0, "SELECT date('%.4q-%.2q-%.2q')",
                        zDay, zDay+4, zDay+6);
      nDay = db_int(0, "SELECT julianday(%Q)-julianday(%Q)", zEnd, zStart);
      if( nDay==0 ){
        zDay = &zDay[9];
        goto single_ymd;
      }
      if( nDay<0 ){
        char *zTemp = zEnd;
        zEnd = zStart;
        zStart = zTemp;
        nDay = 1 - nDay;
      }else{
        nDay += 1;
      }
      zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00";
      if( nDay>0 && db_int(0,
          "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
          " WHERE blob.rid=event.objid"
          "   AND mtime>=julianday(%Q,'1 day',%Q)%s)",
          zEnd, zTZMod, blob_sql_text(&cond))
      ){
        zNext = db_text(0,
           "SELECT strftime('%%Y%%m%%d-',%Q,'%d days')||"
                  "strftime('%%Y%%m%%d%q',%Q,'%d day');",
                  zStart, nDay, &"Z"[!bZulu], zEnd, nDay);
        zNewerButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
        zNewerButtonLabel = mprintf("Following %d days", nDay);
      fossil_free(zNext);
        fossil_free(zNext);
      zNext = db_text(0, "SELECT date(%Q,'-7 days');", zYearWeekStart);
      if( db_int(0,
      }
      if( nDay>1 && db_int(0,
          "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
          " WHERE blob.rid=event.objid AND mtime<julianday(%Q)%s)",
          zYearWeekStart, blob_sql_text(&cond))
          " WHERE blob.rid=event.objid"
          "   AND mtime<julianday(%Q,'-1 day',%Q)%s)",
          zStart, zTZMod, blob_sql_text(&cond))
      ){
        zNext = db_text(0,
           "SELECT strftime('%%Y%%m%%d-',%Q,'%d days')||"
                  "strftime('%%Y%%m%%d%q',%Q,'%d day');",
                  zStart, -nDay, &"Z"[!bZulu], zEnd, -nDay);
        zOlderButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0));
        zOlderButtonLabel = "Previous week";
        zOlderButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
        zOlderButtonLabel = mprintf("Previous %d days", nDay);
      }
      fossil_free(zNext);
      blob_append_sql(&cond, " AND %Q=strftime('%%Y-%%W',event.mtime) ",
                   zYearWeek);
        fossil_free(zNext);
      }
      blob_append_sql(&cond,
        " AND event.mtime>=julianday(%Q,%Q)"
        " AND event.mtime<julianday(%Q,%Q,'+1 day')\n",
        zStart, zTZMod, zEnd, zTZMod);
      nEntry = -1;

      if( fossil_ui_localtime() && bZulu ){
        zDay = mprintf("%d days between %zZ and %zZ", nDay, zStart, zEnd);
      }else{
        zDay = mprintf("%d days between %z and %z", nDay, zStart, zEnd);
      }
    }
    else if( zDay ){
      char *zNext;
      int bZulu = 0;
      const char *zTZMod;
    single_ymd:
      bZulu = 0;
      zDay = timeline_expand_datetime(zDay);
      zDay = timeline_expand_datetime(zDay, &bZulu);
      zDay = db_text(0, "SELECT date(%Q)", zDay);
      if( zDay==0 || zDay[0]==0 ){
        zDay = db_text(0, "SELECT date('now')");
      }
      zNext = db_text(0, "SELECT date(%Q,'+1 day');", zDay);
      zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00";
      if( db_int(0,
          "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
          " WHERE blob.rid=event.objid AND mtime>=julianday(%Q)%s)",
          zNext, blob_sql_text(&cond))
          " WHERE blob.rid=event.objid"
          "   AND mtime>=julianday(%Q,'+1 day',%Q)%s)",
          zDay, zTZMod, blob_sql_text(&cond))
      ){
        zNext = db_text(0,"SELECT strftime('%%Y%%m%%d%q',%Q,'+1 day');",
                        &"Z"[!bZulu], zDay);
        zNewerButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
        zNewerButtonLabel = "Following day";
      }
      fossil_free(zNext);
        fossil_free(zNext);
      zNext = db_text(0, "SELECT date(%Q,'-1 day');", zDay);
      }
      if( db_int(0,
          "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
          " WHERE blob.rid=event.objid AND mtime<julianday(%Q)%s)",
          zDay, blob_sql_text(&cond))
          " WHERE blob.rid=event.objid"
          "   AND mtime<julianday(%Q,'-1 day',%Q)%s)",
          zDay, zTZMod, blob_sql_text(&cond))
      ){
        zNext = db_text(0,"SELECT strftime('%%Y%%m%%d%q',%Q,'-1 day');",
                        &"Z"[!bZulu], zDay);
        zOlderButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
        zOlderButtonLabel = "Previous day";
      }
      fossil_free(zNext);
      blob_append_sql(&cond, " AND %Q=date(event.mtime) ",
                   zDay);
        fossil_free(zNext);
      }
      blob_append_sql(&cond,
        " AND event.mtime>=julianday(%Q,%Q)"
        " AND event.mtime<julianday(%Q,%Q,'+1 day')\n",
        zDay, zTZMod, zDay, zTZMod);
      nEntry = -1;
      if( fossil_ui_localtime() && bZulu ){
        zDay = mprintf("%zZ", zDay); /* Add Z suffix to day for the title */
      }
    }
    else if( zNDays ){
      nDays = atoi(zNDays);
      if( nDays<1 ) nDays = 1;
      blob_append_sql(&cond, " AND event.mtime>=julianday('now','-%d days') ",
                      nDays);
      nEntry = -1;
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669




2670
2671
2672
2673
2674
2675
2676
2677
2678
2679

2680
2681

2682
2683
2684
2685
2686
2687
2688
2689
2690

2691
2692
2693
2694
2695

2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712



2713
2714

2715
2716
2717
2718



2719
2720
2721
2722
2723
2724



2725
2726
2727
2728
2729
2730
2731
2732



2733
2734
2735
2736
2737
2738
2739





2740
2741
2742
2743
2744
2745
2746
2848
2849
2850
2851
2852
2853
2854



2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866


2867


2868









2869





2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884



2885
2886
2887
2888

2889
2890



2891
2892
2893
2894
2895
2896



2897
2898
2899
2900
2901
2902
2903
2904



2905
2906
2907
2908
2909
2910
2911



2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923







-
-
-
+
+
+
+








-
-
+
-
-
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
+














-
-
-
+
+
+

-
+

-
-
-
+
+
+



-
-
-
+
+
+





-
-
-
+
+
+




-
-
-
+
+
+
+
+







      blob_append_sql(&cond, " AND %Q=strftime('%%Y',event.mtime) ",
                      zYear);
      nEntry = -1;
    }
    if( zTagSql ){
      db_multi_exec(
        "CREATE TEMP TABLE selected_nodes(rid INTEGER PRIMARY KEY);"
        "INSERT OR IGNORE INTO selected_nodes"
        " SELECT tagxref.rid FROM tagxref NATURAL JOIN tag"
        " WHERE %s AND tagtype>0", zTagSql/*safe-for-%s*/
        "INSERT OR IGNORE INTO selected_nodes\n"
        "  SELECT tagxref.rid FROM tagxref NATURAL JOIN tag\n"
        "   WHERE tagtype>0\n"
        "     AND %s", zTagSql/*safe-for-%s*/
      );
      if( zMark ){
        /* If the t=release option is used with m=UUID, then also
        ** include the UUID check-in in the display list */
        int ridMark = name_to_rid(zMark);
        db_multi_exec(
          "INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridMark);
      }
      if( P("x")!=0 ){
        char *zX = fossil_strdup(P("x"));
      add_extra_rids("selected_nodes",P("x"));
        int ii;
        int ridX;
      add_extra_rids("selected_nodes",P("sel1"));
        while( zX[0] ){
          char c;
          if( zX[0]==',' || zX[0]==' ' ){ zX++; continue; }
          for(ii=1; zX[ii] && zX[ii]!=',' && zX[ii]!=' '; ii++){}
          c = zX[ii];
          zX[ii] = 0;
          ridX = name_to_rid(zX);
          db_multi_exec(
            "INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridX);
      add_extra_rids("selected_nodes",P("sel2"));
          zX[ii] = c;
          zX += ii;
        }
      }
      if( !related ){
      if( related==0 ){
        blob_append_sql(&cond, " AND blob.rid IN selected_nodes");
      }else{
        db_multi_exec(
          "CREATE TEMP TABLE related_nodes(rid INTEGER PRIMARY KEY);"
          "INSERT INTO related_nodes SELECT rid FROM selected_nodes;"
        );
        blob_append_sql(&cond, " AND blob.rid IN related_nodes");
        /* The next two blob_appendf() calls add SQL that causes check-ins that
        ** are not part of the branch which are parents or children of the
        ** branch to be included in the report.  These related check-ins are
        ** useful in helping to visualize what has happened on a quiescent
        ** branch that is infrequently merged with a much more activate branch.
        */
        db_multi_exec(
          "INSERT OR IGNORE INTO related_nodes"
          " SELECT pid FROM selected_nodes CROSS JOIN plink"
          " WHERE selected_nodes.rid=plink.cid;"
          "INSERT OR IGNORE INTO related_nodes\n"
          "  SELECT pid FROM selected_nodes CROSS JOIN plink\n"
          "  WHERE selected_nodes.rid=plink.cid;"
        );
        if( P("mionly")==0 ){
        if( related==1 ){
          db_multi_exec(
            "INSERT OR IGNORE INTO related_nodes"
            " SELECT cid FROM selected_nodes CROSS JOIN plink"
            " WHERE selected_nodes.rid=plink.pid;"
            "INSERT OR IGNORE INTO related_nodes\n"
            "  SELECT cid FROM selected_nodes CROSS JOIN plink\n"
            "  WHERE selected_nodes.rid=plink.pid;"
          );
          if( showCherrypicks ){
            db_multi_exec(
              "INSERT OR IGNORE INTO related_nodes"
              " SELECT childid FROM selected_nodes CROSS JOIN cherrypick"
              " WHERE selected_nodes.rid=cherrypick.parentid;"
              "INSERT OR IGNORE INTO related_nodes\n"
              "  SELECT childid FROM selected_nodes CROSS JOIN cherrypick\n"
              "  WHERE selected_nodes.rid=cherrypick.parentid;"
            );
          }
        }
        if( showCherrypicks ){
          db_multi_exec(
            "INSERT OR IGNORE INTO related_nodes"
            " SELECT parentid FROM selected_nodes CROSS JOIN cherrypick"
            " WHERE selected_nodes.rid=cherrypick.childid;"
            "INSERT OR IGNORE INTO related_nodes\n"
            "  SELECT parentid FROM selected_nodes CROSS JOIN cherrypick\n"
            "  WHERE selected_nodes.rid=cherrypick.childid;"
          );
        }
        if( (tmFlags & TIMELINE_UNHIDE)==0 ){
          db_multi_exec(
            "DELETE FROM related_nodes WHERE rid IN "
            " (SELECT related_nodes.rid FROM related_nodes, tagxref"
            " WHERE tagid=%d AND tagtype>0 AND tagxref.rid=related_nodes.rid)",
            "DELETE FROM related_nodes\n"
            " WHERE rid IN (SELECT related_nodes.rid\n"
            "                 FROM related_nodes, tagxref\n"
            "                WHERE tagid=%d AND tagtype>0\n"
            "                  AND tagxref.rid=related_nodes.rid)",
            TAG_HIDDEN
          );
        }
      }
    }
    if( (zType[0]=='w' && !g.perm.RdWiki)
     || (zType[0]=='t' && !g.perm.RdTkt)
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828



2829
2830
2831
2832
2833

2834
2835
2836
2837
2838

2839
2840
2841
2842
2843
2844
2845

2846
2847
2848
2849
2850
2851
2852
2853

2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867

2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880

2881
2882
2883
2884
2885
2886
2887
2996
2997
2998
2999
3000
3001
3002



3003
3004
3005
3006
3007
3008
3009

3010
3011
3012
3013
3014

3015
3016
3017
3018
3019
3020
3021

3022
3023
3024
3025
3026
3027
3028
3029

3030
3031
3032
3033



3034
3035
3036
3037
3038
3039
3040

3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053

3054
3055
3056
3057
3058
3059
3060
3061







-
-
-
+
+
+




-
+




-
+






-
+







-
+



-
-
-







-
+












-
+







          zSearch, zSearch, zSearch);
      }else{
        blob_append_sql(&cond,
          " AND (event.comment LIKE '%%%q%%' OR event.brief LIKE '%%%q%%')",
          zSearch, zSearch);
      }
    }
    rBefore = symbolic_name_to_mtime(zBefore, &zBefore);
    rAfter = symbolic_name_to_mtime(zAfter, &zAfter);
    rCirca = symbolic_name_to_mtime(zCirca, &zCirca);
    rBefore = symbolic_name_to_mtime(zBefore, &zBefore, 1);
    rAfter = symbolic_name_to_mtime(zAfter, &zAfter, 0);
    rCirca = symbolic_name_to_mtime(zCirca, &zCirca, 0);
    blob_append_sql(&sql, "%s", blob_sql_text(&cond));
    if( rAfter>0.0 ){
      if( rBefore>0.0 ){
        blob_append_sql(&sql,
           " AND event.mtime>=%.17g AND event.mtime<=%.17g"
           " AND event.mtime>=%.17g AND event.mtime<=%.17g\n"
           " ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND);
        nEntry = -1;
      }else{
        blob_append_sql(&sql,
           " AND event.mtime>=%.17g  ORDER BY event.mtime ASC",
           " AND event.mtime>=%.17g\n ORDER BY event.mtime ASC",
           rAfter-ONE_SECOND);
      }
      zCirca = 0;
      url_add_parameter(&url, "c", 0);
    }else if( rBefore>0.0 ){
      blob_append_sql(&sql,
         " AND event.mtime<=%.17g ORDER BY event.mtime DESC",
         " AND event.mtime<=%.17g\n ORDER BY event.mtime DESC",
         rBefore+ONE_SECOND);
      zCirca = 0;
      url_add_parameter(&url, "c", 0);
    }else if( rCirca>0.0 ){
      Blob sql2;
      blob_init(&sql2, blob_sql_text(&sql), -1);
      blob_append_sql(&sql2,
          " AND event.mtime>=%f ORDER BY event.mtime ASC", rCirca);
          " AND event.mtime>=%f\n ORDER BY event.mtime ASC", rCirca);
      if( nEntry>0 ){
        blob_append_sql(&sql2," LIMIT %d", (nEntry+1)/2);
      }
      if( PB("showsql") ){
         @ <pre>%h(blob_sql_text(&sql2))</pre>
      }
      db_multi_exec("%s", blob_sql_text(&sql2));
      if( nEntry>0 ){
        nEntry -= db_int(0,"select count(*) from timeline");
        if( nEntry<=0 ) nEntry = 1;
      }
      blob_reset(&sql2);
      blob_append_sql(&sql,
          " AND event.mtime<=%f ORDER BY event.mtime DESC",
          " AND event.mtime<=%f\n ORDER BY event.mtime DESC",
          rCirca
      );
      if( zMark==0 ) zMark = zCirca;
    }else{
      blob_append_sql(&sql, " ORDER BY event.mtime DESC");
    }
    if( nEntry>0 ) blob_append_sql(&sql, " LIMIT %d", nEntry);
    db_multi_exec("%s", blob_sql_text(&sql));

    n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
    zPlural = n==1 ? "" : "s";
    if( zYearMonth ){
      blob_appendf(&desc, "%d %s%s for the month beginning %h-01",
      blob_appendf(&desc, "%d %s%s for the month beginning %h",
                   n, zEType, zPlural, zYearMonth);
    }else if( zYearWeek ){
      blob_appendf(&desc, "%d %s%s for week %h beginning on %h",
                   n, zEType, zPlural, zYearWeek, zYearWeekStart);
    }else if( zDay ){
      blob_appendf(&desc, "%d %s%s occurring on %h", n, zEType, zPlural, zDay);
    }else if( zNDays ){
2962
2963
2964
2965
2966
2967
2968
2969

2970
2971
2972

2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985

2986
2987
2988

2989
2990
2991
2992
2993
2994
2995
3136
3137
3138
3139
3140
3141
3142

3143
3144
3145

3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158

3159
3160
3161

3162
3163
3164
3165
3166
3167
3168
3169







-
+


-
+












-
+


-
+







      static const char *const azMatchStyles[] = {
        "exact", "Exact", "glob", "Glob", "like", "Like", "regexp", "Regexp",
        "brlist", "List"
      };
      double rDate;
      zDate = db_text(0, "SELECT min(timestamp) FROM timeline /*scan*/");
      if( (!zDate || !zDate[0]) && ( zAfter || zBefore ) ){
        zDate = mprintf("%s", (zAfter ? zAfter : zBefore));
        zDate = fossil_strdup((zAfter ? zAfter : zBefore));
      }
      if( zDate ){
        rDate = symbolic_name_to_mtime(zDate, 0);
        rDate = symbolic_name_to_mtime(zDate, 0, 0);
        if( db_int(0,
            "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
            " WHERE blob.rid=event.objid AND mtime<=%.17g%s)",
            rDate-ONE_SECOND, blob_sql_text(&cond))
        ){
          zOlderButton = fossil_strdup(url_render(&url, "b", zDate, "a", 0));
          zOlderButtonLabel = "More";
        }
        free(zDate);
      }
      zDate = db_text(0, "SELECT max(timestamp) FROM timeline /*scan*/");
      if( (!zDate || !zDate[0]) && ( zAfter || zBefore ) ){
        zDate = mprintf("%s", (zBefore ? zBefore : zAfter));
        zDate = fossil_strdup((zBefore ? zBefore : zAfter));
      }
      if( zDate ){
        rDate = symbolic_name_to_mtime(zDate, 0);
        rDate = symbolic_name_to_mtime(zDate, 0, 0);
        if( db_int(0,
            "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
            " WHERE blob.rid=event.objid AND mtime>=%.17g%s)",
            rDate+ONE_SECOND, blob_sql_text(&cond))
        ){
          zNewerButton = fossil_strdup(url_render(&url, "a", zDate, "b", 0));
          zNewerButtonLabel = "More";
3007
3008
3009
3010
3011
3012
3013
3014
3015




3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029

3030
3031
3032
3033
3034

3035
3036
3037
3038
3039
3040
3041
3181
3182
3183
3184
3185
3186
3187


3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204

3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218







-
-
+
+
+
+













-
+





+







      if( advancedMenu ){
        style_submenu_entry("t", "Tag Filter:", -8, 0);
        style_submenu_multichoice("ms", count(azMatchStyles)/2,azMatchStyles,0);
      }
    }
    blob_zero(&cond);
  }
  if( PB("showsql") ){
    @ <pre>%h(blob_sql_text(&sql))</pre>
  if( showSql ){
    db_append_dml_to_blob(0);
    @ <pre>%h(blob_str(&allSql))</pre>
    blob_reset(&allSql);
  }
  if( search_restrict(SRCH_CKIN)!=0 ){
    style_submenu_element("Search", "%R/search?y=c");
  }
  if( advancedMenu ){
    style_submenu_element("Basic", "%s",
        url_render(&url, "advm", "0", "udc", "1"));
  }else{
    style_submenu_element("Advanced", "%s",
        url_render(&url, "advm", "1", "udc", "1"));
  }
  if( PB("showid") ) tmFlags |= TIMELINE_SHOWRID;
  if( useDividers && zMark && zMark[0] ){
    double r = symbolic_name_to_mtime(zMark, 0);
    double r = symbolic_name_to_mtime(zMark, 0, 0);
    if( r>0.0 && !selectedRid ) selectedRid = timeline_add_divider(r);
  }
  blob_zero(&sql);
  if( PB("oldestfirst") ){
    db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby ASC /*scan*/");
    tmFlags &= ~(TIMELINE_GRAPH|TIMELINE_CHPICK);
  }else{
    db_prepare(&q, "SELECT * FROM timeline ORDER BY sortby DESC /*scan*/");
  }
  if( fossil_islower(desc.aData[0]) ){
    desc.aData[0] = fossil_toupper(desc.aData[0]);
  }
  if( zBrName ){
3060
3061
3062
3063
3064
3065
3066










3067
3068
3069
3070
3071
3072














3073
3074




3075
3076
3077
3078
3079
3080


3081
3082
3083
3084
3085
3086
3087
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273


3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292







+
+
+
+
+
+
+
+
+
+






+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+






+
+







  }
  blob_reset(&desc);

  /* Report any errors. */
  if( zError ){
    @ <p class="generalError">%h(zError)</p>
  }
   
  /* Swap zNewer and zOlder buttons if we display oldestfirst */
  if( PB("oldestfirst") ){
    char *zSwap = zNewerButton;
    char *zSwapLabel = zNewerButtonLabel;
    zNewerButton = zOlderButton;
    zNewerButtonLabel = zOlderButtonLabel;
    zOlderButton = zSwap;
    zOlderButtonLabel = zSwapLabel;
  }

  if( zNewerButton ){
    @ %z(chref("button","%s",zNewerButton))%h(zNewerButtonLabel)\
    @ &nbsp;&uarr;</a>
  }
  cgi_check_for_malice();
  {
    Matcher *pLeftBranch;
    const char *zPattern = P("sl");
    if( zPattern!=0 ){
      MatchStyle ms;
      if( zMatchStyle!=0 ){
        ms = matchStyle;
      }else{
        ms = strpbrk(zPattern,"*[?")!=0 ? MS_GLOB : MS_BRLIST;
      }
      pLeftBranch = match_create(ms,zPattern);
    }else{
      pLeftBranch = match_create(matchStyle, zBrName?zBrName:zTagName);
    }
  www_print_timeline(&q, tmFlags, zThisUser, zThisTag, zBrName,
                     selectedRid, secondaryRid, 0);
    www_print_timeline(&q, tmFlags, zThisUser, zThisTag, pLeftBranch,
                       selectedRid, secondaryRid, 0);
    match_free(pLeftBranch);
  }
  db_finalize(&q);
  if( zOlderButton ){
    @ %z(chref("button","%s",zOlderButton))%h(zOlderButtonLabel)\
    @ &nbsp;&darr;</a>
  }
  document_emit_js(/*handles pikchrs rendered above*/);
  blob_reset(&sql);
  blob_reset(&desc);
  style_finish_page();
}

/*
** Translate a timeline entry into the printable format by
** converting every %-substitutions as follows:
**
3370
3371
3372
3373
3374
3375
3376

































3377
3378
3379
3380
3381
3382

3383
3384
3385
3386
3387
3388

3389
3390
3391
3392
3393
3394
3395
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626

3627
3628
3629
3630
3631
3632
3633
3634







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+






+





-
+







      fossil_print("+++ end of timeline (%d) +++\n", nEntry);
    }else{
      fossil_print("+++ no more data (%d) +++\n", nEntry);
    }
  }
  if( fchngQueryInit ) db_finalize(&fchngQuery);
}

/*
**    wiki_to_text(TEXT)
**
** Return a text rendering of Fossil-Wiki TEXT, intended for display
** on a timeline.  The timeline-plaintext and timeline-hard-newlines
** settings are considered when doing this rendering.
*/
static void wiki_to_text_sqlfunc(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  const char *zIn, *zOut;
  int nIn, nOut;
  Blob in, html, txt;
  zIn = (const char*)sqlite3_value_text(argv[0]);
  if( zIn==0 ) return;
  nIn = sqlite3_value_bytes(argv[0]);
  blob_init(&in, zIn, nIn);
  blob_init(&html, 0, 0);
  wiki_convert(&in, &html, wiki_convert_flags(0));
  blob_reset(&in);
  blob_init(&txt, 0, 0);
  html_to_plaintext(blob_str(&html), &txt, 0);
  blob_reset(&html);
  nOut = blob_size(&txt);
  zOut = blob_str(&txt);
  while( fossil_isspace(zOut[0]) ){ zOut++; nOut--; }
  while( nOut>0 && fossil_isspace(zOut[nOut-1]) ){ nOut--; }
  sqlite3_result_text(context, zOut, nOut, SQLITE_TRANSIENT);
  blob_reset(&txt);
}

/*
** Return a pointer to a static string that forms the basis for
** a timeline query for display on a TTY.
*/
const char *timeline_query_for_tty(void){
  static int once = 0;
  static const char zBaseSql[] =
    @ SELECT
    @   blob.rid AS rid,
    @   uuid,
    @   datetime(event.mtime,toLocal()) AS mDateTime,
    @   coalesce(ecomment,comment)
    @   wiki_to_text(coalesce(ecomment,comment))
    @     || ' (user: ' || coalesce(euser,user,'?')
    @     || (SELECT case when length(x)>0 then ' tags: ' || x else '' end
    @           FROM (SELECT group_concat(substr(tagname,5), ', ') AS x
    @                   FROM tag, tagxref
    @                  WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
    @                    AND tagxref.rid=blob.rid AND tagxref.tagtype>0))
    @     || ')' as comment,
3409
3410
3411
3412
3413
3414
3415





3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432

3433
3434
3435
3436
3437
3438
3439
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675

3676
3677
3678
3679
3680
3681
3682
3683







+
+
+
+
+
















-
+







    @ FROM tag CROSS JOIN event CROSS JOIN blob
    @      LEFT JOIN tagxref ON tagxref.tagid=tag.tagid
    @   AND tagxref.tagtype>0
    @   AND tagxref.rid=blob.rid
    @ WHERE blob.rid=event.objid
    @   AND tag.tagname='branch'
  ;
  if( !once && g.db ){
    once = 1;
    sqlite3_create_function(g.db, "wiki_to_text", 1, SQLITE_UTF8, 0,
                            wiki_to_text_sqlfunc, 0, 0);
  }
  return zBaseSql;
}

/*
** Return true if the input string is a date in the ISO 8601 format:
** YYYY-MM-DD.
*/
static int isIsoDate(const char *z){
  return strlen(z)==10
      && z[4]=='-'
      && z[7]=='-'
      && fossil_isdigit(z[0])
      && fossil_isdigit(z[5]);
}

/*
** Return true if the input string can be converted to a julianday.
** Return true if the input string can be converted to a Julian day.
*/
static int fossil_is_julianday(const char *zDate){
  return db_int(0, "SELECT EXISTS (SELECT julianday(%Q) AS jd"
                   " WHERE jd IS NOT NULL)", zDate);
}


3483
3484
3485
3486
3487
3488
3489
3490


3491
3492
3493
3494
3495
3496
3497
3498
3499

3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514

3515
3516
3517
3518
3519
3520
3521


3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536

3537
3538
3539
3540
3541
3542
3543
3727
3728
3729
3730
3731
3732
3733

3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767

3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792







-
+
+









+















+






-
+
+















+







**   --medium             Medium-verbose entry formatting
**   --full               Extra verbose entry formatting
**   -n|--limit N         If N is positive, output the first N entries.  If
**                        N is negative, output the first -N lines.  If N is
**                        zero, no limit.  Default is -20 meaning 20 lines.
**   --offset P           Skip P changes
**   -p|--path PATH       Output items affecting PATH only.
**                        PATH can be a file or a sub directory.
**                        PATH can be a file or a subdirectory.
**   -r|--reverse         Show items in chronological order.
**   -R REPO_FILE         Specifies the repository db to use. Default is
**                        the current check-out's repository.
**   --sql                Show the SQL used to generate the timeline
**   -t|--type TYPE       Output items from the given types only, such as:
**                            ci = file commits only
**                            e  = technical notes only
**                            f  = forum posts only
**                            t  = tickets only
**                            w  = wiki commits only
**   -u|--for-user USER   Only show items associated with USER
**   -v|--verbose         Output the list of files changed by each commit
**                        and the type of each change (edited, deleted,
**                        etc.) after the check-in comment.
**   -W|--width N         Width of lines (default is to auto-detect). N must be
**                        either greater than 20 or it must be zero 0 to
**                        indicate no limit, resulting in a single line per
**                        entry.
*/
void timeline_cmd(void){
  Stmt q;
  int n, k, width;
  const char *zLimit;
  const char *zWidth;
  const char *zOffset;
  const char *zType;
  const char *zUser;
  char *zOrigin;
  char *zDate;
  Blob sql;
  int objid = 0;
  Blob uuid;
  int mode = TIMELINE_MODE_NONE;
  int verboseFlag = 0 ;
  int verboseFlag = 0;
  int reverseFlag = 0;
  int iOffset;
  const char *zFilePattern = 0;
  const char *zFormat = 0;
  const char *zBr = 0;
  Blob treeName;
  int showSql = 0;

  verboseFlag = find_option("verbose","v", 0)!=0;
  if( !verboseFlag){
    verboseFlag = find_option("showfiles","f", 0)!=0; /* deprecated */
  }
  db_find_and_open_repository(0, 0);
  zLimit = find_option("limit","n",1);
  zWidth = find_option("width","W",1);
  zType = find_option("type","t",1);
  zUser = find_option("for-user","u",1);
  zFilePattern = find_option("path","p",1);
  zFormat = find_option("format","F",1);
  zBr = find_option("branch","b",1);
  if( find_option("current-branch","c",0)!=0 ){
    if( !g.localOpen ){
      fossil_fatal("not within an open check-out");
    }else{
3571
3572
3573
3574
3575
3576
3577

3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598

3599
3600
3601
3602
3603
3604
3605
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847

3848
3849
3850
3851
3852
3853
3854
3855







+




















-
+







      fossil_fatal("-W|--width value must be >20 or 0");
    }
  }else{
    width = -1;
  }
  zOffset = find_option("offset",0,1);
  iOffset = zOffset ? atoi(zOffset) : 0;
  reverseFlag = find_option("reverse","r",0)!=0;

  /* We should be done with options.. */
  verify_all_options();

  if( g.argc>=4 ){
    k = strlen(g.argv[2]);
    if( strncmp(g.argv[2],"before",k)==0 ){
      mode = TIMELINE_MODE_BEFORE;
    }else if( strncmp(g.argv[2],"after",k)==0 && k>1 ){
      mode = TIMELINE_MODE_AFTER;
    }else if( strncmp(g.argv[2],"descendants",k)==0 ){
      mode = TIMELINE_MODE_CHILDREN;
    }else if( strncmp(g.argv[2],"children",k)==0 ){
      mode = TIMELINE_MODE_CHILDREN;
    }else if( strncmp(g.argv[2],"ancestors",k)==0 && k>1 ){
      mode = TIMELINE_MODE_PARENTS;
    }else if( strncmp(g.argv[2],"parents",k)==0 ){
      mode = TIMELINE_MODE_PARENTS;
    }else if(!zType && !zLimit){
      usage("?WHEN? ?CHECKIN|DATETIME? ?-n|--limit #? ?-t|--type TYPE? "
            "?-W|--width WIDTH? ?-p|--path PATH?");
            "?-W|--width WIDTH? ?-p|--path PATH? ?-r|--reverse?");
    }
    if( '-' != *g.argv[3] ){
      zOrigin = g.argv[3];
    }else{
      zOrigin = "now";
    }
  }else if( g.argc==3 ){
3649
3650
3651
3652
3653
3654
3655




3656
3657
3658
3659
3660






3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932



3933
3934
3935
3936
3937
3938
3939







+
+
+
+





+
+
+
+
+
+












-
-
-







       * zFilePattern. */
      zFilePattern = 0;
    }
  }

  if( mode==TIMELINE_MODE_NONE ) mode = TIMELINE_MODE_BEFORE;
  blob_zero(&sql);
  if( mode==TIMELINE_MODE_AFTER ){
    /* Extra outer select to get older rows in reverse order */
    blob_append(&sql, "SELECT *\nFROM (", -1);
  }
  blob_append(&sql, timeline_query_for_tty(), -1);
  blob_append_sql(&sql, "\n  AND event.mtime %s %s",
     ( mode==TIMELINE_MODE_BEFORE ||
       mode==TIMELINE_MODE_PARENTS ) ? "<=" : ">=", zDate /*safe-for-%s*/
  );
  if( zType && (zType[0]!='a') ){
    blob_append_sql(&sql, "\n  AND event.type=%Q ", zType);
  }
  if( zUser && (zUser[0]!='\0') ){
    blob_append_sql(&sql, "\n  AND user0=%Q ", zUser);
  }

  /* When zFilePattern is specified, compute complete ancestry;
   * limit later at print_timeline() */
  if( mode==TIMELINE_MODE_CHILDREN || mode==TIMELINE_MODE_PARENTS ){
    db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)");
    if( mode==TIMELINE_MODE_CHILDREN ){
      compute_descendants(objid, (zFilePattern ? 0 : n));
    }else{
      compute_ancestors(objid, (zFilePattern ? 0 : n), 0, 0);
    }
    blob_append_sql(&sql, "\n  AND blob.rid IN ok");
  }
  if( zType && (zType[0]!='a') ){
    blob_append_sql(&sql, "\n  AND event.type=%Q ", zType);
  }
  if( zFilePattern ){
    blob_append(&sql,
       "\n  AND EXISTS(SELECT 1 FROM mlink\n"
         "              WHERE mlink.mid=event.objid\n"
         "                AND mlink.fnid IN ", -1);
    if( filenames_are_case_sensitive() ){
      blob_append_sql(&sql,
3710
3711
3712
3713
3714
3715
3716









3717







3718
3719
3720
3721
3722
3723
3724
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982

3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996







+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+







      "                          AND e.comment LIKE '_checkin/%%'\n"
      "        LEFT JOIN tagxref tx ON tx.rid=b.rid AND tx.tagid=%d\n"
      "          WHERE tx.value='%q'\n"
      ")\n"                                              /* No merge closures */
      "  AND (tagxref.value IS NULL OR tagxref.value='%q')",
      zBr, zBr, zBr, TAG_BRANCH, zBr, zBr);
  }

  if( mode==TIMELINE_MODE_AFTER ){
    int lim = n;
    if( n == 0 ){
      lim = -1; /* 0 means no limit */
    }else if( n < 0 ){
      lim = -n;
    }
    /* Complete the above outer select. */
  blob_append_sql(&sql, "\nORDER BY event.mtime DESC");
    blob_append_sql(&sql,
        "\nORDER BY event.mtime LIMIT %d) t ORDER BY t.mDateTime %s",
          lim, reverseFlag ? "" : "DESC");
  }else{
    blob_append_sql(&sql,
         "\nORDER BY event.mtime %s", reverseFlag ? "" : "DESC");
  }
  if( iOffset>0 ){
    /* Don't handle LIMIT here, otherwise print_timeline()
     * will not determine the end-marker correctly! */
    blob_append_sql(&sql, "\n LIMIT -1 OFFSET %d", iOffset);
  }
  if( showSql ){
    fossil_print("%s\n", blob_str(&sql));
3736
3737
3738
3739
3740
3741
3742
3743

3744
3745
3746
3747
3748

3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759

3760
3761
3762
3763
3764
3765
3766
4008
4009
4010
4011
4012
4013
4014

4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031

4032
4033
4034
4035
4036
4037
4038
4039







-
+





+










-
+







** day of the year for various years in the history of the project.
**
** Query parameters:
**
**    today=DATE             Use DATE as today's date
*/
void thisdayinhistory_page(void){
  static int aYearsAgo[] = { 1, 2, 3, 4, 5, 10, 15, 20, 30, 40, 50, 75, 100 };
  static int aYearsAgo[] = { 1,2,3,4,5,10,15,20,25,30,40,50,75,100 };
  const char *zToday;
  char *zStartOfProject;
  int i;
  Stmt q;
  char *z;
  int bZulu = 0;

  login_check_credentials();
  if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum) ){
    login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
    return;
  }
  style_set_current_feature("timeline");
  style_header("Today In History");
  zToday = (char*)P("today");
  if( zToday ){
    zToday = timeline_expand_datetime(zToday);
    zToday = timeline_expand_datetime(zToday, &bZulu);
    if( !fossil_isdate(zToday) ) zToday = 0;
  }
  if( zToday==0 ){
    zToday = db_text(0, "SELECT date('now',toLocal())");
  }
  @ <h1>This Day In History For %h(zToday)</h1>
  z = db_text(0, "SELECT date(%Q,'-1 day')", zToday);
Changes to src/tkt.c.
100
101
102
103
104
105
106
107

108
109
110
111
112
113
114
100
101
102
103
104
105
106

107
108
109
110
111
112
113
114







-
+







      continue;
    }
    if( strchr(zFieldName,' ')!=0 ) continue;
    if( nField%10==0 ){
      aField = fossil_realloc(aField, sizeof(aField[0])*(nField+10) );
    }
    aField[nField].zBsln = 0;
    aField[nField].zName = mprintf("%s", zFieldName);
    aField[nField].zName = fossil_strdup(zFieldName);
    aField[nField].mUsed = USEDBY_TICKET;
    nField++;
  }
  db_finalize(&q);
  if( nBaselines ){
    db_prepare(&q, "SELECT 1 FROM pragma_table_info('ticket') "
                   "WHERE type = 'INTEGER' AND name = :n");
143
144
145
146
147
148
149
150

151
152
153
154
155
156
157
143
144
145
146
147
148
149

150
151
152
153
154
155
156
157







-
+







      aField[i].mUsed |= USEDBY_TICKETCHNG;
      continue;
    }
    if( nField%10==0 ){
      aField = fossil_realloc(aField, sizeof(aField[0])*(nField+10) );
    }
    aField[nField].zBsln = 0;
    aField[nField].zName = mprintf("%s", zFieldName);
    aField[nField].zName = fossil_strdup(zFieldName);
    aField[nField].mUsed = USEDBY_TICKETCHNG;
    nField++;
  }
  db_finalize(&q);
  qsort(aField, nField, sizeof(aField[0]), nameCmpr);
  noRegularMimetype = 1;
  for(i=0; i<nField; i++){
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
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







+


-
+
+
+
+

+













-
+

+




+
+




-
+












-
+







** Otherwise, db_reveal() is a no-op and the content remains
** obscured.
*/
static void initializeVariablesFromDb(void){
  const char *zName;
  Stmt q;
  int i, n, size, j;
  const char *zCTimeColumn = haveTicketCTime ? "tkt_ctime" : "tkt_mtime";

  zName = PD("name","-none-");
  db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, *"
  db_prepare(&q, "SELECT datetime(tkt_mtime,toLocal()) AS tkt_datetime, "
                 "datetime(%s,toLocal()) AS tkt_datetime_creation, "
                 "julianday('now') - tkt_mtime, "
                 "julianday('now') - %s, *"
                 "  FROM ticket WHERE tkt_uuid GLOB '%q*'",
                 zCTimeColumn/*safe-for-%s*/, zCTimeColumn/*safe-for-%s*/,
                 zName);
  if( db_step(&q)==SQLITE_ROW ){
    n = db_column_count(&q);
    for(i=0; i<n; i++){
      const char *zVal = db_column_text(&q, i);
      const char *zName = db_column_name(&q, i);
      char *zRevealed = 0;
      if( zVal==0 ){
        zVal = "";
      }else if( strncmp(zName, "private_", 8)==0 ){
        zVal = zRevealed = db_reveal(zVal);
      }
      if( (j = fieldId(zName))>=0 ){
        aField[j].zValue = mprintf("%s", zVal);
        aField[j].zValue = fossil_strdup(zVal);
      }else if( memcmp(zName, "tkt_", 4)==0 && Th_Fetch(zName, &size)==0 ){
        /* TICKET table columns that begin with "tkt_" are always safe */
        Th_Store(zName, zVal);
      }
      free(zRevealed);
    }
    Th_Store("tkt_mage", human_readable_age(db_column_double(&q, 2)));
    Th_Store("tkt_cage", human_readable_age(db_column_double(&q, 3)));
  }
  db_finalize(&q);
  for(i=0; i<nField; i++){
    if( Th_Fetch(aField[i].zName, &size)==0 ){
      Th_Store(aField[i].zName, aField[i].zValue);
      Th_StoreUnsafe(aField[i].zName, aField[i].zValue);
    }
  }
}

/*
** Transfer all CGI parameters to variables in the interpreter.
*/
static void initializeVariablesFromCGI(void){
  int i;
  const char *z;

  for(i=0; (z = cgi_parameter_name(i))!=0; i++){
    Th_Store(z, P(z));
    Th_StoreUnsafe(z, P(z));
  }
}

/*
** Information about a single J-card
*/
struct jCardInfo {
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
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







+
+
+












-
-
-

-
+

+
+
+
+







    }else{
      showTimeline = 0;
    }
  }
  if( !showTimeline && g.perm.Hyperlink ){
    style_submenu_element("Timeline", "%R/info/%T", zUuid);
  }
  zFullName = db_text(0,
       "SELECT tkt_uuid FROM ticket"
       " WHERE tkt_uuid GLOB '%q*'", zUuid);
  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);
  safe_html_context(DOCSRC_TICKET);
  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>");
    attachment_list(zFullName, "<h2>Attachments:</h2>", 1);
  }

  builtin_fossil_js_bundle_or("dom", "storage", NULL);
  builtin_request_js("fossil.page.ticket.js");
  builtin_fulfill_js_requests();

  style_finish_page();
}

/*
** TH1 command: append_field FIELD STRING
**
807
808
809
810
811
812
813
814

815
816
817
818


819
820
821
822
823
824
825
819
820
821
822
823
824
825

826
827
828


829
830
831
832
833
834
835
836
837







-
+


-
-
+
+







  int idx;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "append_field FIELD STRING");
  }
  if( g.thTrace ){
    Th_Trace("append_field %#h {%#h}<br>\n",
              argl[1], argv[1], argl[2], argv[2]);
              TH1_LEN(argl[1]), argv[1], TH1_LEN(argl[2]), argv[2]);
  }
  for(idx=0; idx<nField; idx++){
    if( memcmp(aField[idx].zName, argv[1], argl[1])==0
        && aField[idx].zName[argl[1]]==0 ){
    if( memcmp(aField[idx].zName, argv[1], TH1_LEN(argl[1]))==0
        && aField[idx].zName[TH1_LEN(argl[1])]==0 ){
      break;
    }
  }
  if( idx>=nField ){
    Th_ErrorMessage(g.interp, "no such TICKET column: ", argv[1], argl[1]);
    return TH_ERROR;
  }
927
928
929
930
931
932
933

934
935
936
937
938
939
940
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953







+







  }
  for(i=0; i<nField; i++){
    const char *zValue;
    int nValue;
    if( aField[i].zAppend ) continue;
    zValue = Th_Fetch(aField[i].zName, &nValue);
    if( zValue ){
      nValue = TH1_LEN(nValue);
      while( nValue>0 && fossil_isspace(zValue[nValue-1]) ){ nValue--; }
      if( ((aField[i].mUsed & USEDBY_TICKETCHNG)!=0 && nValue>0)
       || memcmp(zValue, aField[i].zValue, nValue)!=0
       ||(int)strlen(aField[i].zValue)!=nValue
      ){
        if( memcmp(aField[i].zName, "private_", 8)==0 ){
          zValue = db_conceal(zValue, nValue);
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
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







+

-
-
-
-
+





-
+




-
+





+
+
+
-
+
+







  initializeVariablesFromDb();
  if( g.zPath[0]=='d' ) showAllFields();
  form_begin(0, "%R/%s", g.zPath);
  if( P("date_override") && g.perm.Setup ){
    @ <input type="hidden" name="date_override" value="%h(P("date_override"))">
  }
  zScript = ticket_newpage_code();
  Th_Store("private_contact", "");
  if( g.zLogin && g.zLogin[0] ){
    int nEmail = 0;
    (void)Th_MaybeGetVar(g.interp, "private_contact", &nEmail);
    uid = nEmail>0
      ? 0 : db_int(0, "SELECT uid FROM user WHERE login=%Q", g.zLogin);
    uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.zLogin);
    if( uid ){
      char * zEmail =
        db_text(0, "SELECT find_emailaddr(info) FROM user WHERE uid=%d",
                uid);
      if( zEmail ){
        Th_Store("private_contact", zEmail);
        Th_StoreUnsafe("private_contact", zEmail);
        fossil_free(zEmail);
      }
    }
  }
  Th_Store("login", login_name());
  Th_StoreUnsafe("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 ){
    if( P("submitandnew") ){
      cgi_redirect(mprintf("%R/tktnew/%s", zNewUuid));
    }else{
    cgi_redirect(mprintf("%R/tktview/%s", zNewUuid));
      cgi_redirect(mprintf("%R/tktview/%s", zNewUuid));
    }
    return;
  }
  captcha_generate(0);
  @ </form>
  if( g.thTrace ) Th_Trace("END_TKTVIEW<br>\n", -1);
  style_finish_page();
}
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







-
+







  getAllTicketFields();
  initializeVariablesFromCGI();
  initializeVariablesFromDb();
  if( g.zPath[0]=='d' ) showAllFields();
  form_begin(0, "%R/%s", g.zPath);
  @ <input type="hidden" name="name" value="%s(zName)">
  zScript = ticket_editpage_code();
  Th_Store("login", login_name());
  Th_StoreUnsafe("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("%R/tktview/%s", zName));
    return;
1503
1504
1505
1506
1507
1508
1509
1510

1511
1512
1513
1514
1515
1516
1517
1518

1519
1520
1521
1522
1523
1524
1525
1518
1519
1520
1521
1522
1523
1524

1525
1526
1527
1528
1529
1530
1531
1532

1533
1534
1535
1536
1537
1538
1539
1540







-
+







-
+







**
** Run various subcommands to control tickets
**
** > fossil ticket show (REPORTTITLE|REPORTNR) ?TICKETFILTER? ?OPTIONS?
**
**     Options:
**       -l|--limit LIMITCHAR
**       -q|--quote
**       --quote
**       -R|--repository REPO
**
**     Run the ticket report, identified by the report format title
**     used in the GUI. The data is written as flat file on stdout,
**     using TAB as separator. The separator can be changed using
**     the -l or --limit option.
**
**     If TICKETFILTER is given on the commandline, the query is
**     If TICKETFILTER is given on the command line, the query is
**     limited with a new WHERE-condition.
**       example:  Report lists a column # with the uuid
**                 TICKETFILTER may be [#]='uuuuuuuuu'
**       example:  Report only lists rows with status not open
**                 TICKETFILTER: status != 'open'
**
**     If --quote is used, the tickets are encoded by quoting special
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
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
1584







-
-
+
+










-
+




-
+







**     List all fields defined for ticket in the fossil repository.
**
** > fossil ticket list reports
** > fossil ticket ls reports
**
**     List all ticket reports defined in the fossil repository.
**
** > fossil ticket set TICKETUUID (FIELD VALUE)+ ?-q|--quote?
** > fossil ticket change TICKETUUID (FIELD VALUE)+ ?-q|--quote?
** > fossil ticket set TICKETUUID (FIELD VALUE)+ ?--quote?
** > fossil ticket change TICKETUUID (FIELD VALUE)+ ?--quote?
**
**     Change ticket identified by TICKETUUID to set the values of
**     each field FIELD to VALUE.
**
**     Field names as defined in the TICKET table.  By default, these
**     names include: type, status, subsystem, priority, severity, foundin,
**     resolution, title, and comment, but other field names can be added
**     or substituted in customized installations.
**
**     If you use +FIELD, the VALUE is appended to the field FIELD.  You
**     can use more than one field/value pair on the commandline.  Using
**     can use more than one field/value pair on the command line.  Using
**     --quote enables the special character decoding as in "ticket
**     show", which allows setting multiline text or text with special
**     characters.
**
** > fossil ticket add FIELD VALUE ?FIELD VALUE .. ? ?-q|--quote?
** > fossil ticket add FIELD VALUE ?FIELD VALUE .. ? ?--quote?
**
**     Like set, but create a new ticket with the given values.
**
** > fossil ticket history TICKETUUID
**
**     Show the complete change history for the ticket
**
1625
1626
1627
1628
1629
1630
1631
1632

1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646

1647
1648
1649
1650
1651
1652
1653
1654







-
+







        fossil_fatal("unknown ticket list option '%s'!",g.argv[3]);
      }
    }
  }else{
    /* add a new ticket or set fields on existing tickets */
    tTktShowEncoding tktEncoding;

    tktEncoding = find_option("quote","q",0) ? tktFossilize : tktNoTab;
    tktEncoding = find_option("quote",0,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;
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
1802
1803







-
+








        zFName = g.argv[i++];
        if( i==g.argc ){
          fossil_fatal("missing value for '%s'!",zFName);
        }
        zFValue = g.argv[i++];
        if( tktEncoding == tktFossilize ){
          zFValue=mprintf("%s",zFValue);
          zFValue=fossil_strdup(zFValue);
          defossilize(zFValue);
        }
        append = (zFName[0] == '+');
        if( append ){
          zFName++;
        }
        j = fieldId(zFName);
Changes to src/tktsetup.c.
123
124
125
126
127
128
129
130

131
132
133
134
135
136
137
123
124
125
126
127
128
129

130
131
132
133
134
135
136
137







-
+








  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
  }
  style_set_current_feature("tktsetup");
  if( PB("setup") ){
  if( P("setup") ){
    cgi_redirect("tktsetup");
  }
  isSubmit = P("submit")!=0;
  z = P("x");
  if( z==0 ){
    z = db_get(zDbField, zDfltValue);
  }
162
163
164
165
166
167
168

169
170
171
172
173
174
175
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176







+







  @ </p></blockquote>
  @ </div></form>
  @ <hr>
  @ <h2>Default %s(zTitle)</h2>
  @ <blockquote><pre>
  @ %h(zDfltValue)
  @ </pre></blockquote>
  style_submenu_element("Back", "%R/tktsetup");
  style_finish_page();
}

/*
** WEBPAGE: tktsetup_tab
** Administrative page for defining the "ticket" table used
** to hold ticket information.
299
300
301
302
303
304
305
306

307
308
309
310
311
312
313
300
301
302
303
304
305
306

307
308
309
310
311
312
313
314







-
+







    30
  );
}

static const char zDefaultNew[] =
@ <th1>
@   if {![info exists mutype]} {set mutype Markdown}
@   if {[info exists submit]} {
@   if {[info exists submit] || [info exists submitandnew]} {
@      set status Open
@      if {$mutype eq "HTML"} {
@        set mimetype "text/html"
@      } elseif {$mutype eq "Wiki"} {
@        set mimetype "text/x-fossil-wiki"
@      } elseif {$mutype eq "Markdown"} {
@        set mimetype text/x-markdown
347
348
349
350
351
352
353


















354
355
356
357
358
359
360
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







@ <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>
@
@ <th1>
@   if {[capexpr {w}]} {
@      html {<tr><td class="tktDspLabel">Priority:</td><td>}
@      combobox priority $priority_choices 1
@      html {
@        <td align="left">How important is the affected functionality?</td>
@        </td></tr>
@      }
@
@      html {<tr><td class="tktDspLabel">Subsystem:</td><td>}
@      combobox subsystem $subsystem_choices 1
@      html {
@        <td align="left">Which subsystem is affected?</td>
@        </td></tr>
@      }
@   }
@ </th1>
@
@ <tr>
@ <td align="right">EMail:</td>
@ <td align="left">
@ <input 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>
403
404
405
406
407
408
409
410









411
412
413
414
415
416
417
418

419
420
421
422
423
424
425
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







-
+
+
+
+
+
+
+
+
+







-
+







@
@ <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>
@ button to create the new ticket.</td>
@ </tr>
@
@ <tr>
@ <td><td align="left">
@ <input type="submit" name="submitandnew" value="Submit and New">
@ </td>
@ <td align="left">Create the new ticket and start another
@ ticket form with the inputs.</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>
@ <td>Abandon and forget this ticket.</td>
@ </tr>
@ </table>
;

/*
** Return the code used to generate the new ticket page
*/
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
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







-
+










+
+
+
+
+
+
+







@ <table cellpadding="5">
@ <tr><td class="tktDspLabel">Ticket&nbsp;Hash:</td>
@ <th1>
@ if {[info exists tkt_uuid]} {
@   html "<td class='tktDspValue' colspan='3'>"
@   copybtn hash-tk 0 $tkt_uuid 2
@   if {[hascap s]} {
@     html " ($tkt_id)"
@     puts " ($tkt_id)"
@   }
@   html "</td></tr>\n"
@ } else {
@   if {[hascap s]} {
@     html "<td class='tktDspValue' colspan='3'>Deleted "
@     html "(0)</td></tr>\n"
@   } else {
@     html "<td class='tktDspValue' colspan='3'>Deleted</td></tr>\n"
@   }
@ }
@
@ if {[capexpr {n}]} {
@   submenu link "Copy Ticket" $baseurl/tktnew/$tkt_uuid
@ }
@ if {[capexpr {nk}]} {
@   submenu link "Edit Wiki" $baseurl/wikiedit?name=ticket/$tkt_uuid
@ }
@ </th1>
@ <tr><td class="tktDspLabel">Title:</td>
@ <td class="tktDspValue" colspan="3">
@ $<title>
@ </td></tr>
@ <tr><td class="tktDspLabel">Status:</td><td class="tktDspValue">
@ $<status>
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
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
636
637
638
639
640
641
642
643
644

645
646
647
648
649
650
651
652







-
+
+
+
+



+
+
+
+
+
+
+
+
+
+

+
-
+


+

-


+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+

+

+
+
+
+
+

















-
+







-
-
+
+
+


+

-
+

-
+

-
+



-
+











+

-
+







@ </td>
@ <td class="tktDspLabel">Resolution:</td><td class="tktDspValue">
@ $<resolution>
@ </td></tr>
@ <tr><td class="tktDspLabel">Last&nbsp;Modified:</td><td class="tktDspValue">
@ <th1>
@ if {[info exists tkt_datetime]} {
@   html $tkt_datetime
@   puts $tkt_datetime
@ }
@ if {[info exists tkt_mage]} {
@   html "<br>[htmlize $tkt_mage] ago"
@ }
@ </th1>
@ </td>
@ <td class="tktDspLabel">Created:</td><td class="tktDspValue">
@ <th1>
@ if {[info exists tkt_datetime_creation]} {
@   puts $tkt_datetime_creation
@ }
@ if {[info exists tkt_cage]} {
@   html "<br>[htmlize $tkt_cage] ago"
@ }
@ </th1>
@ </td></tr>
@ <th1>enable_output [hascap e]</th1>
@   <tr>
@   <td class="tktDspLabel">Contact:</td><td class="tktDspValue">
@   <td class="tktDspLabel">Contact:</td><td class="tktDspValue" colspan="3">
@   $<private_contact>
@   </td>
@   </tr>
@ <th1>enable_output 1</th1>
@ </tr>
@ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td>
@ <td colspan="3" valign="top" class="tktDspValue">
@ <th1>
@ set versionlink ""
@ set urlfoundin [httpize $foundin]
@ set tagpattern {^[-0-9A-Za-z_\\.]+$}
@ if [regexp $tagpattern $foundin] {
@   query {SELECT count(*) AS match FROM tag
@          WHERE tagname=concat('sym-',$foundin)} {
@     if {$match} {set versionlink "timeline?t=$urlfoundin"}
@   }
@ }
@ set hashpattern {^[0-9a-f]+$}
@ if [regexp $hashpattern $foundin] {
@   set pattern $foundin*
@   query {SELECT count(*) AS match FROM blob WHERE uuid GLOB $pattern} {
@     if {$match} {set versionlink "info/$urlfoundin"}
@   }
@ }
@ if {$versionlink eq ""} {
@ $<foundin>
@   puts $foundin
@ } else {
@   html "<a href=\""
@   puts $versionlink
@   html "\">"
@   puts $foundin
@   html "</a>"
@ }
@ </th1>
@ </td></tr>
@ </table>
@
@ <th1>
@ wiki_assoc "ticket" $tkt_uuid
@ </th1>
@
@ <table cellpadding="5" style="min-width:100%">
@ <th1>
@ if {[info exists comment]} {
@   if {[string length $comment]>10} {
@     html {
@       <tr><td class="tktDspLabel">Description:</td></tr>
@       <tr><td colspan="5" class="tktDspValue">
@     }
@     if {[info exists plaintext]} {
@       set r [randhex]
@       wiki "<verbatim-$r links>\n$comment\n</verbatim-$r>"
@     } else {
@       wiki $comment
@     }
@   }
@ }
@ set seenRow 0
@ set alwaysPlaintext [info exists plaintext]
@ query {SELECT datetime(tkt_mtime) AS xdate, login AS xlogin,
@ query {SELECT datetime(tkt_mtime,toLocal()) AS xdate, login AS xlogin,
@               mimetype as xmimetype, icomment AS xcomment,
@               username AS xusername
@          FROM ticketchng
@         WHERE tkt_id=$tkt_id AND length(icomment)>0} {
@   if {$seenRow} {
@     html "<hr>\n"
@   } else {
@     html "<tr><td class='tktDspLabel'>User Comments:</td></tr>\n"
@     html "<tr><td colspan='5' class='tktDspValue'>\n"
@     html "<tr><td class='tktDspLabel' style='text-align:left'>\n"
@     html "User Comments:</td></tr>\n"
@     html "<tr><td colspan='5' class='tktDspValue'><div class='tktCommentArea'>\n"
@     set seenRow 1
@   }
@   html "<div class='tktCommentEntry'>"
@   html "<span class='tktDspCommenter'>"
@   html "[htmlize $xlogin]"
@   puts $xlogin
@   if {$xlogin ne $xusername && [string length $xusername]>0} {
@     html " (claiming to be [htmlize $xusername])"
@     puts " (claiming to be $xusername)"
@   }
@   html " added on $xdate:"
@   puts " added on $xdate:"
@   html "</span>\n"
@   if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
@     set r [randhex]
@     if {$xmimetype ne "text/plain"} {html "([htmlize $xmimetype])\n"}
@     if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"}
@     wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
@   } elseif {$xmimetype eq "text/x-fossil-wiki"} {
@     wiki "<p>\n[string trimright $xcomment]\n</p>\n"
@   } elseif {$xmimetype eq "text/x-markdown"} {
@     html [lindex [markdown $xcomment] 1]
@   } elseif {$xmimetype eq "text/html"} {
@     wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n"
@   } else {
@     set r [randhex]
@     wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n"
@   }
@   html "</div>"; # .tktCommentEntry
@ }
@ if {$seenRow} {html "</td></tr>\n"}
@ if {$seenRow} {html "</div></td></tr>\n"}
@ </th1>
@ </table>
;


/*
** Return the code used to generate the view ticket page
703
704
705
706
707
708
709












































710
711
712
713
714
715
716
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
838
839
840
841
842
843







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







@ <tr>
@ <td align="right">
@ <input type="submit" name="cancel" value="Cancel">
@ </td>
@ <td>Abandon this edit</td>
@ </tr>
@
@ <th1>
@ set seenRow 0
@ set alwaysPlaintext [info exists plaintext]
@ query {SELECT datetime(tkt_mtime,toLocal()) AS xdate, login AS xlogin,
@               mimetype as xmimetype, icomment AS xcomment,
@               username AS xusername
@          FROM ticketchng
@         WHERE tkt_id=$tkt_id AND length(icomment)>0} {
@   if {$seenRow} {
@     html "<hr>\n"
@   } else {
@     html "<tr><td colspan='2'><hr></td></tr>\n"
@     html "<tr><td colspan='2' class='tktDspLabel' style='text-align:left'>\n"
@     html "Previous User Comments:</td></tr>\n"
@     html "<tr><td colspan='2' class='tktDspValue'><div class='tktCommentArea'>\n"
@     set seenRow 1
@   }
@   html "<div class='tktCommentEntry'>"
@   html "<span class='tktDspCommenter'>"
@   puts $xlogin
@   if {$xlogin ne $xusername && [string length $xusername]>0} {
@     puts " (claiming to be $xusername)"
@   }
@   puts " added on $xdate:"
@   html "</span>\n"
@   if {$alwaysPlaintext || $xmimetype eq "text/plain"} {
@     set r [randhex]
@     if {$xmimetype ne "text/plain"} {puts "($xmimetype)\n"}
@     wiki "<verbatim-$r>[string trimright $xcomment]</verbatim-$r>\n"
@   } elseif {$xmimetype eq "text/x-fossil-wiki"} {
@     wiki "<p>\n[string trimright $xcomment]\n</p>\n"
@   } elseif {$xmimetype eq "text/x-markdown"} {
@     html [lindex [markdown $xcomment] 1]
@   } elseif {$xmimetype eq "text/html"} {
@     wiki "<p><nowiki>\n[string trimright $xcomment]\n</nowiki>\n"
@   } else {
@     set r [randhex]
@     wiki "<verbatim-$r links>[string trimright $xcomment]</verbatim-$r>\n"
@   }
@   html "</div>"; # .tktCommentEntry
@ }
@ if {$seenRow} {html "</div></td></tr>\n"}
@ </th1>
@
@ </table>
;

/*
** Return the code used to generate the edit ticket page
*/
const char *ticket_editpage_code(void){
801
802
803
804
805
806
807

808

809
810
811
812
813
814
815
928
929
930
931
932
933
934
935

936
937
938
939
940
941
942
943







+
-
+







@   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',
@   substr(tkt_uuid,1,10) AS '#',
@   datetime(tkt_ctime,toLocal()) AS 'created',
@   datetime(tkt_mtime) AS 'mtime',
@   datetime(tkt_mtime,toLocal()) AS 'modified',
@   type,
@   status,
@   subsystem,
@   title,
@   comment AS '_comments'
@ FROM ticket
;
888
889
890
891
892
893
894
895

896
897
898
899
900
901
902
1016
1017
1018
1019
1020
1021
1022

1023
1024
1025
1026
1027
1028
1029
1030







-
+







    10
  );
}

/*
** WEBPAGE: tktsetup_timeline
**
** Administrative page used ot configure how tickets are
** Administrative page used to configure how tickets are
** rendered on timeline views.
*/
void tktsetup_timeline_page(void){
  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed(0);
    return;
935
936
937
938
939
940
941

942
943
944
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073







+



  @ <hr>
  @ <p>
  @ <input type="submit"  name="submit" value="Apply Changes">
  @ <input type="submit" name="setup" value="Cancel">
  @ </p>
  @ </div></form>
  db_end_transaction(0);
  style_submenu_element("Back", "%R/tktsetup");
  style_finish_page();

}
Changes to src/undo.c.
21
22
23
24
25
26
27
28

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

28
29
30
31
32
33
34
35







-
+







#include "undo.h"

#if INTERFACE
/*
** Possible return values from the undo_maybe_save() routine.
*/
#define UNDO_NONE     (0) /* Placeholder only used to initialize vars. */
#define UNDO_SAVED_OK (1) /* The specified file was saved succesfully. */
#define UNDO_SAVED_OK (1) /* The specified file was saved successfully. */
#define UNDO_DISABLED (2) /* File not saved, subsystem is disabled. */
#define UNDO_INACTIVE (3) /* File not saved, subsystem is not active. */
#define UNDO_TOOBIG   (4) /* File not saved, it exceeded a size limit. */
#endif

/*
** Undo the change to the file zPathname.  zPathname is the pathname
68
69
70
71
72
73
74
75

76
77
78
79
80
81
82
68
69
70
71
72
73
74

75
76
77
78
79
80
81
82







-
+







    blob_zero(&new);
    old_exists = db_column_int(&q, 1);
    old_exe = db_column_int(&q, 2);
    if( old_exists ){
      db_ephemeral_blob(&q, 0, &new);
    }
    if( file_unsafe_in_tree_path(zFullname) ){
      /* do nothign with this unsafe file */
      /* do nothing with this unsafe file */
    }else if( old_exists ){
      if( new_exists ){
        fossil_print("%s   %s\n", redoFlag ? "REDO" : "UNDO", zPathname);
      }else{
        fossil_print("NEW    %s\n", zPathname);
      }
      if( new_exists && (new_link || old_link) ){
287
288
289
290
291
292
293
294

295
296
297
298
299
300
301
287
288
289
290
291
292
293

294
295
296
297
298
299
300
301







-
+







** WARNING: Please do NOT call this function with a limit
**          value less than zero, call the undo_save()
**          function instead.
**
** The return value of this function will always be one of the
** following codes:
**
** UNDO_SAVED_OK: The specified file was saved succesfully.
** UNDO_SAVED_OK: The specified file was saved successfully.
**
** UNDO_DISABLED: The specified file was NOT saved, because the
**                "undo subsystem" is disabled.  This error may
**                indicate that a call to undo_disable() was
**                issued.
**
** UNDO_INACTIVE: The specified file was NOT saved, because the
Changes to src/unicode.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
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
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
165
166







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







  */
  static const unsigned int aEntry[] = {
    0x00000030, 0x0000E807, 0x00016C06, 0x0001EC2F, 0x0002AC07,
    0x0002D001, 0x0002D803, 0x0002EC01, 0x0002FC01, 0x00035C01,
    0x0003DC01, 0x000B0804, 0x000B480E, 0x000B9407, 0x000BB401,
    0x000BBC81, 0x000DD401, 0x000DF801, 0x000E1002, 0x000E1C01,
    0x000FD801, 0x00120808, 0x00156806, 0x00162402, 0x00163403,
    0x00164437, 0x0017CC02, 0x0018001D, 0x00187802, 0x00192C15,
    0x0019A804, 0x0019C001, 0x001B5001, 0x001B580F, 0x001B9C07,
    0x001BF402, 0x001C000E, 0x001C3C01, 0x001C4401, 0x001CC01B,
    0x001E980B, 0x001FAC09, 0x001FD804, 0x001FF403, 0x00205804,
    0x00206C09, 0x00209403, 0x0020A405, 0x0020C00F, 0x00216403,
    0x00217801, 0x00234C31, 0x0024E803, 0x0024F812, 0x00254407,
    0x00258804, 0x0025C001, 0x00260403, 0x0026F001, 0x0026F807,
    0x00271C02, 0x00272C03, 0x00275C01, 0x00278802, 0x0027C802,
    0x0027E802, 0x0027F402, 0x00280403, 0x0028F001, 0x0028F805,
    0x00291C02, 0x00292C03, 0x00294401, 0x0029C002, 0x0029D402,
    0x002A0403, 0x002AF001, 0x002AF808, 0x002B1C03, 0x002B2C03,
    0x002B8802, 0x002BC002, 0x002BE806, 0x002C0403, 0x002CF001,
    0x002CF807, 0x002D1C02, 0x002D2C03, 0x002D5403, 0x002D8802,
    0x002DC001, 0x002E0801, 0x002EF805, 0x002F1803, 0x002F2804,
    0x002F5C01, 0x002FCC08, 0x00300005, 0x0030F807, 0x00311803,
    0x00312804, 0x00315402, 0x00318802, 0x0031DC01, 0x0031FC01,
    0x00320404, 0x0032F001, 0x0032F807, 0x00331803, 0x00332804,
    0x00335402, 0x00338802, 0x00340004, 0x0034EC02, 0x0034F807,
    0x00351803, 0x00352804, 0x00353C01, 0x00355C01, 0x00358802,
    0x0035E401, 0x00360403, 0x00372801, 0x00373C06, 0x00375801,
    0x00376008, 0x0037C803, 0x0038C401, 0x0038D007, 0x0038FC01,
    0x00391C09, 0x00396802, 0x003AC401, 0x003AD009, 0x003B2006,
    0x003C041F, 0x003CD00C, 0x003DC417, 0x003E340B, 0x003E6424,
    0x003EF80F, 0x003F380D, 0x0040AC14, 0x00412806, 0x00415804,
    0x00417803, 0x00418803, 0x00419C07, 0x0041C404, 0x0042080C,
    0x00423C01, 0x00426806, 0x0043EC01, 0x004D740C, 0x004E400A,
    0x00500001, 0x0059B402, 0x005A0001, 0x005A6C02, 0x005BAC03,
    0x005C4803, 0x005CC805, 0x005D4802, 0x005DC802, 0x005ED023,
    0x005F6004, 0x005F7401, 0x0060000F, 0x00621402, 0x0062A401,
    0x0064800C, 0x0064C00C, 0x00650001, 0x00651002, 0x00677822,
    0x00685C05, 0x00687802, 0x0069540A, 0x0069801D, 0x0069FC01,
    0x006A8007, 0x006AA006, 0x006AC011, 0x006C0005, 0x006CD011,
    0x006D6823, 0x006E0003, 0x006E840D, 0x006F980E, 0x006FF004,
    0x00164437, 0x0017CC02, 0x00180020, 0x00192C15, 0x0019A804,
    0x0019C001, 0x001B5001, 0x001B580F, 0x001B9C07, 0x001BF402,
    0x001C000E, 0x001C3C01, 0x001C4401, 0x001CC01B, 0x001E980B,
    0x001FAC09, 0x001FD804, 0x001FF403, 0x00205804, 0x00206C09,
    0x00209403, 0x0020A405, 0x0020C00F, 0x00216403, 0x00217801,
    0x00222001, 0x00224002, 0x00225C09, 0x0023283A, 0x0024E803,
    0x0024F812, 0x00254407, 0x00258804, 0x0025C001, 0x00260403,
    0x0026F001, 0x0026F807, 0x00271C02, 0x00272C03, 0x00275C01,
    0x00278802, 0x0027C802, 0x0027E802, 0x0027F402, 0x00280403,
    0x0028F001, 0x0028F805, 0x00291C02, 0x00292C03, 0x00294401,
    0x0029C002, 0x0029D402, 0x002A0403, 0x002AF001, 0x002AF808,
    0x002B1C03, 0x002B2C03, 0x002B8802, 0x002BC002, 0x002BE806,
    0x002C0403, 0x002CF001, 0x002CF807, 0x002D1C02, 0x002D2C03,
    0x002D5403, 0x002D8802, 0x002DC001, 0x002E0801, 0x002EF805,
    0x002F1803, 0x002F2804, 0x002F5C01, 0x002FCC08, 0x00300005,
    0x0030F001, 0x0030F807, 0x00311803, 0x00312804, 0x00315402,
    0x00318802, 0x0031DC01, 0x0031FC01, 0x00320404, 0x0032F001,
    0x0032F807, 0x00331803, 0x00332804, 0x00335402, 0x00338802,
    0x0033CC01, 0x00340004, 0x0034EC02, 0x0034F807, 0x00351803,
    0x00352804, 0x00353C01, 0x00355C01, 0x00358802, 0x0035E401,
    0x00360403, 0x00372801, 0x00373C06, 0x00375801, 0x00376008,
    0x0037C803, 0x0038C401, 0x0038D007, 0x0038FC01, 0x00391C09,
    0x00396802, 0x003AC401, 0x003AD009, 0x003B2007, 0x003C041F,
    0x003CD00C, 0x003DC417, 0x003E340B, 0x003E6424, 0x003EF80F,
    0x003F380D, 0x0040AC14, 0x00412806, 0x00415804, 0x00417803,
    0x00418803, 0x00419C07, 0x0041C404, 0x0042080C, 0x00423C01,
    0x00426806, 0x0043EC01, 0x004D740C, 0x004E400A, 0x00500001,
    0x0059B402, 0x005A0001, 0x005A6C02, 0x005BAC03, 0x005C4804,
    0x005CC805, 0x005D4802, 0x005DC802, 0x005ED023, 0x005F6004,
    0x005F7401, 0x00600010, 0x00621402, 0x0062A401, 0x0064800C,
    0x0064C00C, 0x00650001, 0x00651002, 0x00677822, 0x00685C05,
    0x00687802, 0x0069540A, 0x0069801D, 0x0069FC01, 0x006A8007,
    0x006AA006, 0x006AC02E, 0x006B800C, 0x006C0005, 0x006CD011,
    0x006D3802, 0x006D6829, 0x006E840D, 0x006F980E, 0x006FF004,
    0x00709014, 0x0070EC05, 0x0071F802, 0x00730008, 0x00734019,
    0x0073B401, 0x0073D001, 0x0073DC03, 0x0077003A, 0x0077EC05,
    0x007EF401, 0x007EFC03, 0x007F3403, 0x007F7403, 0x007FB403,
    0x007FF402, 0x00800065, 0x0081980A, 0x0081E805, 0x00822805,
    0x00828020, 0x00834021, 0x00840002, 0x00840C04, 0x00842002,
    0x00845001, 0x00845803, 0x00847806, 0x00849401, 0x00849C01,
    0x0084A401, 0x0084B801, 0x0084E802, 0x00850005, 0x00852804,
    0x00853C01, 0x00862802, 0x00864297, 0x0091000B, 0x0092704E,
    0x00940276, 0x009E53E0, 0x00ADD820, 0x00AE5C69, 0x00B39406,
    0x00B3BC03, 0x00B3E404, 0x00B3F802, 0x00B5C001, 0x00B5FC01,
    0x00B7804F, 0x00B8C023, 0x00BA001A, 0x00BA6C59, 0x00BC00D6,
    0x00BFC00C, 0x00C00005, 0x00C02019, 0x00C0A807, 0x00C0D802,
    0x00C0F403, 0x00C26404, 0x00C28001, 0x00C3EC01, 0x00C64002,
    0x00C6580A, 0x00C70024, 0x00C8001F, 0x00C8A81E, 0x00C94001,
    0x00C98020, 0x00CA2827, 0x00CB0140, 0x01370040, 0x02924037,
    0x0293F802, 0x02983403, 0x0299BC10, 0x029A7802, 0x029BC008,
    0x029C0017, 0x029C8002, 0x029E2402, 0x02A00801, 0x02A01801,
    0x02A02C01, 0x02A08C0A, 0x02A0D804, 0x02A1D004, 0x02A20002,
    0x02A2D012, 0x02A33802, 0x02A38012, 0x02A3E003, 0x02A3F001,
    0x02A3FC01, 0x02A4980A, 0x02A51C0D, 0x02A57C01, 0x02A60004,
    0x02A6CC1B, 0x02A77802, 0x02A79401, 0x02A8A40E, 0x02A90C01,
    0x02A93002, 0x02A97004, 0x02A9DC03, 0x02A9EC03, 0x02AAC001,
    0x02AAC803, 0x02AADC02, 0x02AAF802, 0x02AB0401, 0x02AB7802,
    0x02ABAC07, 0x02ABD402, 0x02AD6C01, 0x02ADA802, 0x02AF8C0B,
    0x03600001, 0x036DFC02, 0x036FFC02, 0x037FFC01, 0x03EC7801,
    0x0073B401, 0x0073D001, 0x0073DC03, 0x00770040, 0x007EF401,
    0x007EFC03, 0x007F3403, 0x007F7403, 0x007FB403, 0x007FF402,
    0x00800065, 0x0081980A, 0x0081E805, 0x00822805, 0x00828022,
    0x00834021, 0x00840002, 0x00840C04, 0x00842002, 0x00845001,
    0x00845803, 0x00847806, 0x00849401, 0x00849C01, 0x0084A401,
    0x0084B801, 0x0084E802, 0x00850005, 0x00852804, 0x00853C01,
    0x00862802, 0x0086429A, 0x0091000B, 0x0092704E, 0x00940276,
    0x009E53E0, 0x00ADD88A, 0x00B39406, 0x00B3BC03, 0x00B3E404,
    0x00B3F802, 0x00B5C001, 0x00B5FC01, 0x00B7804F, 0x00B8C02E,
    0x00BA001A, 0x00BA6C59, 0x00BC00D6, 0x00BFC015, 0x00C02019,
    0x00C0A807, 0x00C0D802, 0x00C0F403, 0x00C26404, 0x00C28001,
    0x00C3EC01, 0x00C64002, 0x00C6580A, 0x00C70026, 0x00C7BC01,
    0x00C8001F, 0x00C8A81E, 0x00C94001, 0x00C98020, 0x00CA2827,
    0x00CB0140, 0x01370040, 0x02924037, 0x0293F802, 0x02983403,
    0x0299BC10, 0x029A7802, 0x029BC008, 0x029C0017, 0x029C8002,
    0x029E2402, 0x02A00801, 0x02A01801, 0x02A02C01, 0x02A08C0A,
    0x02A0D804, 0x02A1D004, 0x02A20002, 0x02A2D012, 0x02A33802,
    0x02A38012, 0x02A3E003, 0x02A3F001, 0x02A3FC01, 0x02A4980A,
    0x02A51C0D, 0x02A57C01, 0x02A60004, 0x02A6CC1B, 0x02A77802,
    0x02A79401, 0x02A8A40E, 0x02A90C01, 0x02A93002, 0x02A97004,
    0x02A9DC03, 0x02A9EC03, 0x02AAC001, 0x02AAC803, 0x02AADC02,
    0x02AAF802, 0x02AB0401, 0x02AB7802, 0x02ABAC07, 0x02ABD402,
    0x02AD6C01, 0x02ADA802, 0x02AF8C0B, 0x03600001, 0x036DFC02,
    0x036FFC02, 0x037FFC01, 0x03EC7801, 0x03ECA401, 0x03EEC821,
    0x03ECA401, 0x03EEC810, 0x03F4F802, 0x03F7F002, 0x03F8001A,
    0x03F88033, 0x03F95013, 0x03F9A004, 0x03FBFC01, 0x03FC040F,
    0x03FC6807, 0x03FCEC06, 0x03FD6C0B, 0x03FF8007, 0x03FFA007,
    0x03FFE405, 0x04040003, 0x0404DC09, 0x0405E411, 0x04063003,
    0x0406400D, 0x04068001, 0x0407402E, 0x040B8001, 0x040DD805,
    0x040E7C01, 0x040F4001, 0x0415BC01, 0x04215C01, 0x0421DC02,
    0x04247C01, 0x0424FC01, 0x04280403, 0x04281402, 0x04283004,
    0x0428E003, 0x0428FC01, 0x04294009, 0x0429FC01, 0x042B2001,
    0x042B9402, 0x042BC007, 0x042CE407, 0x042E6404, 0x04349004,
    0x043AAC03, 0x043D180B, 0x043D5405, 0x04400003, 0x0440E016,
    0x0441FC04, 0x0442C012, 0x04433401, 0x04440003, 0x04449C0E,
    0x04450004, 0x04451402, 0x0445CC03, 0x04460003, 0x0446CC0E,
    0x0447140B, 0x04476C01, 0x04477403, 0x0448B013, 0x044AA401,
    0x044B7C0C, 0x044C0004, 0x044CEC02, 0x044CF807, 0x044D1C02,
    0x044D2C03, 0x044D5C01, 0x044D8802, 0x044D9807, 0x044DC005,
    0x0450D412, 0x04512C05, 0x04516802, 0x04517402, 0x0452C014,
    0x04531801, 0x0456BC07, 0x0456E020, 0x04577002, 0x0458C014,
    0x0459800D, 0x045AAC0D, 0x045C740F, 0x045CF004, 0x0460B010,
    0x0464C006, 0x0464DC02, 0x0464EC04, 0x04650001, 0x04650805,
    0x04674407, 0x04676807, 0x04678801, 0x04679001, 0x0468040A,
    0x0468CC07, 0x0468EC0D, 0x0469440B, 0x046A2813, 0x046A7805,
    0x03F4F812, 0x03F64002, 0x03F72008, 0x03F7F01E, 0x03F88033,
    0x03F95013, 0x03F9A004, 0x03FBFC01, 0x03FC040F, 0x03FC6807,
    0x03FCEC06, 0x03FD6C0B, 0x03FF8007, 0x03FFA007, 0x03FFE405,
    0x04040003, 0x0404DC09, 0x0405E411, 0x04063003, 0x0406400D,
    0x04068001, 0x0407402E, 0x040B8001, 0x040DD805, 0x040E7C01,
    0x040F4001, 0x0415BC01, 0x04215C01, 0x0421DC02, 0x04247C01,
    0x0424FC01, 0x04280403, 0x04281402, 0x04283004, 0x0428E003,
    0x0428FC01, 0x04294009, 0x0429FC01, 0x042B2001, 0x042B9402,
    0x042BC007, 0x042CE407, 0x042E6404, 0x04349004, 0x0435A406,
    0x04363802, 0x043AAC03, 0x043B4009, 0x043BE806, 0x043D180B,
    0x043D5405, 0x043E0808, 0x04400003, 0x0440E016, 0x0441C001,
    0x0441CC02, 0x0441FC04, 0x0442C013, 0x04433401, 0x04440003,
    0x04449C0E, 0x04450004, 0x04451402, 0x0445CC03, 0x04460003,
    0x0446CC0E, 0x0447140B, 0x04476C01, 0x04477403, 0x0448B013,
    0x04490401, 0x044AA401, 0x044B7C0C, 0x044C0004, 0x044CEC02,
    0x044CF807, 0x044D1C02, 0x044D2C03, 0x044D5C01, 0x044D8802,
    0x044D9807, 0x044DC005, 0x044EE009, 0x044F0801, 0x044F1401,
    0x044F1C04, 0x044F3005, 0x044F4801, 0x044F5002, 0x044F5C02,
    0x044F8402, 0x0450D412, 0x04512C05, 0x04516802, 0x04517402,
    0x0452C014, 0x04531801, 0x0456BC07, 0x0456E020, 0x04577002,
    0x0458C014, 0x0459800D, 0x045AAC0D, 0x045AE401, 0x045C740F,
    0x045CF004, 0x0460B010, 0x0464C006, 0x0464DC02, 0x0464EC04,
    0x04650001, 0x04650805, 0x04674407, 0x04676807, 0x04678801,
    0x04679001, 0x0468040A, 0x0468CC07, 0x0468EC0D, 0x0469440B,
    0x046A2813, 0x046A7805, 0x046C000A, 0x046D8008, 0x046F8401,
    0x0470BC08, 0x0470E008, 0x04710405, 0x0471C002, 0x04724816,
    0x0472A40E, 0x0474C406, 0x0474E801, 0x0474F002, 0x0474FC07,
    0x04751C01, 0x04762805, 0x04764002, 0x04764C05, 0x047BCC06,
    0x047C0002, 0x047C0C01, 0x047CD007, 0x047CF812, 0x047D6801,
    0x047F541D, 0x047FFC01, 0x0491C005, 0x04D0C009, 0x05A9B802,
    0x05ABC006, 0x05ACC010, 0x05AD1002, 0x05BA5C04, 0x05BD3C01,
    0x05BD4437, 0x05BE3C04, 0x05BF8801, 0x05BF9001, 0x05BFC002,
    0x06F27008, 0x074000F6, 0x07440027, 0x0744A4C0, 0x07480046,
    0x074C0057, 0x075B0401, 0x075B6C01, 0x075BEC01, 0x075C5401,
    0x075CD401, 0x075D3C01, 0x075DBC01, 0x075E2401, 0x075EA401,
    0x075F0C01, 0x0760028C, 0x076A6C05, 0x076A840F, 0x07800007,
    0x07802011, 0x07806C07, 0x07808C02, 0x07809805, 0x0784C007,
    0x07853C01, 0x078BB004, 0x078BFC01, 0x07A34007, 0x07A51007,
    0x07A57802, 0x07B2B001, 0x07B2C001, 0x07B4B801, 0x07BBC002,
    0x07C0002C, 0x07C0C064, 0x07C2800F, 0x07C2C40F, 0x07C3040F,
    0x07C34425, 0x07C434A1, 0x07C7981D, 0x07C8402C, 0x07C90009,
    0x07C94002, 0x07C98006, 0x07CC03D8, 0x07DB800D, 0x07DBC00D,
    0x07DC0074, 0x07DE0059, 0x07DF800C, 0x07E0000C, 0x07E04038,
    0x07E1400A, 0x07E18028, 0x07E2401E, 0x07E2C002, 0x07E40079,
    0x07E5E852, 0x07E73487, 0x07E9800E, 0x07E9C005, 0x07E9E003,
    0x07EA0007, 0x07EA4019, 0x07EAC007, 0x07EB0003, 0x07EB4007,
    0x07EC0093, 0x07EE5037, 0x38000401, 0x38008060, 0x380400F0,
    0x047F541D, 0x047FFC01, 0x0491C005, 0x04BFC402, 0x04D0C011,
    0x04D11C0F, 0x05847812, 0x05A9B802, 0x05ABC006, 0x05ACC010,
    0x05AD1002, 0x05B5B403, 0x05BA5C04, 0x05BD3C01, 0x05BD4437,
    0x05BE3C04, 0x05BF8801, 0x05BF9001, 0x05BFC002, 0x06F27008,
    0x073000F0, 0x0733E803, 0x073401B4, 0x073AE817, 0x073B8011,
    0x073C002E, 0x073CC017, 0x073D4074, 0x074000F6, 0x07440027,
    0x0744A4C2, 0x07480046, 0x074C0057, 0x075B0401, 0x075B6C01,
    0x075BEC01, 0x075C5401, 0x075CD401, 0x075D3C01, 0x075DBC01,
    0x075E2401, 0x075EA401, 0x075F0C01, 0x0760028C, 0x076A6C05,
    0x076A840F, 0x07800007, 0x07802011, 0x07806C07, 0x07808C02,
    0x07809805, 0x07823C01, 0x0784C007, 0x07853C01, 0x078AB801,
    0x078BB004, 0x078BFC01, 0x0793B004, 0x0797B802, 0x0797FC01,
    0x079B8C01, 0x079B9801, 0x079BB802, 0x079BD401, 0x07A34007,
    0x07A51007, 0x07A57802, 0x07B2B001, 0x07B2C001, 0x07B4B801,
    0x07BBC002, 0x07C0002C, 0x07C0C064, 0x07C2800F, 0x07C2C40F,
    0x07C3040F, 0x07C34425, 0x07C434A1, 0x07C7981D, 0x07C8402C,
    0x07C90009, 0x07C94002, 0x07C98006, 0x07CC03D9, 0x07DB7011,
    0x07DBC00D, 0x07DC00DA, 0x07DF800C, 0x07DFC001, 0x07E0000C,
    0x07E04038, 0x07E1400A, 0x07E18028, 0x07E2401E, 0x07E2C00C,
    0x07E30002, 0x07E34009, 0x07E40158, 0x07E9800E, 0x07E9C00D,
    0x07EA000B, 0x07EA3839, 0x07EB2001, 0x07EB3410, 0x07EB7C0C,
    0x07EBBC0A, 0x07EC0093, 0x07EE505C, 0x07EFE801, 0x38000401,
    0x38008060, 0x380400F0,
  };
  static const unsigned int aAscii[4] = {
    0xFFFFFFFF, 0xFC00FFFF, 0xF8000001, 0xF8000001,
  };

  if( (unsigned int)c<128 ){
    return ( (aAscii[c >> 5] & ((unsigned int)1 << (c & 0x001F)))==0 );
176
177
178
179
180
181
182
183

184
185
186
187
188
189
190
187
188
189
190
191
192
193

194
195
196
197
198
199
200
201







-
+









/*
** If the argument is a codepoint corresponding to a lowercase letter
** in the ASCII range with a diacritic added, return the codepoint
** of the ASCII letter only. For example, if passed 235 - "LATIN
** SMALL LETTER E WITH DIAERESIS" - return 65 ("LATIN SMALL LETTER
** E"). The resuls of passing a codepoint that corresponds to an
** E"). The results of passing a codepoint that corresponds to an
** uppercase letter are undefined.
*/
static int unicode_remove_diacritic(int c, int bComplex){
  static const unsigned short aDia[] = {
        0,  1797,  1848,  1859,  1891,  1928,  1940,  1995,
     2024,  2040,  2060,  2110,  2168,  2206,  2264,  2286,
     2344,  2383,  2472,  2488,  2516,  2596,  2668,  2732,
238
239
240
241
242
243
244
245

246
247
248
249
250
251
252
253
249
250
251
252
253
254
255

256

257
258
259
260
261
262
263







-
+
-







      iLo = iTest+1;
    }else{
      iHi = iTest-1;
    }
  }
  assert( key>=aDia[iRes] );
  if( bComplex==0 && (aChar[iRes] & 0x80) ) return c;
  return (c > (aDia[iRes]>>3) + (aDia[iRes]&0x07)) ? c :
  return (c > (aDia[iRes]>>3) + (aDia[iRes]&0x07)) ? c : ((int)aChar[iRes] & 0x7F);
                                                     ((int)aChar[iRes] & 0x7F);
}


/*
** Return true if the argument interpreted as a unicode codepoint
** is a diacritical modifier character.
*/
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
381
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
381
382
383
384
385
386

387
388
389
390
391
392
393







-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+



-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+


-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-







  ** http://www.unicode.org for details.
  */
  static const struct TableEntry {
    unsigned short iCode;
    unsigned char flags;
    unsigned char nRange;
  } aEntry[] = {
    {65, 14, 26},          {181, 66, 1},          {192, 14, 23},
    {216, 14, 7},          {256, 1, 48},          {306, 1, 6},
    {313, 1, 16},          {330, 1, 46},          {376, 156, 1},
    {377, 1, 6},           {383, 144, 1},         {385, 52, 1},
    {386, 1, 4},           {390, 46, 1},          {391, 0, 1},
    {393, 44, 2},          {395, 0, 1},           {398, 34, 1},
    {399, 40, 1},          {400, 42, 1},          {401, 0, 1},
    {403, 44, 1},          {404, 48, 1},          {406, 54, 1},
    {407, 50, 1},          {408, 0, 1},           {412, 54, 1},
    {413, 56, 1},          {415, 58, 1},          {416, 1, 6},
    {422, 62, 1},          {423, 0, 1},           {425, 62, 1},
    {428, 0, 1},           {430, 62, 1},          {431, 0, 1},
    {433, 60, 2},          {435, 1, 4},           {439, 64, 1},
    {65, 16, 26},          {181, 70, 1},          {192, 16, 23},
    {216, 16, 7},          {256, 1, 48},          {306, 1, 6},
    {313, 1, 16},          {330, 1, 46},          {376, 168, 1},
    {377, 1, 6},           {383, 156, 1},         {385, 56, 1},
    {386, 1, 4},           {390, 50, 1},          {391, 0, 1},
    {393, 48, 2},          {395, 0, 1},           {398, 38, 1},
    {399, 44, 1},          {400, 46, 1},          {401, 0, 1},
    {403, 48, 1},          {404, 52, 1},          {406, 58, 1},
    {407, 54, 1},          {408, 0, 1},           {412, 58, 1},
    {413, 60, 1},          {415, 62, 1},          {416, 1, 6},
    {422, 66, 1},          {423, 0, 1},           {425, 66, 1},
    {428, 0, 1},           {430, 66, 1},          {431, 0, 1},
    {433, 64, 2},          {435, 1, 4},           {439, 68, 1},
    {440, 0, 1},           {444, 0, 1},           {452, 2, 1},
    {453, 0, 1},           {455, 2, 1},           {456, 0, 1},
    {458, 2, 1},           {459, 1, 18},          {478, 1, 18},
    {497, 2, 1},           {498, 1, 4},           {502, 162, 1},
    {503, 174, 1},         {504, 1, 40},          {544, 150, 1},
    {546, 1, 18},          {570, 74, 1},          {571, 0, 1},
    {573, 148, 1},         {574, 72, 1},          {577, 0, 1},
    {579, 146, 1},         {580, 30, 1},          {581, 32, 1},
    {582, 1, 10},          {837, 38, 1},          {880, 1, 4},
    {886, 0, 1},           {895, 38, 1},          {902, 20, 1},
    {904, 18, 3},          {908, 28, 1},          {910, 26, 2},
    {913, 14, 17},         {931, 14, 9},          {962, 0, 1},
    {975, 4, 1},           {976, 180, 1},         {977, 182, 1},
    {981, 186, 1},         {982, 184, 1},         {984, 1, 24},
    {1008, 176, 1},        {1009, 178, 1},        {1012, 170, 1},
    {1013, 168, 1},        {1015, 0, 1},          {1017, 192, 1},
    {1018, 0, 1},          {1021, 150, 3},        {1024, 36, 16},
    {1040, 14, 32},        {1120, 1, 34},         {1162, 1, 54},
    {497, 2, 1},           {498, 1, 4},           {502, 174, 1},
    {503, 186, 1},         {504, 1, 40},          {544, 162, 1},
    {546, 1, 18},          {570, 78, 1},          {571, 0, 1},
    {573, 160, 1},         {574, 76, 1},          {577, 0, 1},
    {579, 158, 1},         {580, 34, 1},          {581, 36, 1},
    {582, 1, 10},          {837, 42, 1},          {880, 1, 4},
    {886, 0, 1},           {895, 42, 1},          {902, 22, 1},
    {904, 20, 3},          {908, 32, 1},          {910, 30, 2},
    {913, 16, 17},         {931, 16, 9},          {962, 0, 1},
    {975, 4, 1},           {976, 192, 1},         {977, 194, 1},
    {981, 198, 1},         {982, 196, 1},         {984, 1, 24},
    {1008, 188, 1},        {1009, 190, 1},        {1012, 182, 1},
    {1013, 180, 1},        {1015, 0, 1},          {1017, 204, 1},
    {1018, 0, 1},          {1021, 162, 3},        {1024, 40, 16},
    {1040, 16, 32},        {1120, 1, 34},         {1162, 1, 54},
    {1216, 6, 1},          {1217, 1, 14},         {1232, 1, 96},
    {1329, 24, 38},        {4256, 70, 38},        {4295, 70, 1},
    {4301, 70, 1},         {5112, 190, 6},        {7296, 126, 1},
    {7297, 128, 1},        {7298, 130, 1},        {7299, 134, 2},
    {7301, 132, 1},        {7302, 136, 1},        {7303, 138, 1},
    {7304, 100, 1},        {7312, 142, 43},       {7357, 142, 3},
    {7680, 1, 150},        {7835, 172, 1},        {7838, 120, 1},
    {7840, 1, 96},         {7944, 190, 8},        {7960, 190, 6},
    {7976, 190, 8},        {7992, 190, 8},        {8008, 190, 6},
    {8025, 191, 8},        {8040, 190, 8},        {8072, 190, 8},
    {8088, 190, 8},        {8104, 190, 8},        {8120, 190, 2},
    {8122, 166, 2},        {8124, 188, 1},        {8126, 124, 1},
    {8136, 164, 4},        {8140, 188, 1},        {8152, 190, 2},
    {8154, 160, 2},        {8168, 190, 2},        {8170, 158, 2},
    {8172, 192, 1},        {8184, 152, 2},        {8186, 154, 2},
    {8188, 188, 1},        {8486, 122, 1},        {8490, 116, 1},
    {8491, 118, 1},        {8498, 12, 1},         {8544, 8, 16},
    {8579, 0, 1},          {9398, 10, 26},        {11264, 24, 47},
    {11360, 0, 1},         {11362, 112, 1},       {11363, 140, 1},
    {11364, 114, 1},       {11367, 1, 6},         {11373, 108, 1},
    {11374, 110, 1},       {11375, 104, 1},       {11376, 106, 1},
    {11378, 0, 1},         {11381, 0, 1},         {11390, 102, 2},
    {1329, 28, 38},        {4256, 74, 38},        {4295, 74, 1},
    {4301, 74, 1},         {5112, 202, 6},        {7296, 138, 1},
    {7297, 140, 1},        {7298, 142, 1},        {7299, 146, 2},
    {7301, 144, 1},        {7302, 148, 1},        {7303, 150, 1},
    {7304, 108, 1},        {7305, 0, 1},          {7312, 154, 43},
    {7357, 154, 3},        {7680, 1, 150},        {7835, 184, 1},
    {7838, 128, 1},        {7840, 1, 96},         {7944, 202, 8},
    {7960, 202, 6},        {7976, 202, 8},        {7992, 202, 8},
    {8008, 202, 6},        {8025, 203, 8},        {8040, 202, 8},
    {8072, 202, 8},        {8088, 202, 8},        {8104, 202, 8},
    {8120, 202, 2},        {8122, 178, 2},        {8124, 200, 1},
    {8126, 136, 1},        {8136, 176, 4},        {8140, 200, 1},
    {8147, 132, 1},        {8152, 202, 2},        {8154, 172, 2},
    {8163, 134, 1},        {8168, 202, 2},        {8170, 170, 2},
    {8172, 204, 1},        {8184, 164, 2},        {8186, 166, 2},
    {8188, 200, 1},        {8486, 130, 1},        {8490, 124, 1},
    {8491, 126, 1},        {8498, 14, 1},         {8544, 8, 16},
    {8579, 0, 1},          {9398, 10, 26},        {11264, 28, 48},
    {11360, 0, 1},         {11362, 120, 1},       {11363, 152, 1},
    {11364, 122, 1},       {11367, 1, 6},         {11373, 116, 1},
    {11374, 118, 1},       {11375, 112, 1},       {11376, 114, 1},
    {11378, 0, 1},         {11381, 0, 1},         {11390, 110, 2},
    {11392, 1, 100},       {11499, 1, 4},         {11506, 0, 1},
    {42560, 1, 46},        {42624, 1, 28},        {42786, 1, 14},
    {42802, 1, 62},        {42873, 1, 4},         {42877, 98, 1},
    {42878, 1, 10},        {42891, 0, 1},         {42893, 88, 1},
    {42896, 1, 4},         {42902, 1, 20},        {42922, 80, 1},
    {42923, 76, 1},        {42924, 78, 1},        {42925, 84, 1},
    {42926, 80, 1},        {42928, 92, 1},        {42929, 86, 1},
    {42930, 90, 1},        {42931, 68, 1},        {42932, 1, 12},
    {42946, 0, 1},         {42948, 178, 1},       {42949, 82, 1},
    {42950, 96, 1},        {42951, 1, 4},         {42997, 0, 1},
    {43888, 94, 80},       {65313, 14, 26},
    {42802, 1, 62},        {42873, 1, 4},         {42877, 106, 1},
    {42878, 1, 10},        {42891, 0, 1},         {42893, 96, 1},
    {42896, 1, 4},         {42902, 1, 20},        {42922, 88, 1},
    {42923, 84, 1},        {42924, 86, 1},        {42925, 92, 1},
    {42926, 88, 1},        {42928, 100, 1},       {42929, 94, 1},
    {42930, 98, 1},        {42931, 72, 1},        {42932, 1, 16},
    {42948, 190, 1},       {42949, 90, 1},        {42950, 104, 1},
    {42951, 1, 4},         {42955, 82, 1},        {42956, 1, 16},
    {42972, 80, 1},        {42997, 0, 1},         {43888, 102, 80},
    {64261, 0, 1},         {65313, 16, 26},
  };
  static const unsigned short aiOff[] = {
   1,     2,     8,     15,    16,    26,    28,    32,
   34,    37,    38,    40,    48,    63,    64,    69,
   71,    79,    80,    116,   202,   203,   205,   206,
   207,   209,   210,   211,   213,   214,   217,   218,
   219,   775,   928,   7264,  10792, 10795, 23217, 23221,
   23228, 23229, 23231, 23254, 23256, 23275, 23278, 26672,
   30152, 30204, 35267, 54721, 54753, 54754, 54756, 54787,
   54793, 54809, 57153, 57274, 57921, 58019, 58363, 59314,
   59315, 59324, 59325, 59326, 59332, 59356, 61722, 62528,
   65268, 65341, 65373, 65406, 65408, 65410, 65415, 65424,
   65436, 65439, 65450, 65462, 65472, 65476, 65478, 65480,
   65482, 65488, 65506, 65511, 65514, 65521, 65527, 65528,
   1,     2,     8,     15,    16,    26,    27,    28,
   32,    34,    37,    38,    39,    40,    48,    63,
   64,    69,    71,    79,    80,    116,   202,   203,
   205,   206,   207,   209,   210,   211,   213,   214,
   217,   218,   219,   775,   928,   7264,  10792, 10795,
   22975, 23193, 23217, 23221, 23228, 23229, 23231, 23254,
   23256, 23275, 23278, 26672, 30152, 30204, 35267, 54721,
   54753, 54754, 54756, 54787, 54793, 54809, 57153, 57274,
   57921, 58019, 58301, 58317, 58363, 59314, 59315, 59324,
   59325, 59326, 59332, 59356, 61722, 62528, 65268, 65341,
   65373, 65406, 65408, 65410, 65415, 65424, 65436, 65439,
   65450, 65462, 65472, 65476, 65478, 65480, 65482, 65488,
   65506, 65511, 65514, 65521, 65527, 65528, 65529,
   65529,
  };

  int ret = c;

  assert( sizeof(unsigned short)==2 && sizeof(unsigned char)==1 );

  if( c<128 ){
411
412
413
414
415
416
417












418
419
420



421
422
423
424
425
426



427
428
429
430
431
432
433
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







+
+
+
+
+
+
+
+
+
+
+
+



+
+
+






+
+
+







  }

  else if( c>=66560 && c<66600 ){
    ret = c + 40;
  }
  else if( c>=66736 && c<66772 ){
    ret = c + 40;
  }
  else if( c>=66928 && c<66939 ){
    ret = c + 39;
  }
  else if( c>=66940 && c<66955 ){
    ret = c + 39;
  }
  else if( c>=66956 && c<66963 ){
    ret = c + 39;
  }
  else if( c>=66964 && c<66966 ){
    ret = c + 39;
  }
  else if( c>=68736 && c<68787 ){
    ret = c + 64;
  }
  else if( c>=68944 && c<68966 ){
    ret = c + 32;
  }
  else if( c>=71840 && c<71872 ){
    ret = c + 32;
  }
  else if( c>=93760 && c<93792 ){
    ret = c + 32;
  }
  else if( c>=93856 && c<93881 ){
    ret = c + 27;
  }
  else if( c>=125184 && c<125218 ){
    ret = c + 34;
  }

  return ret;
}
Changes to src/unversioned.c.
145
146
147
148
149
150
151


152
153
154
155
156
157
158
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160







+
+







    db_bind_int(&ins, ":encoding", 1);
    db_bind_blob(&ins, ":content", &compressed);
  }else{
    db_bind_int(&ins, ":encoding", 0);
    db_bind_blob(&ins, ":content", pContent);
  }
  db_step(&ins);
  admin_log("Wrote unversioned file \"%w\" with hash %!S",
            zUVFile, blob_str(&hash));
  blob_reset(&compressed);
  blob_reset(&hash);
  db_finalize(&ins);
  db_unset("uv-hash", 0);
}


216
217
218
219
220
221
222
223
224


225
226
227
228
229
230
231
218
219
220
221
222
223
224


225
226
227
228
229
230
231
232
233







-
-
+
+







    if( fossil_isspace(zName[0]) ) return 1;
    zName++;
  }
  return 0;
}

/*
** COMMAND: uv#
** COMMAND: unversioned
** COMMAND: uv#                           abbrv-subcom
** COMMAND: unversioned                   abbrv-subcom
**
** Usage: %fossil unversioned SUBCOMMAND ARGS...
**    or: %fossil uv SUBCOMMAND ARGS..
**
** Unversioned files (UV-files) are artifacts that are synced and are available
** for download but which do not preserve history.  Only the most recent version
** of each UV-file is retained.  Changes to an UV-file are permanent and cannot
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
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







+
+




















+



















+







**                           the name to be different in the repository versus
**                           what appears on disk, but it only allows adding
**                           a single file at a time.
**
**    cat FILE ...           Concatenate the content of FILEs to stdout.
**
**    edit FILE              Bring up FILE in a text editor for modification.
**                           Options:
**                             --editor NAME     Name of the text editor to use
**
**    export FILE OUTPUT     Write the content of FILE into OUTPUT on disk
**
**    list | ls              Show all unversioned files held in the local
**                           repository.
**
**                           Options:
**                              --glob PATTERN   Show only files that match
**                              --like PATTERN   Show only files that match
**                              -l               Show additional details for
**                                               files that match. Implied
**                                               when 'list' is used.
**
**    revert ?URL?           Restore the state of all unversioned files in the
**                           local repository to match the remote repository
**                           URL.
**
**                           Options:
**                              -v|--verbose     Extra diagnostic output
**                              -n|--dry-run     Show what would have happened
**                              --proxy PROXY    Use the specified HTTP proxy
**
**    remove|rm|delete FILE ...
**                           Remove unversioned files from the local repository.
**                           Changes are not pushed to other repositories until
**                           the next sync.
**
**                           Options:
**                              --glob PATTERN   Remove files that match
**                              --like PATTERN   Remove files that match
**
**    sync ?URL?             Synchronize the state of all unversioned files with
**                           the remote repository URL.  The most recent version
**                           of each file is propagated to all repositories and
**                           all prior versions are permanently forgotten.
**                           The remote account requires the 'y' capability.
**
**                           Options:
**                              -v|--verbose     Extra diagnostic output
**                              -n|--dry-run     Show what would have happened
**                              --proxy PROXY    Use the specified HTTP proxy
**
**    touch FILE ...         Update the TIMESTAMP on all of the listed files
**
** Options:
**   --mtime TIMESTAMP       Use TIMESTAMP instead of "now" for the "add",
**                           "edit", "remove", and "touch" subcommands.
**   -R|--repository REPO    Use REPO as the repository
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
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







+
+
+
















+
+



+
+
+
+







  }
  if( strncmp(zCmd, "add", nCmd)==0 ){
    const char *zError = 0;
    const char *zIn;
    const char *zAs;
    Blob file;
    int i;
    i64 mxSize = sqlite3_limit(g.db,SQLITE_LIMIT_LENGTH,-1) - 850;
                   /* Extra space for other fields      ------^^^  */
                   /* of the UNVESIONED table row.                 */

    zAs = find_option("as",0,1);
    verify_all_options();
    if( zAs && g.argc!=4 ) usage("add DISKFILE --as UVFILE");
    db_begin_transaction();
    content_rcvid_init("#!fossil unversioned add");
    for(i=3; i<g.argc; i++){
      zIn = zAs ? zAs : g.argv[i];
      if( zIn[0]==0 ){
        zError = "be empty string";
      }else if( zIn[0]=='/' ){
        zError = "be absolute";
      }else if ( !file_is_simple_pathname(zIn,1) ){
        zError = "contain complex paths";
      }else if( contains_whitespace(zIn) ){
        zError = "contain whitespace";
      }else if( strlen(zIn)>500 ){
        zError = "be more than 500 bytes long";
      }
      if( zError ){
        fossil_fatal("unversioned filenames may not %s: %Q", zError, zIn);
      }
      if( file_size(g.argv[i], ExtFILE)>mxSize ){
        fossil_fatal("file \"%s\" is too big; max size: %,lld bytes",
                     g.argv[i], mxSize);
      }
      blob_init(&file,0,0);
      blob_read_from_file(&file, g.argv[i], ExtFILE);
      unversioned_write(zIn, &file, mtime);
      blob_reset(&file);
    }
    db_end_transaction(0);
357
358
359
360
361
362
363
364
365
366
367
368
369
370



371
372
373
374
375
376
377
372
373
374
375
376
377
378



379
380
381
382
383
384
385
386
387
388
389
390
391
392







-
-
-




+
+
+







  }else if( strncmp(zCmd, "edit", nCmd)==0 ){
    const char *zEditor;    /* Name of the text-editor command */
    const char *zTFile;     /* Temporary file */
    const char *zUVFile;    /* Name of the unversioned file */
    char *zCmd;             /* Command to run the text editor */
    Blob content;           /* Content of the unversioned file */

    verify_all_options();
    if( g.argc!=4) usage("edit UVFILE");
    zUVFile = g.argv[3];
    zEditor = fossil_text_editor();
    if( zEditor==0 ){
      fossil_fatal("no text editor - set the VISUAL env variable");
    }
    verify_all_options();
    if( g.argc!=4) usage("edit UVFILE");
    zUVFile = g.argv[3];
    zTFile = fossil_temp_filename();
    if( zTFile==0 ) fossil_fatal("cannot find a temporary filename");
    db_begin_transaction();
    content_rcvid_init("#!fossil unversioned edit");
    if( unversioned_content(zUVFile, &content)==0 ){
      fossil_fatal("no such uv-file: %Q", zUVFile);
    }
Changes to src/update.c.
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
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







-
+












+







** new check-out.
**
** The -n or --dry-run option causes this command to do a "dry run".
** It prints out what would have happened but does not actually make
** any changes to the current check-out or the repository.
**
** The -v or --verbose option prints status information about
** unchanged files in addition to those file that actually do change.
** unchanged files in addition to those files that actually do change.
**
** Options:
**   --case-sensitive BOOL   Override case-sensitive setting
**   --debug                 Print debug information on stdout
**   -n|--dry-run            If given, display instead of run actions
**   --force-missing         Force update if missing content after sync
**   -K|--keep-merge-files   On merge conflict, retain the temporary files
**                           used for merging, named *-baseline, *-original,
**                           and *-merge.
**   --latest                Acceptable in place of VERSION, update to
**                           latest version
**   --nosync                Do not auto-sync prior to update
**   --proxy PROXY           Use PROXY as http proxy during sync operation
**   --setmtime              Set timestamps of all files to match their
**                           SCM-side times (the timestamp of the last
**                           check-in which modified them).
**   -v|--verbose            Print status information about all files
**   -W|--width WIDTH        Width of lines (default is to auto-detect).
**                           Must be more than 20 or 0 (= no limit,
**                           resulting in a single line per entry).
130
131
132
133
134
135
136



137
138
139
140
141
142
143
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147







+
+
+







  int nConflict = 0;    /* Number of merge conflicts */
  int nOverwrite = 0;   /* Number of unmanaged files overwritten */
  int nUpdate = 0;      /* Number of changes of any kind */
  int bNosync = 0;      /* --nosync.  Omit the auto-sync */
  int width;            /* Width of printed comment lines */
  Stmt mtimeXfer;       /* Statement to transfer mtimes */
  const char *zWidth;   /* Width option string value */
  const char *zCurBrName;      /* Current branch name */
  const char *zNewBrName;      /* New branch name */
  const char *zBrChgMsg = "";  /* Message to display if branch changes */

  if( !internalUpdate ){
    undo_capture_command_line();
    url_proxy_options();
  }
  zWidth = find_option("width","W",1);
  if( zWidth ){
161
162
163
164
165
166
167

168
169
170
171
172
173
174
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179







+







  bNosync = find_option("nosync",0,0)!=0;

  /* We should be done with options.. */
  verify_all_options();

  db_must_be_within_tree();
  vid = db_lget_int("checkout", 0);
  zCurBrName = branch_of_rid(vid);
  user_select();
  if( !dryRunFlag && !internalUpdate && !bNosync ){
    if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag, 1, "update") ){
      fossil_fatal("update abandoned due to sync failure");
    }
  }

192
193
194
195
196
197
198
199

200
201
202
203
204
205
206
197
198
199
200
201
202
203

204
205
206
207
208
209
210
211







-
+







       if( tid==0 || !is_a_version(tid) ){
        fossil_fatal("no such check-in: %s", g.argv[2]);
      }
    }
  }

  /* If no VERSION is specified on the command-line, then look for a
  ** descendent of the current version.  If there are multiple descendants,
  ** descendant of the current version.  If there are multiple descendants,
  ** look for one from the same branch as the current version.  If there
  ** are still multiple descendants, show them all and refuse to update
  ** until the user selects one.
  */
  if( tid==0 ){
    int closeCode = 1;
    compute_leaves(vid, closeCode);
249
250
251
252
253
254
255
256

257
258
259
260
261
262
263
254
255
256
257
258
259
260

261
262
263
264
265
266
267
268







-
+







  if( !dryRunFlag && !internalUpdate ) undo_begin();
  if( load_vfile_from_rid(tid) && !forceMissingFlag ){
    fossil_fatal("missing content, unable to update");
  };

  /*
  ** The record.fn field is used to match files against each other.  The
  ** FV table contains one row for each each unique filename in
  ** FV table contains one row for each unique filename in
  ** in the current check-out, the pivot, and the version being merged.
  */
  db_multi_exec(
    "DROP TABLE IF EXISTS fv;"
    "CREATE TEMP TABLE fv("
    "  fn TEXT %s PRIMARY KEY,"   /* The filename relative to root */
    "  idv INTEGER,"              /* VFILE entry for current version */
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
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
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
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
636







+















+
+
+
+



+











+
+
+





-
+






+
+
+














+






+










+
+
+



















-
-
-







+


+
+
+












+
+
+


+
+




















+
+
+

-
-
-
-
-
+
+
+
+
+











+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







+
+
+
+
+


-
+





-
-
+
+







  db_prepare(&mtimeXfer,
    "UPDATE vfile SET mtime=(SELECT mtime FROM vfile WHERE id=:idv)"
    " WHERE id=:idt"
  );
  assert( g.zLocalRoot!=0 );
  assert( strlen(g.zLocalRoot)>0 );
  assert( g.zLocalRoot[strlen(g.zLocalRoot)-1]=='/' );
  merge_info_init();
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);  /* The filename from root */
    int idv = db_column_int(&q, 1);             /* VFILE entry for current */
    int ridv = db_column_int(&q, 2);            /* RecordID for current */
    int idt = db_column_int(&q, 3);             /* VFILE entry for target */
    int ridt = db_column_int(&q, 4);            /* RecordID for target */
    int chnged = db_column_int(&q, 5);          /* Current is edited */
    const char *zNewName = db_column_text(&q,6);/* New filename */
    int isexe = db_column_int(&q, 7);           /* EXE perm for new file */
    int islinkv = db_column_int(&q, 8);         /* Is current file is a link */
    int islinkt = db_column_int(&q, 9);         /* Is target file is a link */
    int deleted = db_column_int(&q, 10);        /* Marked for deletion */
    char *zFullPath;                            /* Full pathname of the file */
    char *zFullNewPath;                         /* Full pathname of dest */
    char nameChng;                              /* True if the name changed */
    const char *zOp = 0;                        /* Type of change.  */
    i64 sz = 0;                                 /* Size of the file */
    int nc = 0;                                 /* Number of conflicts */
    const char *zErrMsg = 0;                    /* Error message */

    zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
    zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName);
    sz = file_size(zFullNewPath, ExtFILE);
    nameChng = fossil_strcmp(zName, zNewName);
    nUpdate++;
    if( deleted ){
      db_multi_exec("UPDATE vfile SET deleted=1 WHERE id=%d", idt);
    }
    if( idv>0 && ridv==0 && idt>0 && ridt>0 ){
      /* Conflict.  This file has been added to the current check-out
      ** but also exists in the target check-out.  Use the current version.
      */
      fossil_print("CONFLICT %s\n", zName);
      nConflict++;
      zOp = "CONFLICT";
      nc = 1;
      zErrMsg = "duplicate file";
    }else if( idt>0 && idv==0 ){
      /* File added in the target. */
      if( file_isfile_or_link(zFullPath) ){
        /* Name of backup file with Original content */
        char *zOrig = file_newname(zFullPath, "original", 1);
        /* Backup previously unanaged file before to be overwritten */
        /* Backup previously unmanaged file before being overwritten */
        file_copy(zFullPath, zOrig);
        fossil_free(zOrig);
        fossil_print("ADD %s - overwrites an unmanaged file", zName);
        if( !dryRunFlag ) fossil_print(", original copy backed up locally");
        fossil_print("\n");
        nOverwrite++;
        nc = 1;
        zOp = "CONFLICT";
        zErrMsg = "new file overwrites unmanaged file";
      }else{
        fossil_print("ADD %s\n", zName);
      }
      if( !dryRunFlag && !internalUpdate ) undo_save(zName);
      if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
    }else if( idt>0 && idv>0 && ridt!=ridv && (chnged==0 || deleted) ){
      /* The file is unedited.  Change it to the target version */
      if( deleted ){
        fossil_print("UPDATE %s - change to unmanaged file\n", zName);
      }else{
        fossil_print("UPDATE %s\n", zName);
      }
      if( !dryRunFlag && !internalUpdate ) undo_save(zName);
      if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
      zOp = "UPDATE";
    }else if( idt>0 && idv>0 && !deleted && file_size(zFullPath, RepoFILE)<0 ){
      /* The file missing from the local check-out. Restore it to the
      ** version that appears in the target. */
      fossil_print("UPDATE %s\n", zName);
      if( !dryRunFlag && !internalUpdate ) undo_save(zName);
      if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
      zOp = "UPDATE";
    }else if( idt==0 && idv>0 ){
      if( ridv==0 ){
        /* Added in current check-out.  Continue to hold the file as
        ** as an addition */
        db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv);
      }else if( chnged ){
        /* Edited locally but deleted from the target.  Do not track the
        ** file but keep the edited version around. */
        fossil_print("CONFLICT %s - edited locally but deleted by update\n",
                     zName);
        zOp = "CONFLICT";
        zErrMsg = "edited locally but deleted by update";
        nc = 1;
        nConflict++;
      }else{
        fossil_print("REMOVE %s\n", zName);
        if( !dryRunFlag && !internalUpdate ) undo_save(zName);
        if( !dryRunFlag ){
          char *zDir;
          file_delete(zFullPath);
          zDir = file_dirname(zName);
          while( zDir!=0 ){
            char *zNext;
            db_multi_exec("INSERT OR IGNORE INTO dir_to_delete(name)"
                          "VALUES(%Q)", zDir);
            zNext = db_changes() ? file_dirname(zDir) : 0;
            fossil_free(zDir);
            zDir = zNext;
          }
        }
      }
    }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){
      /* Merge the changes in the current tree into the target version */
      Blob r, t, v;
      int rc;
      if( nameChng ){
        fossil_print("MERGE %s -> %s\n", zName, zNewName);
      }else{
        fossil_print("MERGE %s\n", zName);
      }
      if( islinkv || islinkt ){
        fossil_print("***** Cannot merge symlink %s\n", zNewName);
        zOp = "CONFLICT";
        nConflict++;
      }else{
        /* Merge the changes in the current tree into the target version */
        Blob r, t, v;
        int rc;
        unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0;
        if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES;
        if( !dryRunFlag && !internalUpdate ) undo_save(zName);
        content_get(ridt, &t);
        content_get(ridv, &v);
        rc = merge_3way(&v, zFullPath, &t, &r, mergeFlags);
        if( rc>=0 ){
          if( !dryRunFlag ){
            blob_write_to_file(&r, zFullNewPath);
            file_setexe(zFullNewPath, isexe);
          }
          if( rc>0 ){
            nc = rc;
            zOp = "CONFLICT";
            zErrMsg = "merge conflicts";
            fossil_print("***** %d merge conflicts in %s\n", rc, zNewName);
            nConflict++;
          }else{
            zOp = "MERGE";
          }
        }else{
          if( !dryRunFlag ){
            if( !keepMergeFlag ){
              /* Name of backup file with Original content */
              char *zOrig = file_newname(zFullPath, "original", 1);
              /* Backup non-mergeable binary file when --keep-merge-files is
                 not specified */
              file_copy(zFullPath, zOrig);
              fossil_free(zOrig);
            }
            blob_write_to_file(&t, zFullNewPath);
            file_setexe(zFullNewPath, isexe);
          }
          fossil_print("***** Cannot merge binary file %s", zNewName);
          if( !dryRunFlag ){
            fossil_print(", original copy backed up locally");
          }
          fossil_print("\n");
          nConflict++;
          zOp = "ERROR";
          zErrMsg = "cannot merge binary file";
          nc = 1;
        }
      }
      if( nameChng && !dryRunFlag ) file_delete(zFullPath);
      blob_reset(&v);
      blob_reset(&t);
      blob_reset(&r);
        blob_reset(&v);
        blob_reset(&t);
        blob_reset(&r);
      }
      if( nameChng && !dryRunFlag ) file_delete(zFullPath);
    }else{
      nUpdate--;
      if( chnged ){
        if( verboseFlag ) fossil_print("EDITED %s\n", zName);
      }else{
        db_bind_int(&mtimeXfer, ":idv", idv);
        db_bind_int(&mtimeXfer, ":idt", idt);
        db_step(&mtimeXfer);
        db_reset(&mtimeXfer);
        if( verboseFlag ) fossil_print("UNCHANGED %s\n", zName);
      }
    }
    if( zOp!=0 ){
      db_multi_exec(
        "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)"
        "VALUES(%Q,%Q,%d,%Q,NULL,%lld,%Q,%d,%Q,%d,%Q)",
        /* op   */ zOp,
        /* fnp  */ zName,
        /* ridp */ ridv,
        /* fn   */ zNewName,
        /* sz   */ sz,
        /* fnm  */ zName,
        /* ridm */ ridt,
        /* fnr  */ zNewName,
        /* nc   */ nc,
        /* msg  */ zErrMsg
      );
    }
    free(zFullPath);
    free(zFullNewPath);
  }
  db_finalize(&q);
  db_finalize(&mtimeXfer);
  fossil_print("%.79c\n",'-');
  zNewBrName = branch_of_rid(tid);
  if( g.argc<3 && fossil_strcmp(zCurBrName, zNewBrName)!=0 ){
    zBrChgMsg = mprintf("  Branch changed from %s to %s.",
                           zCurBrName, zNewBrName);
  }
  if( nUpdate==0 ){
    show_common_info(tid, "checkout:", 1, 0);
    fossil_print("%-13s None. Already up-to-date\n", "changes:");
    fossil_print("%-13s None. Already up-to-date.%s\n", "changes:", zBrChgMsg);
  }else{
    fossil_print("%-13s %.40s %s\n", "updated-from:", rid_to_uuid(vid),
                 db_text("", "SELECT datetime(mtime) || ' UTC' FROM event "
                         "  WHERE objid=%d", vid));
    show_common_info(tid, "updated-to:", 1, 0);
    fossil_print("%-13s %d file%s modified.\n", "changes:",
                 nUpdate, nUpdate>1 ? "s" : "");
    fossil_print("%-13s %d file%s modified.%s\n", "changes:",
                 nUpdate, nUpdate>1 ? "s" : "", zBrChgMsg);
  }

  /* Report on conflicts
  */
  if( !dryRunFlag ){
    Stmt q;
    int nMerge = 0;
702
703
704
705
706
707
708
709

710
711
712
713
714
715
716
754
755
756
757
758
759
760

761
762
763
764
765
766
767
768







-
+







  int vid;
  Manifest *pManifest;

  /* Determine the check-in manifest artifact ID.  Panic on failure. */
  if( zRevision ){
    vid = name_to_typed_rid(zRevision, "ci");
  }else if( !g.localOpen ){
    vid = name_to_typed_rid(db_get("main-branch", 0), "ci");
    vid = name_to_typed_rid(db_main_branch(), "ci");
  }else{
    vid = db_lget_int("checkout", 0);
    if( !is_a_version(vid) ){
      if( vid==0 ) return 0;
      zRevision = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
      if( zRevision ){
        fossil_fatal("check-out artifact is not a check-in: %s", zRevision);
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
838
839
840
841
842

843




844
845
846
847
848
849
850
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







+













+







+
















+
-
+
+
+
+







**
** Revert all files if no file name is provided.
**
** If a file is reverted accidentally, it can be restored using
** the "fossil undo" command.
**
** Options:
**   --noundo                 Do not record changes in the undo/redo log.
**   -r|--revision VERSION    Revert given FILE(s) back to given
**                            VERSION
**
** See also: [[redo]], [[undo]], [[checkout]], [[update]]
*/
void revert_cmd(void){
  Manifest *pCoManifest;          /* Manifest of current check-out */
  Manifest *pRvManifest;          /* Manifest of selected revert version */
  ManifestFile *pCoFile;          /* File within current check-out manifest */
  ManifestFile *pRvFile;          /* File within revert version manifest */
  const char *zFile;              /* Filename relative to check-out root */
  const char *zRevision;          /* Selected revert version, NULL if current */
  Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */
  int useUndo = 1;                /* True to record changes in UNDO */
  int i;
  Stmt q;
  int revertAll = 0;
  int revisionOptNotSupported = 0;

  undo_capture_command_line();
  zRevision = find_option("revision", "r", 1);
  useUndo = find_option("noundo", 0, 0)==0;
  verify_all_options();

  if( g.argc<2 ){
    usage("?OPTIONS? [FILE] ...");
  }
  if( zRevision && g.argc<3 ){
    fossil_fatal("directories or the entire tree can only be reverted"
                 " back to current version");
  }
  db_must_be_within_tree();

  /* Get manifests of revert version and (if different) current check-out. */
  pRvManifest = historical_manifest(zRevision);
  pCoManifest = zRevision ? historical_manifest(0) : 0;

  db_begin_transaction();
  if( useUndo ){
  undo_begin();
    undo_begin();
  }else{
    undo_reset();
  }
  db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);");

  if( g.argc>2 ){
    for(i=2; i<g.argc; i++){
      Blob fname;
      zFile = mprintf("%/", g.argv[i]);
      blob_zero(&fname);
933
934
935
936
937
938
939
940

941
942
943
944
945
946
947
992
993
994
995
996
997
998

999
1000
1001
1002
1003
1004
1005
1006







-
+







    zFull = mprintf("%/%/", g.zLocalRoot, zFile);
    pRvFile = pRvManifest? manifest_file_find(pRvManifest, zFile) : 0;
    if( !pRvFile ){
      if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q",
                 zFile, zFile)==0 ){
        fossil_print("UNMANAGE %s\n", zFile);
      }else{
        undo_save(zFile);
        if( useUndo ) 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;"
960
961
962
963
964
965
966
967

968
969
970
971
972
973
974
1019
1020
1021
1022
1023
1024
1025

1026
1027
1028
1029
1030
1031
1032
1033







-
+







        rvChnged = manifest_file_mperm(pRvFile)!=rvPerm
                || fossil_strcmp(pRvFile->zUuid, pCoFile->zUuid)!=0;
      }

      /* Get contents of reverted-to file. */
      content_get(fast_uuid_to_rid(pRvFile->zUuid), &record);

      undo_save(zFile);
      if( useUndo ) undo_save(zFile);
      if( file_size(zFull, RepoFILE)>=0
       && (rvPerm==PERM_LNK || file_islink(0))
      ){
        file_delete(zFull);
      }
      if( rvPerm==PERM_LNK ){
        symlink_create(blob_str(&record), zFull);
986
987
988
989
990
991
992
993

994
995
996
997
998
999
1045
1046
1047
1048
1049
1050
1051

1052
1053
1054
1055
1056
1057
1058







-
+






         mtime, rvChnged, rvPerm==PERM_EXE, rvPerm==PERM_LNK, zFile, zFile
      );
    }
    blob_reset(&record);
    free(zFull);
  }
  db_finalize(&q);
  undo_finish();
  if( useUndo) undo_finish();
  db_end_transaction(0);

  /* Deallocate parsed manifest structures. */
  manifest_destroy(pRvManifest);
  manifest_destroy(pCoManifest);
}
Changes to src/url.c.
56
57
58
59
60
61
62

63
64
65
66
67
68
69
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70







+







  int dfltPort;         /* The default port for the given protocol */
  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: */
  char *subpath;        /* Secondary HTTP request path for ssh: and file: */
  char *pwConfig;       /* CONFIG table entry that gave us the password */
  unsigned flags;       /* Boolean flags controlling URL processing */
  int useProxy;         /* Used to remember that a proxy is in use */
  int proxyOrigPort;       /* Tunneled port number for https through proxy */
  char *proxyUrlPath;      /* Remember path when proxy is use */
  char *proxyUrlCanonical; /* Remember canonical path when proxy is use */
};
225
226
227
228
229
230
231
232

233
234
235
236
237
238

239
240
241
242
243
244
245
226
227
228
229
230
231
232

233
234
235
236
237
238

239
240
241
242
243
244
245
246







-
+





-
+







      if( c!=0 && c!='/' ) fossil_fatal("url missing '/' after port number");
      pUrlData->hostname = mprintf("%s:%d", pUrlData->name, pUrlData->port);
    }else{
      pUrlData->port = pUrlData->dfltPort;
      pUrlData->hostname = pUrlData->name;
    }
    dehttpize(pUrlData->name);
    pUrlData->path = mprintf("%s", &zUrl[i]);
    pUrlData->path = fossil_strdup(&zUrl[i]);
    for(i=0; pUrlData->path[i] && pUrlData->path[i]!='?'; i++){}
    if( pUrlData->path[i] ){
      pUrlData->path[i] = 0;
      i++;
    }
    zExe = mprintf("");
    zExe = fossil_strdup("");
    while( pUrlData->path[i]!=0 ){
      char *zName, *zValue;
      zName = &pUrlData->path[i];
      zValue = zName;
      while( pUrlData->path[i] && pUrlData->path[i]!='=' ){ i++; }
      if( pUrlData->path[i]=='=' ){
        pUrlData->path[i] = 0;
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
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







-
+










-
+


-
+







        "%s://%s%T:%d%T%z",
        pUrlData->protocol, zLogin, pUrlData->name, pUrlData->port,
        pUrlData->path, zExe
      );
    }
    if( pUrlData->isSsh && pUrlData->path[1] ){
      char *zOld = pUrlData->path;
      pUrlData->path = mprintf("%s", zOld+1);
      pUrlData->path = fossil_strdup(zOld+1);
      fossil_free(zOld);
    }
    free(zLogin);
  }else if( strncmp(zUrl, "file:", 5)==0 ){
    pUrlData->isFile = 1;
    if( zUrl[5]=='/' && zUrl[6]=='/' ){
      i = 7;
    }else{
      i = 5;
    }
    zFile = mprintf("%s", &zUrl[i]);
    zFile = fossil_strdup(&zUrl[i]);
  }else if( file_isfile(zUrl, ExtFILE) ){
    pUrlData->isFile = 1;
    zFile = mprintf("%s", zUrl);
    zFile = fossil_strdup(zUrl);
  }else if( file_isdir(zUrl, ExtFILE)==1 ){
    zFile = mprintf("%s/FOSSIL", zUrl);
    if( file_isfile(zFile, ExtFILE) ){
      pUrlData->isFile = 1;
    }else{
      free(zFile);
      zFile = 0;
404
405
406
407
408
409
410

411
412
413
414
415
416
417
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419







+







  }
  fossil_free(p->canonical);
  fossil_free(p->name);
  fossil_free(p->path);
  fossil_free(p->user);
  fossil_free(p->passwd);
  fossil_free(p->fossil);
  fossil_free(p->subpath);
  fossil_free(p->pwConfig);
  memset(p, 0, sizeof(*p));
}

/*
** Move a URL parse from one UrlData object to another.
*/
481
482
483
484
485
486
487

488
489
490
491
492
493
494
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497







+







    fossil_print("g.url.passwd    = %s\n", g.url.passwd);
  }else{
    fossil_print("g.url.passwd    = ************\n");
  }
  fossil_print("g.url.pwConfig  = %s\n", g.url.pwConfig);
  fossil_print("g.url.canonical = %s\n", g.url.canonical);
  fossil_print("g.url.fossil    = %s\n", g.url.fossil);
  fossil_print("g.url.subpath   = %s\n", g.url.subpath);
  fossil_print("g.url.flags     = 0x%04x\n", g.url.flags);
  fossil_print("url_full(g.url) = %z\n", url_full(&g.url));
}

/*
** COMMAND: test-urlparser
**
542
543
544
545
546
547
548


549
550
551
552
553
554
555
556
557
558


559
560
561
562
563
564
565
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







+
+









-
+
+







** The original purpose of this routine is the above.  But this
** also happens to be a convenient place to look for other
** network-related options:
**
**    --nosync             Temporarily disable "autosync"
**
**    --ipv4               Disallow IPv6.  Use only IPv4.
**
**    --ipv6               Disallow IPv4.  Use only IPv6.
**
**    --accept-any-cert    Disable server SSL cert validation. Accept
**                         any SSL cert that the server provides.
**                         WARNING: this option opens you up to
**                         forged-DNS and man-in-the-middle attacks!
*/
void url_proxy_options(void){
  zProxyOpt = find_option("proxy", 0, 1);
  if( find_option("nosync",0,0) ) g.fNoSync = 1;
  if( find_option("ipv4",0,0) ) g.fIPv4 = 1;
  if( find_option("ipv4",0,0) ) g.eIPvers = 1;
  if( find_option("ipv6",0,0) ) g.eIPvers = 2;
#ifdef FOSSIL_ENABLE_SSL
  if( find_option("accept-any-cert",0,0) ){
    ssl_disable_cert_verification();
  }
#endif /* FOSSIL_ENABLE_SSL */
}

Changes to src/user.c.
107
108
109
110
111
112
113


114
115
116
117
118
119
120
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122







+
+







  assert( zPwd==zPwdBuffer );
  return zPwd;
}
void freepass(){
  if( !zPwdBuffer ) return;
  assert( nPwdBuffer>0 );
  fossil_secure_free_page(zPwdBuffer, nPwdBuffer);
  zPwdBuffer = 0;
  nPwdBuffer = 0;
}
#endif

/*
** Scramble substitution matrix:
*/
static char aSubst[256];
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
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







-
-
+
+
-









-
+







*/
char *prompt_for_user_password(const char *zUser){
  char *zPrompt = mprintf("\rpassword for %s: ", zUser);
  char *zPw;
  Blob x;
  fossil_force_newline();
  prompt_for_password(zPrompt, &x, 0);
  free(zPrompt);
  zPw = mprintf("%b", &x);
  fossil_free(zPrompt);
  zPw = blob_str(&x)/*transfer ownership*/;
  blob_reset(&x);
  return zPw;
}

/*
** Prompt the user to enter a single line of text.
*/
void prompt_user(const char *zPrompt, Blob *pIn){
  char *z;
  char zLine[1000];
  blob_zero(pIn);
  blob_init(pIn, 0, 0);
  fossil_force_newline();
  fossil_print("%s", zPrompt);
  fflush(stdout);
  z = fgets(zLine, sizeof(zLine), stdin);
  if( z ){
    int n = (int)strlen(z);
    if( n>0 && z[n-1]=='\n' ) fossil_new_line_started();
324
325
326
327
328
329
330
331

332
333
334






335

336
337


338
339
340
341
342
343
344
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







-
+


-
+
+
+
+
+
+

+
-
-
+
+







**
**        Query or set the capabilities for user USERNAME
**
** > fossil user contact USERNAME ?CONTACT-INFO?
**
**        Query or set contact information for user USERNAME
**
** > fossil user default ?USERNAME?
** > fossil user default ?OPTIONS? ?USERNAME?
**
**        Query or set the default user.  The default user is the
**        user for command-line interaction.
**        user for command-line interaction.  If USERNAME is an
**        empty string, then the default user is unset from the
**        repository and will subsequently be determined by the -U
**        command-line option or by environment variables
**        FOSSIL_USER, USER, LOGNAME, or USERNAME, in that order.
**        OPTIONS:
**
**            -v|--verbose        Show how the default user is computed
** > fossil user list
** > fossil user ls
**
** > fossil user list | ls
**
**        List all users known to the repository
**
** > fossil user new ?USERNAME? ?CONTACT-INFO? ?PASSWORD?
**
**        Create a new user in the repository.  Users can never be
**        deleted.  They can be denied all access but they must continue
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
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
431
432
433
434
435
436
437
438
439
440
441
442
443
444







+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







      "INSERT INTO user(login,pw,cap,info,mtime)"
      "VALUES(%B,%Q,%B,%B,now())",
      &login, zPw, &caps, &contact
    );
    db_protect_pop();
    free(zPw);
  }else if( n>=2 && strncmp(g.argv[2],"default",n)==0 ){
    int eVerbose = find_option("verbose","v",0)!=0;
    verify_all_options();
    if( g.argc==3 ){
      user_select();
      fossil_print("%s\n", g.zLogin);
    }else{
      if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
        fossil_fatal("no such user: %s", g.argv[3]);
      }
      if( g.localOpen ){
        db_lset("default-user", g.argv[3]);
      }else{
        db_set("default-user", g.argv[3], 0);
    if( g.argc>3 ){
      const char *zUser = g.argv[3];
      if( fossil_strcmp(zUser,"")==0 || fossil_stricmp(zUser,"nobody")==0 ){
        db_begin_transaction();
        if( g.localOpen ){
          db_multi_exec("DELETE FROM vvar WHERE name='default-user'");
        }
        db_unset("default-user",0);
        db_commit_transaction();
      }else{
        if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.argv[3]) ){
          fossil_fatal("no such user: %s", g.argv[3]);
        }
        if( g.localOpen ){
          db_lset("default-user", g.argv[3]);
        }else{
          db_set("default-user", g.argv[3], 0);
        }
      }
    }
    if( g.argc==3 || eVerbose ){
      int eHow = user_select();
      const char *zHow = "???";
      switch( eHow ){
        case 1:  zHow = "-U option"; break;
        case 2:  zHow = "previously set"; break;
        case 3:  zHow = "local check-out"; break;
        case 4:  zHow = "repository"; break;
        case 5:  zHow = "FOSSIL_USER"; break;
        case 6:  zHow = "USER"; break;
        case 7:  zHow = "LOGNAME"; break;
        case 8:  zHow = "USERNAME"; break;
        case 9:  zHow = "URL"; break;
      }
      if( eVerbose ){
        fossil_print("%s (determined by %s)\n", g.zLogin, zHow);
      }else{
        fossil_print("%s\n", g.zLogin);
      }
    }
  }else if(( n>=2 && strncmp(g.argv[2],"list",n)==0 ) ||
           ( n>=2 && strncmp(g.argv[2],"ls",n)==0 )){
    Stmt q;
    db_prepare(&q, "SELECT login, info FROM user ORDER BY login");
    while( db_step(&q)==SQLITE_ROW ){
428
429
430
431
432
433
434
435

436
437
438
439
440
441
442
464
465
466
467
468
469
470

471
472
473
474
475
476
477
478







-
+







      fossil_print("password unchanged\n");
    }else{
      char *zSecret = sha1_shared_secret(blob_str(&pw), g.argv[3], 0);
      db_unprotect(PROTECT_USER);
      db_multi_exec("UPDATE user SET pw=%Q, mtime=now() WHERE uid=%d",
                    zSecret, uid);
      db_protect_pop();
      free(zSecret);
      fossil_free(zSecret);
    }
  }else if( n>=2 && strncmp(g.argv[2],"capabilities",2)==0 ){
    int uid;
    if( g.argc!=4 && g.argc!=5 ){
      usage("capabilities USERNAME ?PERMISSIONS?");
    }
    uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", g.argv[3]);
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
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







-
+










+
+
-
+

-
+

-
+

-
+

-
+

-
+

-
+



-
+

-
+




-
+



-
+

-
+

-
+

-
+

-
+

-
+



-
+








  if( zLogin==0 ){
    return 0;
  }
  uid = db_int(0, "SELECT uid FROM user WHERE login=%Q", zLogin);
  if( uid ){
    g.userUid = uid;
    g.zLogin = mprintf("%s", zLogin);
    g.zLogin = fossil_strdup(zLogin);
    return 1;
  }
  return 0;
}

/*
** Figure out what user is at the controls.
**
**   (1)  Use the --user and -U command-line options.
**
**   (2)  The name used for login (if there was a login).
**
**   (2)  If the local database is open, check in VVAR.
**   (3)  If the local database is open, check in VVAR.
**
**   (3)  Check the default user in the repository
**   (4)  Check the default-user in the repository
**
**   (4)  Try the FOSSIL_USER environment variable.
**   (5)  Try the FOSSIL_USER environment variable.
**
**   (5)  Try the USER environment variable.
**   (6)  Try the USER environment variable.
**
**   (6)  Try the LOGNAME environment variable.
**   (7)  Try the LOGNAME environment variable.
**
**   (7)  Try the USERNAME environment variable.
**   (8)  Try the USERNAME environment variable.
**
**   (8)  Check if the user can be extracted from the remote URL.
**   (9)  Check if the user can be extracted from the remote URL.
**
** The user name is stored in g.zLogin.  The uid is in g.userUid.
*/
void user_select(void){
int user_select(void){
  UrlData url;
  if( g.userUid ) return;
  if( g.userUid ) return 1;
  if( g.zLogin ){
    if( attempt_user(g.zLogin)==0 ){
      fossil_fatal("no such user: %s", g.zLogin);
    }else{
      return;
      return 2;
    }
  }

  if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return;
  if( g.localOpen && attempt_user(db_lget("default-user",0)) ) return 3;

  if( attempt_user(db_get("default-user", 0)) ) return;
  if( attempt_user(db_get("default-user", 0)) ) return 4;

  if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return;
  if( attempt_user(fossil_getenv("FOSSIL_USER")) ) return 5;

  if( attempt_user(fossil_getenv("USER")) ) return;
  if( attempt_user(fossil_getenv("USER")) ) return 6;

  if( attempt_user(fossil_getenv("LOGNAME")) ) return;
  if( attempt_user(fossil_getenv("LOGNAME")) ) return 7;

  if( attempt_user(fossil_getenv("USERNAME")) ) return;
  if( attempt_user(fossil_getenv("USERNAME")) ) return 8;

  memset(&url, 0, sizeof(url));
  url_parse_local(0, URL_USE_CONFIG, &url);
  if( url.user && attempt_user(url.user) ) return;
  if( url.user && attempt_user(url.user) ) return 9;

  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");
737
738
739
740
741
742
743
744

745
746
747
748
749
750
751
775
776
777
778
779
780
781

782
783
784
785
786
787
788
789







-
+







  }
  blob_append_sql(&sql,"  ORDER BY rowid DESC LIMIT %d OFFSET %d", n+1, skip);
  if( skip ){
    style_submenu_element("Newer", "%R/user_log?o=%d&n=%d&y=%d",
              skip>=n ? skip-n : 0, n, y);
  }
  rc = db_prepare_ignore_error(&q, "%s", blob_sql_text(&sql));
  fLogEnabled = db_get_boolean("access-log", 0);
  fLogEnabled = db_get_boolean("access-log", 1);
  @ <div align="center">User logging is %s(fLogEnabled?"on":"off").
  @ (Change this on the <a href="setup_settings">settings</a> page.)</div>
  @ <table border="1" cellpadding="5" class="sortable" align="center" \
  @  data-column-types='Ttt' data-init-sort='1'>
  @ <thead><tr><th width="33%%">Date</th><th width="34%%">User</th>
  @ <th width="33%%">IP Address</th></tr></thead><tbody>
  while( rc==SQLITE_OK && db_step(&q)==SQLITE_ROW ){
Changes to src/utf8.c.
23
24
25
26
27
28
29
30

31
32
33
34
35
36
37
23
24
25
26
27
28
29

30
31
32
33
34
35
36
37







-
+







#include "utf8.h"
#include <sqlite3.h>
#ifdef _WIN32
# include <windows.h>
#endif
#include "cygsup.h"

#if defined(_WIN32) || defined(__CYGWIN__)
#if defined(_WIN32)
/*
** Translate MBCS to UTF-8.  Return a pointer to the translated text.
** Call fossil_mbcs_free() to deallocate any memory used to store the
** returned pointer when done.
*/
char *fossil_mbcs_to_utf8(const char *zMbcs){
  extern char *sqlite3_win32_mbcs_to_utf8(const char*);
Changes to src/util.c.
232
233
234
235
236
237
238
239

240
241
242
243
244
245
246
232
233
234
235
236
237
238

239
240
241
242
243
244
245
246







-
+







*/
static int safeCmdStrTest = 0;

/*
** Check the input string to ensure that it is safe to pass into system().
** A string is unsafe for system() on unix if it contains any of the following:
**
**   *  Any occurrance of '$' or '`' except single-quoted or after \
**   *  Any occurrence of '$' or '`' except single-quoted or after \
**   *  Any of the following characters, unquoted:  ;|& or \n except
**      these characters are allowed as the very last character in the
**      string.
**   *  Unbalanced single or double quotes
**
** This routine is intended as a second line of defense against attack.
** It should never fail.  Dangerous shell strings should be detected and
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
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







+
-
-
-
-
-
+
+
+
+
+
+
+
+
+


+
+
+
+
+
+
+
+
+
+
+
+
-
+
+






-
-
-
+
+
-
-
-
-
-
+
+
+
+
+
+







}

/*
** Return the name of the users preferred text editor.  Return NULL if
** not found.
**
** Search algorithm:
** (1) The value of the --editor command-line argument
** (1) The local "editor" setting
** (2) The global "editor" setting
** (3) The VISUAL environment variable
** (4) The EDITOR environment variable
** (5) (Windows only:) "notepad.exe"
** (2) The local "editor" setting
** (3) The global "editor" setting
** (4) The VISUAL environment variable
** (5) The EDITOR environment variable
** (6) Any of several common editors that might be available, such as:
**        notepad, nano, pico, jove, edit, vi, vim, ed
**
** The search only occurs once, the first time this routine is called.
** Second and subsequent invocations always return the same value.
*/
const char *fossil_text_editor(void){
  static const char *zEditor = 0;
  const char *azStdEd[] = {
#ifdef _WIN32
    "notepad",
#endif
    "nano", "pico", "jove", "edit", "vi", "vim", "ed"
  };
  int i = 0;
  if( zEditor==0 ){
    zEditor = find_option("editor",0,1);
  }
  if( zEditor==0 ){
  const char *zEditor = db_get("editor", 0);
    zEditor = db_get("editor", 0);
  }
  if( zEditor==0 ){
    zEditor = fossil_getenv("VISUAL");
  }
  if( zEditor==0 ){
    zEditor = fossil_getenv("EDITOR");
  }
#if defined(_WIN32) || defined(__CYGWIN__)
  if( zEditor==0 ){
    zEditor = mprintf("%s\\notepad.exe", fossil_getenv("SYSTEMROOT"));
  while( zEditor==0 && i<count(azStdEd) ){
    if( fossil_app_on_path(azStdEd[i],0) ){
#if defined(__CYGWIN__)
    zEditor = fossil_utf8_to_path(zEditor, 0);
#endif
  }
#endif
      zEditor = azStdEd[i];
    }else{
      i++;
    }
  }
  if( zEditor && is_false(zEditor) ) zEditor = 0;
  return zEditor;
}

/*
** Construct a temporary filename.
**
** The returned string is obtained from sqlite3_malloc() and must be
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
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
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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+











-
-



+
+
+

-
+




+

+
+
+
+
+
+
+
+
+
+
+




+
+
+
+

-
+
+
+
+


-
+

-
-
-


















-
+




+







    fossil_print("%s (%d bits of entropy)\n", zPassword,
                 (int)(log(et)/log(2.0)));
  }else{
    fossil_print("%s\n", zPassword);
  }
  fossil_free(zPassword);
}

/*
** Generate a version 4 ("random"), variant 1 UUID (RFC 9562, Section 5.4).
**
** Format: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
**           where  M=4  and  N=8, 9, a, or b    (this leaves 122 random bits)
*/
char* fossil_generate_uuid() {
  static const char zDigits[] = "0123456789abcdef";
  unsigned char aBlob[16];
  unsigned char zStr[37];
  unsigned char *p = zStr;
  int i, k;

  sqlite3_randomness(16, aBlob);
  aBlob[6] = (aBlob[6]&0x0f) + 0x40; /* Version byte:  0100 xxxx */
  aBlob[8] = (aBlob[8]&0x3f) + 0x80; /* Variant byte:  10xx xxxx */

  for(i=0, k=0x550; i<16; i++, k=k>>1){
    if( k&1 ){
      *p++ = '-';                    /* Add a dash after byte 4, 6, 8, and 12 */
    }
    *p++ = zDigits[aBlob[i]>>4];
    *p++ = zDigits[aBlob[i]&0xf];
  }
  *p = 0;

  return fossil_strdup((char*)zStr);
}

/*
** COMMAND: test-generate-uuid
**
** Usage: %fossil test-generate-uuid
**
** Generate a version 4 ("random"), variant 1 UUID (RFC 9562, Section 5.4):
**
**     xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx  - where  M=4  and  N=8, 9, a, or b
*/
void test_generate_uuid(void){
  fossil_print("%s\n", fossil_generate_uuid());
}

/*
** Return the number of decimal digits in a nonnegative integer.  This is useful
** when formatting text.
*/
int fossil_num_digits(int n){
  return n<      10 ? 1 : n<      100 ? 2 : n<      1000 ? 3
       : n<   10000 ? 4 : n<   100000 ? 5 : n<   1000000 ? 6
       : n<10000000 ? 7 : n<100000000 ? 8 : n<1000000000 ? 9 : 10;
}

#if !defined(_WIN32)
#if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__)
/*
** Search for an executable on the PATH environment variable.
** Return true (1) if found and false (0) if not found.
**
** Print the full pathname of the first location if ePrint==1.  Print
** all pathnames for the executable if ePrint==2 or more.
*/
static int binaryOnPath(const char *zBinary){
int fossil_app_on_path(const char *zBinary, int ePrint){
  const char *zPath = fossil_getenv("PATH");
  char *zFull;
  int i;
  int bExists;
  int bFound = 0;
  while( zPath && zPath[0] ){
#ifdef _WIN32
    while( zPath[0]==';' ) zPath++;
    for(i=0; zPath[i] && zPath[i]!=';'; i++){}
    zFull = mprintf("%.*s\\%s.exe", i, zPath, zBinary);
    bExists = file_access(zFull, R_OK);
    if( bExists!=0 ){
      fossil_free(zFull);
      zFull = mprintf("%.*s\\%s.bat", i, zPath, zBinary);
      bExists = file_access(zFull, R_OK);
    }
#else
    while( zPath[0]==':' ) zPath++;
    for(i=0; zPath[i] && zPath[i]!=':'; i++){}
    zFull = mprintf("%.*s/%s", i, zPath, zBinary);
    bExists = file_access(zFull, X_OK);
#endif
    if( bExists==0 && ePrint ){
      fossil_print("%s\n", zFull);
    }
    fossil_free(zFull);
    if( bExists==0 ) return 1;
    if( bExists==0 ){
      if( ePrint<2 ) return 1;
      bFound = 1;
    }
    zPath += i;
  }
  return 0;
  return bFound;
}
#endif
#endif


/*
** Return the name of a command that will launch a web-browser.
*/
const char *fossil_web_browser(void){
  const char *zBrowser = 0;
#if defined(_WIN32)
  zBrowser = db_get("web-browser", "start \"\"");
#elif defined(__DARWIN__) || defined(__APPLE__) || defined(__HAIKU__)
  zBrowser = db_get("web-browser", "open");
#else
  zBrowser = db_get("web-browser", 0);
  if( zBrowser==0 ){
    static const char *const azBrowserProg[] =
        { "xdg-open", "gnome-open", "firefox", "google-chrome" };
    int i;
    zBrowser = "echo";
    for(i=0; i<count(azBrowserProg); i++){
      if( binaryOnPath(azBrowserProg[i]) ){
      if( fossil_app_on_path(azBrowserProg[i],0) ){
        zBrowser = azBrowserProg[i];
        break;
      }
    }
    zBrowser = mprintf("%s 2>/dev/null", zBrowser);
  }
#endif
  return zBrowser;
}

/*
** On non-Windows systems, calls nice(2) with the given level. Errors
Changes to src/vfile.c.
569
570
571
572
573
574
575
576

577
578
579
580
581
582
583
569
570
571
572
573
574
575

576
577
578
579
580
581
582
583







-
+







  if( depth==0 ){
    db_finalize(&ins);
  }
}

/*
** Scans the specified base directory for any directories within it, while
** keeping a count of how many files they each contains, either directly or
** keeping a count of how many files they each contain, either directly or
** indirectly.
**
** Subdirectories are scanned recursively.
** Omit files named in VFILE.
**
** Directories whose names begin with "." are omitted unless the SCAN_ALL
** flag is set.
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
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







-
+













-
+







      char *zPath;
      char *zUtf8;
      if( pEntry->d_name[0]=='.' ){
        if( (scanFlags & SCAN_ALL)==0 ) continue;
        if( pEntry->d_name[1]==0 ) continue;
        if( pEntry->d_name[1]=='.' && pEntry->d_name[2]==0 ) continue;
      }
      zOrigPath = mprintf("%s", blob_str(pPath));
      zOrigPath = fossil_strdup(blob_str(pPath));
      zUtf8 = fossil_path_to_utf8(pEntry->d_name);
      blob_appendf(pPath, "/%s", zUtf8);
      zPath = blob_str(pPath);
      if( glob_match(pIgnore1, &zPath[nPrefix+1]) ||
          glob_match(pIgnore2, &zPath[nPrefix+1]) ){
        /* do nothing */
#ifdef _DIRENT_HAVE_D_TYPE
      }else if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK)
          ? (file_isdir(zPath, eFType)==1) : (pEntry->d_type==DT_DIR) ){
#else
      }else if( file_isdir(zPath, eFType)==1 ){
#endif
        if( (scanFlags & SCAN_NESTED) || !vfile_top_of_checkout(zPath) ){
          char *zSavePath = mprintf("%s", zPath);
          char *zSavePath = fossil_strdup(zPath);
          int count = vfile_dir_scan(pPath, nPrefix, scanFlags, pIgnore1,
                                     pIgnore2, eFType);
          db_bind_text(&ins, ":file", &zSavePath[nPrefix+1]);
          db_bind_int(&ins, ":count", count);
          db_step(&ins);
          db_reset(&ins);
          fossil_free(zSavePath);
Changes to src/wiki.c.
19
20
21
22
23
24
25



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







+
+
+







** This file contains code to do formatting of wiki text.
*/
#include "config.h"
#include <assert.h>
#include <ctype.h>
#include "wiki.h"

#define has_prefix(literal_prfx, zStr) \
  (fossil_strncmp((zStr), "" literal_prfx, (sizeof literal_prfx)-1)==0)

/*
** Return true if the input string is a well-formed wiki page name.
**
** Well-formed wiki page names do not begin or end with whitespace,
** and do not contain tabs or other control characters and do not
** contain more than a single space character in a row.  Well-formed
** names must be between 1 and 100 characters in length, inclusive.
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

447
448
449
450
451
452
453
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







+









-
+




-
+


-
+

+
+
+













+







/* Return values from wiki_page_type() */
#if INTERFACE
# define WIKITYPE_UNKNOWN    (-1)
# define WIKITYPE_NORMAL     0
# define WIKITYPE_BRANCH     1
# define WIKITYPE_CHECKIN    2
# define WIKITYPE_TAG        3
# define WIKITYPE_TICKET     4
#endif

/*
** Figure out what type of wiki page we are dealing with.
*/
int wiki_page_type(const char *zPageName){
  if( db_get_boolean("wiki-about",1)==0 ){
    return WIKITYPE_NORMAL;
  }else
  if( sqlite3_strglob("checkin/*", zPageName)==0
  if( has_prefix("checkin/", zPageName)
   && db_exists("SELECT 1 FROM blob WHERE uuid=%Q",zPageName+8)
  ){
    return WIKITYPE_CHECKIN;
  }else
  if( sqlite3_strglob("branch/*", zPageName)==0 ){
  if( has_prefix("branch/", zPageName) ){
    return WIKITYPE_BRANCH;
  }else
  if( sqlite3_strglob("tag/*", zPageName)==0 ){
  if( has_prefix("tag/", zPageName) ){
    return WIKITYPE_TAG;
  }else
  if( has_prefix("ticket/", zPageName) ){
    return WIKITYPE_TICKET;
  }
  return WIKITYPE_NORMAL;
}

/*
** Returns a JSON-friendly string form of the integer value returned
** by wiki_page_type(zPageName).
*/
const char * wiki_page_type_name(const char *zPageName){
  switch(wiki_page_type(zPageName)){
    case WIKITYPE_CHECKIN: return "checkin";
    case WIKITYPE_BRANCH: return "branch";
    case WIKITYPE_TAG: return "tag";
    case WIKITYPE_TICKET: return "ticket";
    case WIKITYPE_NORMAL:
    default: return "normal";
  }
}

/*
** Add an appropriate style_header() for either the /wiki or /wikiedit page
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
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







+
+
+
+
+
+
+
+
+
+















+





+
+
+







        cgi_redirectf("%R/timeline?t=%t",zPageName);
      }else{
        style_header("Notes About Tag %h", zPageName);
        style_submenu_element("Tag Timeline","%R/timeline?t=%t",zPageName);
      }
      break;
    }
    case WIKITYPE_TICKET: {
      zPageName += 7;
      if( zExtra[0]==0 && !P("p") ){
        cgi_redirectf("%R/tktview/%s",zPageName);
      }else{
        style_header("Notes About Ticket %h", zPageName);
        style_submenu_element("Ticket","%R/tktview/%s",zPageName);
      }
      break;
    }
  }
  return eType;
}

/*
** Wiki pages with special names "branch/...", "checkin/...", and "tag/..."
** requires perm.Write privilege in addition to perm.WrWiki in order
** to write.  This function determines whether the extra perm.Write
** is required and available.  Return true if writing to the wiki page
** may proceed, and return false if permission is lacking.
*/
static int wiki_special_permission(const char *zPageName){
  if( strncmp(zPageName,"branch/",7)!=0
   && strncmp(zPageName,"checkin/",8)!=0
   && strncmp(zPageName,"tag/",4)!=0
   && strncmp(zPageName,"ticket/",7)!=0
  ){
    return 1;
  }
  if( db_get_boolean("wiki-about",1)==0 ){
    return 1;
  }
  if( strncmp(zPageName,"ticket/",7)==0 ){
    return g.perm.WrTkt;
  }
  return g.perm.Write;
}

/*
** WEBPAGE: wiki
**
550
551
552
553
554
555
556
557

558
559
560
561
562
563
564
572
573
574
575
576
577
578

579
580
581
582
583
584
585
586







-
+







  int isSandbox;
  unsigned submenuFlags = W_HELP;
  Blob wiki;
  Manifest *pWiki = 0;
  const char *zPageName;
  const char *zMimetype = 0;
  int isPopup = P("popup")!=0;
  char *zBody = mprintf("%s","<i>Empty Page</i>");
  char *zBody = fossil_strdup("<i>Empty Page</i>");
  int noSubmenu = P("nsm")!=0 || g.isHome;

  login_check_credentials();
  if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
  zPageName = P("name");
  (void)P("s")/*for cgi_check_for_malice(). "s" == search stringy*/;
  cgi_check_for_malice();
620
621
622
623
624
625
626
627
628


629
630

631
632
633
634
635
636
637
642
643
644
645
646
647
648


649
650
651

652
653
654
655
656
657
658
659







-
-
+
+

-
+







    blob_init(&wiki, zBody, -1);
    safe_html_context(DOCSRC_WIKI);
    wiki_render_by_mimetype(&wiki, zMimetype);
    blob_reset(&wiki);
  }
  manifest_destroy(pWiki);
  if( !isPopup ){
    char * zLabel = mprintf("<hr><h2><a href='%R/attachlist?name=%T'>"
                            "Attachments</a>:</h2><ul>",
    char * zLabel = mprintf("<h2><a href='%R/attachlist?page=%T'>"
                            "Attachments</a>:</h2>",
                            zPageName);
    attachment_list(zPageName, zLabel);
    attachment_list(zPageName, zLabel, 1);
    fossil_free(zLabel);
    document_emit_js(/*for optional pikchr support*/);
    style_finish_page();
  }
}

/*
1329
1330
1331
1332
1333
1334
1335
1336

1337
1338
1339
1340
1341
1342
1343
1351
1352
1353
1354
1355
1356
1357

1358
1359
1360
1361
1362
1363
1364
1365







-
+








  /* Status bar */
  CX("<div id='fossil-status-bar' "
     "title='Status message area. Double-click to clear them.'>"
     "Status messages will go here.</div>\n"
     /* will be moved into the tab container via JS */);

  CX("<div id='wikiedit-edit-status''>"
  CX("<div id='wikiedit-edit-status'>"
     "<span class='name'></span>"
     "<span class='links'></span>"
     "</div>");

  /* Main tab container... */
  CX("<div id='wikiedit-tabs' class='tab-container'>Loading...</div>");
  /* The .hidden class on the following tab elements is to help lessen
1966
1967
1968
1969
1970
1971
1972
1973


1974
1975
1976
1977
1978
1979
1980
1988
1989
1990
1991
1992
1993
1994

1995
1996
1997
1998
1999
2000
2001
2002
2003







-
+
+







    style_submenu_element("Active", "%R/wcontent");
  }else{
    style_submenu_element("All", "%R/wcontent?all=1");
  }
  cgi_check_for_malice();
  showCkBr = db_exists(
    "SELECT tag.tagname AS tn FROM tag JOIN tagxref USING(tagid) "
    "WHERE ( tn GLOB 'wiki-checkin/*' OR tn GLOB 'wiki-branch/*' ) "
    "WHERE ( tn GLOB 'wiki-checkin/*' OR tn GLOB 'wiki-branch/*' OR "
    "        tn GLOB 'wiki-tag/*'     OR tn GLOB 'wiki-ticket/*' ) "
    "  AND TYPEOF(tagxref.value+0)='integer'" );
  if( showCkBr ){
    showCkBr = P("showckbr")!=0;
    style_submenu_checkbox("showckbr", "Show associated wikis", 0, 0);
  }
  wiki_standard_submenu(W_ALL_BUT(W_LIST));
  db_prepare(&q, listAllWikiPages/*works-like:""*/);
1996
1997
1998
1999
2000
2001
2002
2003
2004




2005
2006
2007

2008
2009
2010

2011
2012
2013
2014
2015
2016
2017
2019
2020
2021
2022
2023
2024
2025


2026
2027
2028
2029
2030
2031

2032
2033
2034

2035
2036
2037
2038
2039
2040
2041
2042







-
-
+
+
+
+


-
+


-
+







    double rWmtime = db_column_double(&q, 3);
    sqlite3_int64 iMtime = (sqlite3_int64)(rWmtime*86400.0);
    char *zAge;
    int wcnt = db_column_int(&q, 4);
    char *zWDisplayName;

    if( !showCkBr &&
        (sqlite3_strglob("checkin/*", zWName)==0 ||
         sqlite3_strglob("branch/*", zWName)==0) ){
        (has_prefix("checkin/", zWName) ||
         has_prefix("branch/",  zWName) ||
         has_prefix("tag/",     zWName) ||
         has_prefix("ticket/",  zWName) )){
      continue;
    }
    if( sqlite3_strglob("checkin/*", zWName)==0 ){
    if( has_prefix("checkin/",zWName) || has_prefix("ticket/",zWName) ){
      zWDisplayName = mprintf("%.25s...", zWName);
    }else{
      zWDisplayName = mprintf("%s", zWName);
      zWDisplayName = fossil_strdup(zWName);
    }
    if( wrid==0 ){
      if( !showAll ) continue;
      @ <tr><td data-sortkey="%h(zSort)">\
      @ %z(href("%R/whistory?name=%T",zWName))<s>%h(zWDisplayName)</s></a></td>
    }else{
      @ <tr><td data-sortkey="%h(zSort)">\
2246
2247
2248
2249
2250
2251
2252
2253

2254
2255
2256
2257
2258
2259
2260
2271
2272
2273
2274
2275
2276
2277

2278
2279
2280
2281
2282
2283
2284
2285







-
+







**         -t|--technote               Technotes will be listed instead of
**                                     pages. The technotes will be in order
**                                     of timestamp with the most recent
**                                     first.
**         -a|--show-associated        Show wiki pages associated with
**                                     check-ins and branches.
**         -s|--show-technote-ids      The id of the tech note will be listed
**                                     along side the timestamp. The tech note
**                                     alongside the timestamp. The tech note
**                                     id will be the first word on each line.
**                                     This option only applies if the
**                                     --technote option is also specified.
**
** DATETIME may be "now" or "YYYY-MM-DDTHH:MM:SS.SSS". If in
** year-month-day form, it may be truncated, the "T" may be replaced by
** a space, and it may also name a timezone offset from UTC as "-HH:MM"
2499
2500
2501
2502
2503
2504
2505
2506
2507




2508
2509
2510
2511
2512
2513
2514
2524
2525
2526
2527
2528
2529
2530


2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541







-
-
+
+
+
+







    while( db_step(&q)==SQLITE_ROW ){
      const char *zName = db_column_text(&q, 0);
      const int wrid = db_column_int(&q, 2);
      if(!showAll && !wrid){
        continue;
      }
      if( !showCkBr &&
          (sqlite3_strglob("checkin/*", zName)==0 ||
           sqlite3_strglob("branch/*", zName)==0) ){
          (has_prefix("checkin/", zName) ||
           has_prefix("branch/",  zName) ||
           has_prefix("tag/",     zName) ||
           has_prefix("ticket/",  zName) ) ){
        continue;
      }
      if( showIds ){
        const char *zUuid = db_column_text(&q, 1);
        fossil_print("%s ",zUuid);
      }
      fossil_print( "%s\n",zName);
2549
2550
2551
2552
2553
2554
2555
2556

2557
2558
2559
2560
2561
2562


















2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574

2575
2576
2577
2578
2579
2580
2581
2576
2577
2578
2579
2580
2581
2582

2583
2584
2585
2586
2587


2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616

2617
2618
2619
2620
2621
2622
2623
2624







-
+




-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+











-
+







    @ <div class="section accordion">About %s(zPrefix) %h(zName)</div>
  }
}

/*
** Add an "Wiki" button in a submenu that links to the read-wiki page.
*/
static void wiki_submenu_to_edit_wiki(
static void wiki_submenu_to_read_wiki(
  const char *zPrefix,   /* "branch", "tag", or "checkin" */
  const char *zName,     /* Name of the object */
  unsigned int mFlags    /* Zero or more WIKIASSOC_* flags */
){
  if( g.perm.RdWiki && (mFlags & WIKIASSOC_MENU_READ)!=0 ){
    style_submenu_element("Wiki", "%R/wikiedit?name=%s/%t", zPrefix, zName);
  if( g.perm.RdWiki && (mFlags & WIKIASSOC_MENU_READ)!=0
      && 0!=fossil_strcmp("branch", zPrefix)
      /* ^^^ https://fossil-scm.org/forum/forumpost/ff453de2f30791dd */
  ){
    style_submenu_element("Wiki", "%R/wiki?name=%s/%t", zPrefix, zName);
  }
}

/*
** Add an "Edit Wiki" button in a submenu that links to the edit-wiki page.
*/
static void wiki_submenu_to_edit_wiki(
  const char *zPrefix,   /* "branch", "tag", or "checkin" */
  const char *zName,     /* Name of the object */
  unsigned int mFlags   /* Zero or more WIKIASSOC_* flags */
){
  if( g.perm.WrWiki && (mFlags & WIKIASSOC_MENU_WRITE)!=0 ){
    style_submenu_element("Edit Wiki", "%R/wikiedit?name=%s/%t", zPrefix, zName);
  }
}

/*
** Check to see if there exists a wiki page with a name zPrefix/zName.
** If there is, then render a <div class='section'>..</div> and
** return true.
**
** If there is no such wiki page, return false.
*/
int wiki_render_associated(
  const char *zPrefix,   /* "branch", "tag", or "checkin" */
  const char *zPrefix,   /* "branch", "tag", "ticket", or "checkin" */
  const char *zName,     /* Name of the object */
  unsigned int mFlags    /* Zero or more WIKIASSOC_* flags */
){
  int rid;
  Manifest *pWiki;
  if( !db_get_boolean("wiki-about",1) ) return 0;
  rid = db_int(0,
2599
2600
2601
2602
2603
2604
2605

2606
2607
2608
2609
2610
2611
2612
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656







+







    blob_init(&markdown, pWiki->zWiki, -1);
    markdown_to_html(&markdown, &title, &tail);
    if( blob_size(&title) ){
      @ <div class="section accordion">%h(blob_str(&title))</div>
    }else{
      wiki_section_label(zPrefix, zName, mFlags);
    }
    wiki_submenu_to_read_wiki(zPrefix, zName, mFlags);
    wiki_submenu_to_edit_wiki(zPrefix, zName, mFlags);
    @ <div class="accordion_panel">
    safe_html_context(DOCSRC_WIKI);
    safe_html(&tail);
    convert_href_and_output(&tail);
    @ </div>
    blob_reset(&tail);
Changes to src/wikiformat.c.
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
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







-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







#include <assert.h>
#include "wikiformat.h"

#if INTERFACE
/*
** Allowed wiki transformation operations
*/
#define WIKI_HTMLONLY       0x001  /* HTML markup only.  No wiki */
#define WIKI_INLINE         0x002  /* Do not surround with <p>..</p> */
#define WIKI_NOBLOCK        0x004  /* No block markup of any kind */
#define WIKI_BUTTONS        0x008  /* Allow sub-menu buttons */
#define WIKI_NOBADLINKS     0x010  /* Ignore broken hyperlinks */
#define WIKI_LINKSONLY      0x020  /* No markup.  Only decorate links */
#define WIKI_NEWLINE        0x040  /* Honor \n - break lines at each \n */
#define WIKI_MARKDOWNLINKS  0x080  /* Resolve hyperlinks as in markdown */
#define WIKI_SAFE           0x100  /* Make the result safe for embedding */
#define WIKI_TARGET_BLANK   0x200  /* Hyperlinks go to a new window */
#define WIKI_NOBRACKET      0x400  /* Omit extra [..] around hyperlinks */
#endif
#define WIKI_HTMLONLY      0x0001  /* HTML markup only.  No wiki */
#define WIKI_INLINE        0x0002  /* Do not surround with <p>..</p> */
/* available for reuse:    0x0004  ---  formerly WIKI_NOBLOCK */
#define WIKI_BUTTONS       0x0008  /* Allow sub-menu buttons */
#define WIKI_NOBADLINKS    0x0010  /* Ignore broken hyperlinks */
#define WIKI_LINKSONLY     0x0020  /* No markup.  Only decorate links */
#define WIKI_NEWLINE       0x0040  /* Honor \n - break lines at each \n */
#define WIKI_MARKDOWNLINKS 0x0080  /* Resolve hyperlinks as in markdown */
#define WIKI_SAFE          0x0100  /* Make the result safe for embedding */
#define WIKI_TARGET_BLANK  0x0200  /* Hyperlinks go to a new window */
#define WIKI_NOBRACKET     0x0400  /* Omit extra [..] around hyperlinks */
#define WIKI_ADMIN         0x0800  /* Ignore g.perm.Hyperlink */
#define WIKI_MARK          0x1000  /* Add <mark>..</mark> around problems */

/*
** Return values from wiki_convert
*/
#define RENDER_LINK       0x0001  /* One or more hyperlinks rendered */
#define RENDER_ENTITY     0x0002  /* One or more HTML entities (ex: &lt;) */
#define RENDER_TAG        0x0004  /* One or more HTML tags */
#define RENDER_BLOCKTAG   0x0008  /* One or more HTML block tags (ex: <p>) */
#define RENDER_BLOCK      0x0010  /* Block wiki (paragraphs, etc.) */
#define RENDER_MARK       0x0020  /* Output contains <mark>..</mark> */
#define RENDER_BADLINK    0x0100  /* Bad hyperlink syntax seen */
#define RENDER_BADTARGET  0x0200  /* Bad hyperlink target */
#define RENDER_BADTAG     0x0400  /* Bad HTML tag or tag syntax */
#define RENDER_BADENTITY  0x0800  /* Bad HTML entity syntax */
#define RENDER_BADHTML    0x1000  /* Bad HTML seen */
#define RENDER_ERROR      0x8000  /* Some other kind of error */
/* Composite values: */
#define RENDER_ANYERROR   0x9f00  /* Mask for any kind of error */

#endif /* INTERFACE */


/*
** These are the only markup attributes allowed.
*/
enum allowed_attr_t {
  ATTR_ALIGN = 1,
442
443
444
445
446
447
448
449
450

451
452
453
454
455
456
457
458

459
460
461
462
463
464
465
463
464
465
466
467
468
469


470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486







-
-
+








+







** State flags.  Save the lower 16 bits for the WIKI_* flags.
*/
#define AT_NEWLINE          0x0010000  /* At start of a line */
#define AT_PARAGRAPH        0x0020000  /* At start of a paragraph */
#define ALLOW_WIKI          0x0040000  /* Allow wiki markup */
#define ALLOW_LINKS         0x0080000  /* Allow [...] hyperlinks */
#define FONT_MARKUP_ONLY    0x0100000  /* Only allow MUTYPE_FONT markup */
#define INLINE_MARKUP_ONLY  0x0200000  /* Allow only "inline" markup */
#define IN_LIST             0x0400000  /* Within wiki <ul> or <ol> */
#define IN_LIST             0x0200000  /* Within wiki <ul> or <ol> */

/*
** Current state of the rendering engine
*/
typedef struct Renderer Renderer;
struct Renderer {
  Blob *pOut;                 /* Output appended to this blob */
  int state;                  /* Flag that govern rendering */
  int mRender;                /* Mask of RENDER_* values to return */
  unsigned renderFlags;       /* Flags from the client */
  int wikiList;               /* Current wiki list type */
  int inVerbatim;             /* True in <verbatim> mode */
  int preVerbState;           /* Value of state prior to verbatim */
  int wantAutoParagraph;      /* True if a <p> is desired */
  int inAutoParagraph;        /* True if within an automatic paragraph */
  int pikchrHtmlFlags;        /* Flags for pikchr_to_html() */
543
544
545
546
547
548
549
550

551
552
553
554
555
556
557
564
565
566
567
568
569
570

571
572
573
574
575
576
577
578







-
+







**
**      <
**      &
**      \n
**      [
**
** The "[" is only considered if flags contain ALLOW_LINKS or ALLOW_WIKI.
** The "\n" is only considered interesting if the flags constains ALLOW_WIKI.
** The "\n" is only considered interesting if the flags contains ALLOW_WIKI.
*/
static int textLength(const char *z, int flags){
  const char *zReject;
  if( flags & ALLOW_WIKI ){
    zReject = "<&[\n";
  }else if( flags & ALLOW_LINKS ){
    zReject = "<&[";
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
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







+



+
+
+
+
+
+
+
+




-
-
-
-







** characters in that token.  Write the token type into *pTokenType.
*/
static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){
  int n;
  if( z[0]=='<' ){
    n = html_tag_length(z);
    if( n>0 ){
      p->mRender |= RENDER_TAG;
      *pTokenType = TOKEN_MARKUP;
      return n;
    }else{
      p->mRender |= RENDER_BADTAG;
      *pTokenType = TOKEN_CHARACTER;
      return 1;
    }
  }
  if( z[0]=='&' ){
    p->mRender |= RENDER_ENTITY;
    if( (p->inVerbatim || !isElement(z)) ){
      *pTokenType = TOKEN_CHARACTER;
      return 1;
    }
  }
  if( z[0]=='&' && (p->inVerbatim || !isElement(z)) ){
    *pTokenType = TOKEN_CHARACTER;
    return 1;
  }
  if( (p->state & ALLOW_WIKI)!=0 ){
    if( z[0]=='\n' ){
      n = paragraphBreakLength(z);
      if( n>0 ){
        *pTokenType = TOKEN_PARAGRAPH;
        return n;
      }else{
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
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







-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+













-
-
-
+
+
+
+
+
+
+
+
+
+







    if( (p->state & AT_PARAGRAPH)!=0 && fossil_isspace(z[0]) ){
      n = indentLength(z);
      if( n>0 ){
        *pTokenType = TOKEN_INDENT;
        return n;
      }
    }
    if( z[0]=='[' && (n = linkLength(z))>0 ){
      *pTokenType = TOKEN_LINK;
      return n;
    }
  }else if( (p->state & ALLOW_LINKS)!=0 && z[0]=='[' && (n = linkLength(z))>0 ){
    *pTokenType = TOKEN_LINK;
    return n;
    if( z[0]=='[' ){
      if( (n = linkLength(z))>0 ){
        *pTokenType = TOKEN_LINK;
        return n;
      }else if( p->state & WIKI_MARK ){
        blob_append_string(p->pOut, "<mark>");
        p->mRender |= RENDER_BADLINK|RENDER_MARK;
      }else{
        p->mRender |= RENDER_BADLINK;
      }
    }
  }else if( (p->state & ALLOW_LINKS)!=0 && z[0]=='[' ){
    if( (n = linkLength(z))>0 ){
      *pTokenType = TOKEN_LINK;
      return n;
    }else if( p->state & WIKI_MARK ){
      blob_append_string(p->pOut, "<mark>");
      p->mRender |= RENDER_BADLINK|RENDER_MARK;
    }else{
      p->mRender |= RENDER_BADLINK;
    }
  }
  *pTokenType = TOKEN_TEXT;
  return 1 + textLength(z+1, p->state);
}

/*
** Parse only Wiki links, return everything else as TOKEN_RAW.
**
** z points to the start of a token.  Return the number of
** characters in that token. Write the token type into *pTokenType.
*/
static int nextRawToken(const char *z, Renderer *p, int *pTokenType){
  int n;
  if( z[0]=='[' && (n = linkLength(z))>0 ){
    *pTokenType = TOKEN_LINK;
    return n;
  if( z[0]=='[' ){
    if( (n = linkLength(z))>0 ){
      *pTokenType = TOKEN_LINK;
      return n;
   }else if( p->state & WIKI_MARK ){
     blob_append_string(p->pOut, "<mark>");
     p->mRender |= RENDER_BADLINK|RENDER_MARK;
   }else{
      p->mRender |= RENDER_BADLINK;
    }
  }
  *pTokenType = TOKEN_RAW;
  return 1 + textLength(z+1, p->state);
}

/*
** A single markup is parsed into an instance of the following
1246
1247
1248
1249
1250
1251
1252




1253
1254

1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267

1268
1269
1270
1271
1272
1273
1274
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







+
+
+
+

-
+













+







**
**    [WikiPageName]
**    [wiki:WikiPageName]
**
**    [2010-02-27 07:13]
**
**    [InterMap:Link]  ->  Interwiki link
**
** The return value is a mask of RENDER_* values indicating what happened.
** Probably the return value is 0 on success and RENDER_BADTARGET or
** RENDER_BADLINK if there are problems.
*/
void wiki_resolve_hyperlink(
int wiki_resolve_hyperlink(
  Blob *pOut,             /* Write the HTML output here */
  int mFlags,             /* Rendering option flags */
  const char *zTarget,    /* Hyperlink target; text within [...] */
  char *zClose,           /* Write hyperlink closing text here */
  int nClose,             /* Bytes available in zClose[] */
  const char *zOrig,      /* Complete document text */
  const char *zTitle      /* Title of the link */
){
  const char *zTerm = "</a>";
  const char *z;
  char *zExtra = 0;
  const char *zExtraNS = 0;
  char *zRemote = 0;
  int rc = 0;

  if( zTitle ){
    zExtra = mprintf(" title='%h'", zTitle);
    zExtraNS = zExtra+1;
  }else if( mFlags & WIKI_TARGET_BLANK ){
    zExtra = mprintf(" target='_blank'");
    zExtraNS = zExtra+1;
1314
1315
1316
1317
1318
1319
1320




1321
1322
1323
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
1359
1360

1361
1362
1363

1364
1365
1366
1367















1368
1369
1370
1371
1372
1373
1374
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
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
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452







+
+
+
+




+
-
+



















-
+






+
+
+
+









+



+




+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







          blob_appendf(pOut, "%s", zLB);
          zTerm = "]";
        }
      }
    }else if( !in_this_repo(zTarget) ){
      if( (mFlags & (WIKI_LINKSONLY|WIKI_NOBADLINKS))!=0 ){
        zTerm = "";
      }else if( (mFlags & WIKI_MARK)!=0 ){
        blob_appendf(pOut, "<mark>%s", zLB);
        zTerm = "]</mark>";
        rc |= RENDER_MARK;
      }else{
        blob_appendf(pOut, "<span class=\"brokenlink\">%s", zLB);
        zTerm = "]</span>";
      }
      rc |= RENDER_BADTARGET;
    }else if( g.perm.Hyperlink ){
    }else if( g.perm.Hyperlink || (mFlags & WIKI_ADMIN)!=0 ){
      blob_appendf(pOut, "%z%s",xhref(zExtraNS, "%R/info/%s", zTarget), zLB);
      zTerm = "]</a>";
    }else{
      zTerm = "";
    }
    if( zTerm[0]==']' && (mFlags & WIKI_NOBRACKET)!=0 ) zTerm++;
  }else if( (zRemote = interwiki_url(zTarget))!=0 ){
    blob_appendf(pOut, "<a href=\"%z\"%s>", zRemote, zExtra);
    zTerm = "</a>";
  }else if( (z = validWikiPageName(mFlags, zTarget))!=0 ){
    /* The link is to a valid wiki page name */
    const char *zOverride = wiki_is_overridden(zTarget);
    if( zOverride ){
      blob_appendf(pOut, "<a href=\"%R/info/%S\"%s>", zOverride, zExtra);
    }else{
      blob_appendf(pOut, "<a href=\"%R/wiki?name=%T\"%s>", z, zExtra);
    }
  }else if( strlen(zTarget)>=10 && fossil_isdigit(zTarget[0]) && zTarget[4]=='-'
            && db_int(0, "SELECT datetime(%Q) NOT NULL", zTarget) ){
    /* Dates or date-and-times in ISO8610 resolve to a link to the
    /* Dates or date-and-times in ISO8601 resolve to a link to the
    ** timeline for that date */
    blob_appendf(pOut, "<a href=\"%R/timeline?c=%T\"%s>", zTarget, zExtra);
  }else if( mFlags & WIKI_MARKDOWNLINKS ){
    /* If none of the above, and if rendering links for markdown, then
    ** create a link to the literal text of the target */
    blob_appendf(pOut, "<a href=\"%h\"%s>", zTarget, zExtra);
  }else if( mFlags & WIKI_MARK ){
    blob_appendf(pOut, "<mark>[");
    zTerm = "]</mark>";
    rc |= RENDER_BADTARGET|RENDER_MARK;
  }else if( zOrig && zTarget>=&zOrig[2]
        && zTarget[-1]=='[' && !fossil_isspace(zTarget[-2]) ){
    /* If the hyperlink markup is not preceded by whitespace, then it
    ** is probably a C-language subscript or similar, not really a
    ** hyperlink.  Just ignore it. */
    zTerm = "";
  }else if( (mFlags & (WIKI_NOBADLINKS|WIKI_LINKSONLY))!=0 ){
    /* Also ignore the link if various flags are set */
    zTerm = "";
    rc |= RENDER_BADTARGET;
  }else{
    blob_appendf(pOut, "<span class=\"brokenlink\">[%h]", zTarget);
    zTerm = "</span>";
    rc |= RENDER_BADTARGET;
  }
  if( zExtra ) fossil_free(zExtra);
  assert( (int)strlen(zTerm)<nClose );
  sqlite3_snprintf(nClose, zClose, "%s", zTerm);
  return rc;
}

/*
** Check zTarget to see if it looks like a valid hyperlink target.
** Return true if it does seem valid and false if not.
*/
int wiki_valid_link_target(char *zTarget){
  char zClose[30];
  Blob notUsed;
  blob_init(&notUsed, 0, 0);
  wiki_resolve_hyperlink(&notUsed, WIKI_NOBADLINKS|WIKI_ADMIN,
       zTarget, zClose, sizeof(zClose)-1, 0, 0);
  blob_reset(&notUsed);
  return zClose[0]!=0;
}

/*
** Check to see if the given parsed markup is the correct
** </verbatim> tag.
*/
static int endVerbatim(Renderer *p, ParsedMarkup *pMarkup){
1448
1449
1450
1451
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
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
1517
1518
1519

1520
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
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
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

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
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
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







-

















-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-













-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-



-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-



-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-



-
-
-
-
-
-
+
+
+
+
+
+
-




+
+
+
+

+


+


+
+
+
+
+
+
+
+
+












+
















-
+







**
** This routine will probably modify the content of z[].
*/
static void wiki_render(Renderer *p, char *z){
  int tokenType;
  ParsedMarkup markup;
  int n;
  int inlineOnly = (p->state & INLINE_MARKUP_ONLY)!=0;
  int wikiHtmlOnly = (p->state & (WIKI_HTMLONLY | WIKI_LINKSONLY))!=0;
  int linksOnly = (p->state & WIKI_LINKSONLY)!=0;
  char *zOrig = z;

  /* Make sure the attribute constants and names still align
  ** following changes in the attribute list. */
  assert( fossil_strcmp(aAttribute[ATTR_WIDTH].zName, "width")==0 );

  while( z[0] ){
    if( wikiHtmlOnly ){
      n = nextRawToken(z, p, &tokenType);
    }else{
      n = nextWikiToken(z, p, &tokenType);
    }
    p->state &= ~(AT_NEWLINE|AT_PARAGRAPH);
    switch( tokenType ){
      case TOKEN_PARAGRAPH: {
        if( inlineOnly ){
          /* blob_append_string(p->pOut, " &para; "); */
          blob_append_string(p->pOut, " &nbsp;&nbsp; ");
        }else{
          if( p->wikiList ){
            popStackToTag(p, p->wikiList);
            p->wikiList = 0;
          }
          endAutoParagraph(p);
          blob_append_string(p->pOut, "\n\n");
          p->wantAutoParagraph = 1;
        if( p->wikiList ){
          popStackToTag(p, p->wikiList);
          p->wikiList = 0;
        }
        endAutoParagraph(p);
        blob_append_string(p->pOut, "\n\n");
        p->wantAutoParagraph = 1;
        }
        p->state |= AT_PARAGRAPH|AT_NEWLINE;
        break;
      }
      case TOKEN_NEWLINE: {
        if( p->renderFlags & WIKI_NEWLINE ){
          blob_append_string(p->pOut, "<br>\n");
        }else{
          blob_append_string(p->pOut, "\n");
        }
        p->state |= AT_NEWLINE;
        break;
      }
      case TOKEN_BUL_LI: {
        if( inlineOnly ){
        p->mRender |= RENDER_BLOCK;
          blob_append_string(p->pOut, " &bull; ");
        }else{
          if( p->wikiList!=MARKUP_UL ){
            if( p->wikiList ){
              popStackToTag(p, p->wikiList);
            }
            endAutoParagraph(p);
            pushStack(p, MARKUP_UL);
            blob_append_string(p->pOut, "<ul>");
            p->wikiList = MARKUP_UL;
          }
          popStackToTag(p, MARKUP_LI);
          startAutoParagraph(p);
          pushStack(p, MARKUP_LI);
          blob_append_string(p->pOut, "<li>");
        if( p->wikiList!=MARKUP_UL ){
          if( p->wikiList ){
            popStackToTag(p, p->wikiList);
          }
          endAutoParagraph(p);
          pushStack(p, MARKUP_UL);
          blob_append_string(p->pOut, "<ul>");
          p->wikiList = MARKUP_UL;
        }
        popStackToTag(p, MARKUP_LI);
        startAutoParagraph(p);
        pushStack(p, MARKUP_LI);
        blob_append_string(p->pOut, "<li>");
        }
        break;
      }
      case TOKEN_NUM_LI: {
        if( inlineOnly ){
          blob_append_string(p->pOut, " # ");
        p->mRender |= RENDER_BLOCK;
        }else{
          if( p->wikiList!=MARKUP_OL ){
            if( p->wikiList ){
              popStackToTag(p, p->wikiList);
            }
            endAutoParagraph(p);
            pushStack(p, MARKUP_OL);
            blob_append_string(p->pOut, "<ol>");
            p->wikiList = MARKUP_OL;
          }
          popStackToTag(p, MARKUP_LI);
          startAutoParagraph(p);
          pushStack(p, MARKUP_LI);
          blob_append_string(p->pOut, "<li>");
        if( p->wikiList!=MARKUP_OL ){
          if( p->wikiList ){
            popStackToTag(p, p->wikiList);
          }
          endAutoParagraph(p);
          pushStack(p, MARKUP_OL);
          blob_append_string(p->pOut, "<ol>");
          p->wikiList = MARKUP_OL;
        }
        popStackToTag(p, MARKUP_LI);
        startAutoParagraph(p);
        pushStack(p, MARKUP_LI);
        blob_append_string(p->pOut, "<li>");
        }
        break;
      }
      case TOKEN_ENUM: {
        if( inlineOnly ){
        p->mRender |= RENDER_BLOCK;
          blob_appendf(p->pOut, " (%d) ", atoi(z));
        }else{
          if( p->wikiList!=MARKUP_OL ){
            if( p->wikiList ){
              popStackToTag(p, p->wikiList);
            }
            endAutoParagraph(p);
            pushStack(p, MARKUP_OL);
            blob_append_string(p->pOut, "<ol>");
            p->wikiList = MARKUP_OL;
          }
          popStackToTag(p, MARKUP_LI);
          startAutoParagraph(p);
          pushStack(p, MARKUP_LI);
          blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z));
        if( p->wikiList!=MARKUP_OL ){
          if( p->wikiList ){
            popStackToTag(p, p->wikiList);
          }
          endAutoParagraph(p);
          pushStack(p, MARKUP_OL);
          blob_append_string(p->pOut, "<ol>");
          p->wikiList = MARKUP_OL;
        }
        popStackToTag(p, MARKUP_LI);
        startAutoParagraph(p);
        pushStack(p, MARKUP_LI);
        blob_appendf(p->pOut, "<li value=\"%d\">", atoi(z));
        }
        break;
      }
      case TOKEN_INDENT: {
        if( !inlineOnly ){
          assert( p->wikiList==0 );
          pushStack(p, MARKUP_BLOCKQUOTE);
          blob_append_string(p->pOut, "<blockquote>");
          p->wantAutoParagraph = 0;
          p->wikiList = MARKUP_BLOCKQUOTE;
        p->mRender |= RENDER_BLOCK;
        assert( p->wikiList==0 );
        pushStack(p, MARKUP_BLOCKQUOTE);
        blob_append_string(p->pOut, "<blockquote>");
        p->wantAutoParagraph = 0;
        p->wikiList = MARKUP_BLOCKQUOTE;
        }
        break;
      }
      case TOKEN_CHARACTER: {
        startAutoParagraph(p);
        if( p->state & WIKI_MARK ){
          blob_append_string(p->pOut, "<mark>");
          p->mRender |= RENDER_MARK;
        }
        if( z[0]=='<' ){
          p->mRender |= RENDER_BADTAG;
          blob_append_string(p->pOut, "&lt;");
        }else if( z[0]=='&' ){
          p->mRender |= RENDER_BADENTITY;
          blob_append_string(p->pOut, "&amp;");
        }
        if( p->state & WIKI_MARK ){
          if( fossil_isalnum(z[1]) || (z[1]=='/' && fossil_isalnum(z[2])) ){
            int kk;
            for(kk=2; fossil_isalnum(z[kk]); kk++){}
            blob_append(p->pOut, &z[1], kk-1);
            n = kk;
          }
          blob_append_string(p->pOut, "</mark>");
        }
        break;
      }
      case TOKEN_LINK: {
        char *zTarget;
        char *zDisplay = 0;
        int i, j;
        int savedState;
        char zClose[20];
        char cS1 = 0;
        int iS1 = 0;

        startAutoParagraph(p);
        p->mRender |= RENDER_LINK;
        zTarget = &z[1];
        for(i=1; z[i] && z[i]!=']'; i++){
          if( z[i]=='|' && zDisplay==0 ){
            zDisplay = &z[i+1];
            for(j=i; j>0 && fossil_isspace(z[j-1]); j--){}
            iS1 = j;
            cS1 = z[j];
            z[j] = 0;
          }
        }
        z[i] = 0;
        if( zDisplay==0 ){
          zDisplay = zTarget + interwiki_removable_prefix(zTarget);
        }else{
          while( fossil_isspace(*zDisplay) ) zDisplay++;
        }
        wiki_resolve_hyperlink(p->pOut, p->state,
        p->mRender |= wiki_resolve_hyperlink(p->pOut, p->state,
                               zTarget, zClose, sizeof(zClose), zOrig, 0);
        if( linksOnly || zClose[0]==0 || p->inVerbatim ){
          if( cS1 ) z[iS1] = cS1;
          if( zClose[0]!=']' ){
            blob_appendf(p->pOut, "[%h]%s", zTarget, zClose);
          }else{
            blob_appendf(p->pOut, "%h%s", zTarget, zClose);
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
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
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
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815

1816
1817
1818
1819
1820
1821
1822
1823







+


+
+
+
+
+
+
-
+
-
+
+

















-
-
-
-
-
-

















-
+







          }
        }else

        /* Render invalid markup literally.  The markup appears in the
        ** final output as plain text.
        */
        if( markup.iCode==MARKUP_INVALID ){
          p->mRender |= RENDER_BADTAG;
          unparseMarkup(&markup);
          startAutoParagraph(p);
          if( p->state & WIKI_MARK ){
            p->mRender |= RENDER_MARK;
            blob_append_string(p->pOut, "<mark>");
            htmlize_to_blob(p->pOut, z, n);
            blob_append_string(p->pOut, "</mark>");
          }else{
          blob_append_string(p->pOut, "&lt;");
            blob_append_string(p->pOut, "&lt;");
          n = 1;
            htmlize_to_blob(p->pOut, z+1, n-1);
          }
        }else

        /* If the markup is not font-change markup ignore it if the
        ** font-change-only flag is set.
        */
        if( (markup.iType&MUTYPE_FONT)==0 && (p->state & FONT_MARKUP_ONLY)!=0 ){
          /* Do nothing */
        }else

        if( markup.iCode==MARKUP_NOWIKI ){
          if( markup.endTag ){
            p->state |= ALLOW_WIKI;
          }else{
            p->state &= ~ALLOW_WIKI;
          }
        }else

        /* Ignore block markup for in-line rendering.
        */
        if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){
          /* Do nothing */
        }else

        /* Generate end-tags */
        if( markup.endTag ){
          popStackToTag(p, markup.iCode);
        }else

        /* Push <div> markup onto the stack together with the id=ID attribute.
        */
        if( markup.iCode==MARKUP_DIV && (mAttr & ATTR_ID)!=0 ){
          pushStackWithId(p, markup.iCode, markupId(&markup),
                          (p->state & ALLOW_WIKI)!=0);
        }else

        /* Enter <verbatim> processing.  With verbatim enabled, all other
        ** markup other than the corresponding end-tag with the same ID is
        ** ignored.
        */
        if( markup.iCode==MARKUP_VERBATIM ){
          int ii; //, vAttrDidAppend=0;
          int ii; /*, vAttrDidAppend=0;*/
          const char *zClass = 0;
          p->zVerbatimId = 0;
          p->inVerbatim = 1;
          p->preVerbState = p->state;
          p->state &= ~ALLOW_WIKI;
          for(ii=0; ii<markup.nAttr; ii++){
            if( markup.aAttr[ii].iACode == ATTR_ID ){
1797
1798
1799
1800
1801
1802
1803

1804
1805
1806
1807
1808
1809
1810
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891







+







            pushStack(p, markup.iCode);
          }
        }else
        {
          if( markup.iType==MUTYPE_FONT ){
            startAutoParagraph(p);
          }else if( markup.iType==MUTYPE_BLOCK || markup.iType==MUTYPE_LIST ){
            p->mRender |= RENDER_BLOCKTAG;
            p->wantAutoParagraph = 0;
          }
          if(   markup.iCode==MARKUP_HR
             || markup.iCode==MARKUP_H1
             || markup.iCode==MARKUP_H2
             || markup.iCode==MARKUP_H3
             || markup.iCode==MARKUP_H4
1827
1828
1829
1830
1831
1832
1833


1834
1835

1836
1837
1838
1839
1840
1841
1842
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917

1918
1919
1920
1921
1922
1923
1924
1925







+
+

-
+








/*
** Transform the text in the pIn blob.  Write the results
** into the pOut blob.  The pOut blob should already be
** initialized.  The output is merely appended to pOut.
** If pOut is NULL, then the output is appended to the CGI
** reply.
**
** Return a mask of RENDER_ flags indicating what happened.
*/
void wiki_convert(Blob *pIn, Blob *pOut, int flags){
int wiki_convert(Blob *pIn, Blob *pOut, int flags){
  Renderer renderer;

  memset(&renderer, 0, sizeof(renderer));
  renderer.renderFlags = flags;
  renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH|flags;
  if( flags & WIKI_INLINE ){
    renderer.wantAutoParagraph = 0;
1856
1857
1858
1859
1860
1861
1862

1863
1864
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
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
1938
1939
1940
1941
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
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
1994
1995
1996
1997



1998
1999
2000
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
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
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083







+












+
+

+

+
+

-
-
+
+
-




+
+
+
+
+





-
+



+
+
+
+


-
+

+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+










-
+

-
+
+




-
+






+










+
+
+
+
+
+
+







  wiki_render(&renderer, blob_str(pIn));
  endAutoParagraph(&renderer);
  while( renderer.nStack ){
    popStack(&renderer);
  }
  blob_append_char(renderer.pOut, '\n');
  free(renderer.aStack);
  return renderer.mRender;
}

/*
** COMMAND: test-wiki-render
**
** Usage: %fossil test-wiki-render FILE [OPTIONS]
**
** Translate the input FILE from Fossil-wiki into HTML and write
** the resulting HTML on standard output.
**
** Options:
**    --buttons        Set the WIKI_BUTTONS flag
**    --dark-pikchr    Render pikchrs in dark mode
**    --flow           Render as text using comment_format
**    --htmlonly       Set the WIKI_HTMLONLY flag
**    --inline         Set the WIKI_INLINE flag
**    --linksonly      Set the WIKI_LINKSONLY flag
**    -m TEXT          Use TEXT in place of the content of FILE
**    --mark           Add <mark>...</mark> around problems
**    --nobadlinks     Set the WIKI_NOBADLINKS flag
**    --inline         Set the WIKI_INLINE flag
**    --noblock        Set the WIKI_NOBLOCK flag
**    --text           Run the output through html_to_plaintext()
**    --type           Break down the return code from wiki_convert()
**    --dark-pikchr    Render pikchrs in dark mode
*/
void test_wiki_render(void){
  Blob in, out;
  int flags = 0;
  int bText;
  int bFlow = 0;
  int showType = 0;
  int mType;
  const char *zIn;
  if( find_option("buttons",0,0)!=0 ) flags |= WIKI_BUTTONS;
  if( find_option("htmlonly",0,0)!=0 ) flags |= WIKI_HTMLONLY;
  if( find_option("linksonly",0,0)!=0 ) flags |= WIKI_LINKSONLY;
  if( find_option("nobadlinks",0,0)!=0 ) flags |= WIKI_NOBADLINKS;
  if( find_option("inline",0,0)!=0 ) flags |= WIKI_INLINE;
  if( find_option("noblock",0,0)!=0 ) flags |= WIKI_NOBLOCK;
  if( find_option("mark",0,0)!=0 ) flags |= WIKI_MARK;
  if( find_option("dark-pikchr",0,0)!=0 ){
    pikchr_to_html_add_flags( PIKCHR_PROCESS_DARK_MODE );
  }
  bText = find_option("text",0,0)!=0;
  bFlow = find_option("flow",0,0)!=0;
  showType = find_option("type",0,0)!=0;
  zIn = find_option("msg","m",1);
  db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
  verify_all_options();
  if( g.argc!=3 ) usage("FILE");
  if( (zIn==0 && g.argc!=3) || (zIn!=0 && g.argc!=2) ) usage("FILE");
  blob_zero(&out);
  if( zIn ){
    blob_init(&in, zIn, -1);
  }else{
  blob_read_from_file(&in, g.argv[2], ExtFILE);
  wiki_convert(&in, &out, flags);
  blob_write_to_file(&out, "-");
    blob_read_from_file(&in, g.argv[2], ExtFILE);
  }
  mType = wiki_convert(&in, &out, flags);
  if( bText ){
    Blob txt;
    int htot = HTOT_TRIM;
    if( terminal_is_vt100() ) htot |= HTOT_VT100;
    if( bFlow ) htot |= HTOT_FLOW;
    blob_init(&txt, 0, 0);
    html_to_plaintext(blob_str(&out),&txt, htot);
    blob_reset(&out);
    out = txt;
  }
  if( bFlow ){
    fossil_print("   ");
    comment_print(blob_str(&out), 0, 3, terminal_get_width(80)-3,
                  get_comment_format());
  }else{
    blob_write_to_file(&out, "-");
  }
  if( showType ){
    fossil_print("%.*c\nResult Codes:", terminal_get_width(80)-1, '*');
    if( mType & RENDER_LINK )      fossil_print(" LINK");
    if( mType & RENDER_ENTITY )    fossil_print(" ENTITY");
    if( mType & RENDER_TAG )       fossil_print(" TAG");
    if( mType & RENDER_BLOCKTAG )  fossil_print(" BLOCKTAG");
    if( mType & RENDER_BLOCK )     fossil_print(" BLOCK");
    if( mType & RENDER_MARK )      fossil_print(" MARK");
    if( mType & RENDER_BADLINK )   fossil_print(" BADLINK");
    if( mType & RENDER_BADTARGET ) fossil_print(" BADTARGET");
    if( mType & RENDER_BADTAG )    fossil_print(" BADTAG");
    if( mType & RENDER_BADENTITY ) fossil_print(" BADENTITY");
    if( mType & RENDER_BADHTML )   fossil_print(" BADHTML");
    if( mType & RENDER_ERROR )     fossil_print(" ERROR");
    fossil_print("\n");
  }
}

/*
** COMMAND: test-markdown-render
**
** Usage: %fossil test-markdown-render FILE ...
**
** Render markdown in FILE as HTML on stdout.
** Options:
**
**    --safe            Restrict the output to use only "safe" HTML
**    --dark-pikchr     Render pikchrs in dark mode
**    --lint-footnotes  Print stats for footnotes-related issues
**    --dark-pikchr     Render pikchrs in dark mode
**    --safe            Restrict the output to use only "safe" HTML
**    --text            Run the output through html_to_plaintext().
*/
void test_markdown_render(void){
  Blob in, out;
  int i;
  int bSafe = 0, bFnLint = 0;
  int bSafe = 0, bFnLint = 0, bText = 0;
  db_find_and_open_repository(OPEN_OK_NOT_FOUND|OPEN_SUBSTITUTE,0);
  bSafe = find_option("safe",0,0)!=0;
  bFnLint = find_option("lint-footnotes",0,0)!=0;
  if( find_option("dark-pikchr",0,0)!=0 ){
    pikchr_to_html_add_flags( PIKCHR_PROCESS_DARK_MODE );
  }
  bText = find_option("text",0,0)!=0;
  verify_all_options();
  for(i=2; i<g.argc; i++){
    blob_zero(&out);
    blob_read_from_file(&in, g.argv[i], ExtFILE);
    if( g.argc>3 ){
      fossil_print("<!------ %h ------->\n", g.argv[i]);
    }
    markdown_to_html(&in, 0, &out);
    safe_html_context( bSafe ? DOCSRC_UNTRUSTED : DOCSRC_TRUSTED );
    safe_html(&out);
    if( bText ){
      Blob txt;
      blob_init(&txt, 0, 0);
      html_to_plaintext(blob_str(&out), &txt, HTOT_VT100);
      blob_reset(&out);
      out = txt;
    }
    blob_write_to_file(&out, "-");
    blob_reset(&in);
    blob_reset(&out);
  }
  if( bFnLint && (g.ftntsIssues[0] || g.ftntsIssues[1]
      || g.ftntsIssues[2] || g.ftntsIssues[3] )){
    fossil_fatal("There were issues with footnotes:\n"
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
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
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







+
+

-
+








-




-
-
-




-







**
**       [target]
**       [target|...]
**
** Where "target" can be either an artifact ID prefix or a wiki page
** name.  For each such hyperlink found, add an entry to the
** backlink table.
**
** The return value is a mask of RENDER_ flags.
*/
void wiki_extract_links(
int wiki_extract_links(
  char *z,           /* The wiki text from which to extract links */
  Backlink *pBklnk,  /* Backlink extraction context */
  int flags          /* wiki parsing flags */
){
  Renderer renderer;
  int tokenType;
  ParsedMarkup markup;
  int n;
  int inlineOnly;
  int wikiHtmlOnly = 0;

  memset(&renderer, 0, sizeof(renderer));
  renderer.state = ALLOW_WIKI|AT_NEWLINE|AT_PARAGRAPH;
  if( flags & WIKI_NOBLOCK ){
    renderer.state |= INLINE_MARKUP_ONLY;
  }
  if( wikiUsesHtml() ){
    renderer.state |= WIKI_HTMLONLY;
    wikiHtmlOnly = 1;
  }
  inlineOnly = (renderer.state & INLINE_MARKUP_ONLY)!=0;

  while( z[0] ){
    if( wikiHtmlOnly ){
      n = nextRawToken(z, &renderer, &tokenType);
    }else{
      n = nextWikiToken(z, &renderer, &tokenType);
    }
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2230
2231
2232
2233
2234
2235
2236






2237
2238
2239
2240
2241
2242
2243







-
-
-
-
-
-







          if( markup.endTag ){
            renderer.state |= ALLOW_WIKI;
          }else{
            renderer.state &= ~ALLOW_WIKI;
          }
        }else

        /* Ignore block markup for in-line rendering.
        */
        if( inlineOnly && (markup.iType&MUTYPE_INLINE)==0 ){
          /* Do nothing */
        }else

        /* Generate end-tags */
        if( markup.endTag ){
          popStackToTag(&renderer, markup.iCode);
        }else

        /* Push <div> markup onto the stack together with the id=ID attribute.
        */
2139
2140
2141
2142
2143
2144
2145

2146
2147
2148
2149
2150
2151
2152
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286







+







      default: {
        break;
      }
    }
    z += n;
  }
  free(renderer.aStack);
  return renderer.mRender;
}

/*
** Return the length, in bytes, of the HTML token that z is pointing to.
*/
int html_token_length(const char *z){
  int n;
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

2444
2445

2446
2447
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
2480
2481
2482









2483
2484

2485
2486
2487



2488
2489
2490
2491
2492

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
2519

2520
2521
2522
2523
2524
2525
2526
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561

2562
2563
2564





2565
2566
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
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611



2612
2613
2614
2615
2616
2617
2618


2619


2620


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
2676
2677
2678
2679
2680
2681

2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704

2705
2706
2707
2708
2709
2710
2711
2712







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








-
+


-
-
-
-
-
+
+
+
+
+
+
+
+
+
+







+

















+
+
+
+
+
+
+
+
+
+
+

-
-
-
+
+
+
+
+
+
+
-
-
+
-
-
+
-
-

-
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-

-
+
+

-
-
+
+

-
+




+









-
-
-
-
+
+
+
+
+
+
+
+
+

-
+
-
-
-
+
+
+


-
-
-
+




+
-
+
+





-
+







+
+
+
+
+




+
+




-
+







    blob_zero(&out);
    htmlTidy(blob_str(&in), &out);
    blob_reset(&in);
    fossil_puts(blob_buffer(&out), 0, blob_size(&out));
    blob_reset(&out);
  }
}

#if INTERFACE
/*
** Allowed flag options for html_to_plaintext().
*/
#define HTOT_VT100   0x01 /* <mark> becomes ^[[91m */
#define HTOT_FLOW    0x02 /* Collapse internal whitespace to a single space */
#define HTOT_TRIM    0x04 /* Trim off leading and trailing whitespace */

#endif /* INTERFACE */

/*
** Add <MARK> or </MARK> to the output, or similar VT-100 escape
** codes.
*/
static void addMark(Blob *pOut, int mFlags, int isClose){
  static const char *az[4] = { "<MARK>", "</MARK>", "\033[91m", "\033[0m" };
  int i = 0;
  if( isClose ) i++;
  if( mFlags & HTOT_VT100 ) i += 2;
  blob_append(pOut, az[i], -1);
}

/*
** Remove all HTML markup from the input text.  The output written into
** pOut is pure text.
**
** Put the title on the first line, if there is any <title> markup.
** If there is no <title>, then create a blank first line.
*/
void html_to_plaintext(const char *zIn, Blob *pOut){
void html_to_plaintext(const char *zIn, Blob *pOut, int mFlags){
  int n;
  int i, j;
  int inTitle = 0;          /* True between <title>...</title> */
  int seenText = 0;         /* True after first non-whitespace seen */
  int nNL = 0;              /* Number of \n characters at the end of pOut */
  int nWS = 0;              /* True if pOut ends with whitespace */
  while( fossil_isspace(zIn[0]) ) zIn++;
  int bFlow = 0;          /* Transform internal WS into a single space */
  int prevWS = 1;         /* Previous output was whitespace or start of msg */
  int nMark = 0;          /* True if inside of <mark>..</mark> */

  for(i=0; fossil_isspace(zIn[i]); i++){}
  if( i>0 && (mFlags & HTOT_TRIM)==0 ){
    blob_append(pOut, zIn, i);
  }
  zIn += i;
  if( mFlags & HTOT_FLOW ) bFlow = 1;
  while( zIn[0] ){
    n = html_token_length(zIn);
    if( zIn[0]=='<' && n>1 ){
      int isCloseTag;
      int eTag;
      int eType;
      char zTag[32];
      prevWS = 0;
      isCloseTag = zIn[1]=='/';
      for(i=0, j=1+isCloseTag; i<30 && fossil_isalnum(zIn[j]); i++, j++){
         zTag[i] = fossil_tolower(zIn[j]);
      }
      zTag[i] = 0;
      eTag = findTag(zTag);
      eType = aMarkup[eTag].iType;
      if( eTag==MARKUP_INVALID && fossil_strnicmp(zIn,"<style",6)==0 ){
        zIn += n;
        while( zIn[0] ){
          n = html_token_length(zIn);
          if( fossil_strnicmp(zIn, "</style",7)==0 ) break;
          zIn += n;
        }
        if( zIn[0]=='<' ) zIn += n;
        continue;
      }
      if( eTag==MARKUP_INVALID && strcmp(zTag,"mark")==0 ){
        if( isCloseTag && nMark ){
          addMark(pOut, mFlags, 1);
          nMark = 0;
        }else if( !isCloseTag && !nMark ){
          addMark(pOut, mFlags, 0);
          nMark = 1;
        }
        zIn += n;
        continue;            
      }
      if( eTag==MARKUP_TITLE ){
        inTitle = !isCloseTag;
      }
      if( !isCloseTag && seenText && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
        if( isCloseTag && (mFlags & HTOT_FLOW)==0 ){
          bFlow = 0;
        }else{
          bFlow = 1;
        }
      }
      if( !isCloseTag && (eType & (MUTYPE_BLOCK|MUTYPE_TABLE))!=0 ){
        if( nNL==0 ){
          blob_append_char(pOut, '\n');
        blob_append_char(pOut, '\n');
          nNL++;
        }
      }
        nWS = 1;
      }
    }else if( fossil_isspace(zIn[0]) ){
      if( seenText ){
      if( bFlow==0 ){
        nNL = 0;
        if( !inTitle ){ /* '\n' -> ' ' within <title> */
          for(i=0; i<n; i++) if( zIn[i]=='\n' ) nNL++;
        }
        if( !nWS ){
          blob_append_char(pOut, nNL ? '\n' : ' ');
          nWS = 1;
        }
        if( zIn[n]==0 && (mFlags & HTOT_TRIM) ) break;
        blob_append(pOut, zIn, n);
      }else if( !prevWS ){
        prevWS = 1;
        blob_append_char(pOut, ' ');
        zIn += n;
        n = 0;
      }
      }
    }else if( zIn[0]=='&' ){
      char c = '?';
      u32 c = '?';
      prevWS = 0;
      if( zIn[1]=='#' ){
        int x = atoi(&zIn[1]);
        if( x>0 && x<=127 ) c = x;
        c = atoi(&zIn[2]);
        if( c==0 ) c = '?';
      }else{
        static const struct { int n; char c; char *z; } aEntity[] = {
        static const struct { int n; u32 c; char *z; } aEntity[] = {
           { 5, '&', "&amp;"   },
           { 4, '<', "&lt;"    },
           { 4, '>', "&gt;"    },
           { 6, ' ', "&nbsp;"  },
           { 6, '"', "&quot;"  },
        };
        int jj;
        for(jj=0; jj<count(aEntity); jj++){
          if( aEntity[jj].n==n && strncmp(aEntity[jj].z,zIn,n)==0 ){
            c = aEntity[jj].c;
            break;
          }
        }
      }
      if( fossil_isspace(c) ){
        if( nWS==0 && seenText ) blob_append_char(pOut, c);
        nWS = 1;
        nNL = c=='\n';
      if( c<0x00080 ){
        blob_append_char(pOut, c & 0xff);
      }else if( c<0x00800 ){
        blob_append_char(pOut, 0xc0 + (u8)((c>>6)&0x1f));
        blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
      }else if( c<0x10000 ){
        blob_append_char(pOut, 0xe0 + (u8)((c>>12)&0x0f));
        blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f));
        blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
      }else{
        if( !seenText && !inTitle ) blob_append_char(pOut, '\n');
        blob_append_char(pOut, 0xf0 + (u8)((c>>18)&0x07));
        seenText = 1;
        nNL = nWS = 0;
        blob_append_char(pOut, c);
        blob_append_char(pOut, 0x80 + (u8)((c>>12)&0x3f));
        blob_append_char(pOut, 0x80 + (u8)((c>>6)&0x3f));
        blob_append_char(pOut, 0x80 + (u8)(c&0x3f));
      }
    }else{
      if( !seenText && !inTitle ) blob_append_char(pOut, '\n');
      seenText = 1;
      nNL = nWS = 0;
      prevWS = 0;
      blob_append(pOut, zIn, n);
    }
    zIn += n;
  }
  if( nMark ){
  if( nNL==0 ) blob_append_char(pOut, '\n');
    addMark(pOut, mFlags, 1);
  }
}

/*
** COMMAND: test-html-to-text
**
** Usage: %fossil test-html-to-text FILE ...
** Usage: %fossil test-html-to-text [OPTIONS] FILE ...
**
** Read all files named on the command-line.  Convert the file
** content from HTML to text and write the results on standard
** output.
**
** This command is intended as a test and debug interface for
** the html_to_plaintext() routine.
**
** Options:
**
**     --vt100              Translate <mark> and </mark> into ANSI/VT100
**                          escapes to highlight the contained text.
*/
void test_html_to_text(void){
  Blob in, out;
  int i;
  int mFlags = 0;
  if( find_option("vt100",0,0)!=0 ) mFlags |= HTOT_VT100;

  for(i=2; i<g.argc; i++){
    blob_read_from_file(&in, g.argv[i], ExtFILE);
    blob_zero(&out);
    html_to_plaintext(blob_str(&in), &out);
    html_to_plaintext(blob_str(&in), &out, mFlags);
    blob_reset(&in);
    fossil_puts(blob_buffer(&out), 0, blob_size(&out));
    blob_reset(&out);
  }
}

/****************************************************************************
2557
2558
2559
2560
2561
2562
2563
2564

2565
2566
2567
2568
2569
2570
2571
2743
2744
2745
2746
2747
2748
2749

2750
2751
2752
2753
2754
2755
2756
2757







-
+







static void html_tagstack_init(HtmlTagStack *p){
  p->n = 0;
  p->nAlloc = 0;
  p->aStack = p->aSpace;
}

/*
** Push a new element onto the tag statk
** Push a new element onto the tag stack
*/
static void html_tagstack_push(HtmlTagStack *p, int e){
  if( p->n>=ArraySize(p->aSpace) && p->n>=p->nAlloc ){
    if( p->nAlloc==0 ){
      int *aNew;
      p->nAlloc = 50;
      aNew = fossil_malloc( sizeof(p->aStack[0])*p->nAlloc );
2625
2626
2627
2628
2629
2630
2631
2632

2633
2634
2635
2636
2637
2638
2639
2811
2812
2813
2814
2815
2816
2817

2818
2819
2820
2821
2822
2823
2824
2825







-
+







** Return a nonce to indicate that safe_html() can allow code through
** without censoring.
**
** When safe_html() is asked to sanitize some HTML, it will ignore
** any text in between two consecutive instances of the nonce.  The
** nonce itself is an HTML comment so it is harmless to keep the
** nonce in the middle of the HTML stream.  A different nonce is
** choosen each time Fossil is run, using a lot of randomness, so
** chosen each time Fossil is run, using a lot of randomness, so
** an attacker will be unable to guess the nonce in advance.
**
** The original use-case for this mechanism is to allow Pikchr-generated
** SVG in the middle of HTML generated from Markdown.  The Markdown
** output will normally be processed by safe_html() to prevent accidental
** or malicious introduction of harmful HTML (ex: <script>) in the
** output stream.  The safe_html() only lets through HTML elements
Changes to src/winfile.c.
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
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







+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+



-
+







-
+

-
+




-
-
-
-
-
+


-
+







** undesirable in file name comparison, so lstrcmpiW() is only invoked in cases
** that are technically impossible and contradicting all known laws of physics.
*/
int win32_filenames_equal_nocase(
  const wchar_t *fn1,
  const wchar_t *fn2
){
  /* ---- Data types used by dynamically loaded API functions. -------------- */
  typedef struct { /* UNICODE_STRING from <ntdef.h> */
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
  } MY_UNICODE_STRING;
  /* ---- Prototypes for dynamically loaded API functions. ------------------ */
  typedef int (WINAPI *FNCOMPARESTRINGORDINAL)(LPCWCH,int,LPCWCH,int,BOOL);
  typedef VOID (NTAPI *FNRTLINITUNICODESTRING)(MY_UNICODE_STRING*,PCWSTR);
  typedef BOOLEAN (NTAPI *FNRTLEQUALUNICODESTRING)
    (MY_UNICODE_STRING*,MY_UNICODE_STRING*,BOOLEAN);
  /* ------------------------------------------------------------------------ */
  static FARPROC fnCompareStringOrdinal;
  static FARPROC fnRtlInitUnicodeString;
  static FARPROC fnRtlEqualUnicodeString;
  static FNCOMPARESTRINGORDINAL fnCompareStringOrdinal;
  static FNRTLINITUNICODESTRING fnRtlInitUnicodeString;
  static FNRTLEQUALUNICODESTRING fnRtlEqualUnicodeString;
  static int loaded_CompareStringOrdinal;
  static int loaded_RtlUnicodeStringAPIs;
  if( !loaded_CompareStringOrdinal ){
    fnCompareStringOrdinal =
    fnCompareStringOrdinal = (FNCOMPARESTRINGORDINAL)
      GetProcAddress(GetModuleHandleA("kernel32"),"CompareStringOrdinal");
    loaded_CompareStringOrdinal = 1;
  }
  if( fnCompareStringOrdinal ){
    return fnCompareStringOrdinal(fn1,-1,fn2,-1,1)-2==0;
  }
  if( !loaded_RtlUnicodeStringAPIs ){
    fnRtlInitUnicodeString =
    fnRtlInitUnicodeString = (FNRTLINITUNICODESTRING)
      GetProcAddress(GetModuleHandleA("ntdll"),"RtlInitUnicodeString");
    fnRtlEqualUnicodeString =
    fnRtlEqualUnicodeString = (FNRTLEQUALUNICODESTRING)
      GetProcAddress(GetModuleHandleA("ntdll"),"RtlEqualUnicodeString");
    loaded_RtlUnicodeStringAPIs = 1;
  }
  if( fnRtlInitUnicodeString && fnRtlEqualUnicodeString ){
    struct { /* UNICODE_STRING from <ntdef.h> */
      unsigned short Length;
      unsigned short MaximumLength;
      wchar_t *Buffer;
    } u1, u2;
    MY_UNICODE_STRING u1, u2;
    fnRtlInitUnicodeString(&u1,fn1);
    fnRtlInitUnicodeString(&u2,fn2);
    return (unsigned char)fnRtlEqualUnicodeString(&u1,&u2,1);
    return (BOOLEAN/*unsigned char*/)fnRtlEqualUnicodeString(&u1,&u2,1);
  }
  /* In what kind of strange parallel universe are we? */
  return lstrcmpiW(fn1,fn2)==0;
}

/* Helper macros to deal with directory separators. */
#define IS_DIRSEP(s,i) ( s[i]=='/' || s[i]=='\\' )
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

    }
    zBuf[j] = chSep;            /* Undo working buffer truncation. */
    i = j;
  }
  fossil_free(zBuf);
  return zRes;
}

/* Return the unique identifier (UID) for a file, made up of the file identifier
** (equal to "inode" for Unix-style file systems) plus the volume serial number.
** Call the GetFileInformationByHandleEx() function on Windows Vista, and resort
** to the GetFileInformationByHandle() function on Windows XP. The result string
** is allocated by mprintf(), or NULL on failure.
*/
char *win32_file_id(
  const char *zFileName
){
  /* ---- Data types used by dynamically loaded API functions. -------------- */
  typedef struct { /* FILE_ID_INFO from <winbase.h> */
    ULONGLONG VolumeSerialNumber;
    BYTE FileId[16];
  } MY_FILE_ID_INFO;
  /* ---- Prototypes for dynamically loaded API functions. ------------------ */
  typedef int (WINAPI *FNGETFILEINFORMATIONBYHANDLEEX)
    (HANDLE,int/*enum*/,MY_FILE_ID_INFO*,DWORD);
  /* ------------------------------------------------------------------------ */
  static FNGETFILEINFORMATIONBYHANDLEEX fnGetFileInformationByHandleEx;
  static int loaded_fnGetFileInformationByHandleEx;
  wchar_t *wzFileName = fossil_utf8_to_path(zFileName,0);
  HANDLE hFile;
  char *zFileId = 0;
  hFile = CreateFileW(
            wzFileName,
            0,
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS,
            NULL);
  if( hFile!=INVALID_HANDLE_VALUE ){
    BY_HANDLE_FILE_INFORMATION fi;
    MY_FILE_ID_INFO fi2;
    if( !loaded_fnGetFileInformationByHandleEx ){
      fnGetFileInformationByHandleEx = (FNGETFILEINFORMATIONBYHANDLEEX)
        GetProcAddress(
          GetModuleHandleA("kernel32"),"GetFileInformationByHandleEx");
      loaded_fnGetFileInformationByHandleEx = 1;
    }
    if( fnGetFileInformationByHandleEx ){
      if( fnGetFileInformationByHandleEx(
            hFile,/*FileIdInfo*/0x12,&fi2,sizeof(fi2)) ){
        zFileId = mprintf(
                    "%016llx/"
                      "%02x%02x%02x%02x%02x%02x%02x%02x"
                      "%02x%02x%02x%02x%02x%02x%02x%02x",
                    fi2.VolumeSerialNumber,
                    fi2.FileId[15], fi2.FileId[14],
                    fi2.FileId[13], fi2.FileId[12],
                    fi2.FileId[11], fi2.FileId[10],
                    fi2.FileId[9],  fi2.FileId[8],
                    fi2.FileId[7],  fi2.FileId[6],
                    fi2.FileId[5],  fi2.FileId[4],
                    fi2.FileId[3],  fi2.FileId[2],
                    fi2.FileId[1],  fi2.FileId[0]);
      }
    }
    if( zFileId==0 ){
      if( GetFileInformationByHandle(hFile,&fi) ){
        ULARGE_INTEGER FileId = {{
          /*.LowPart = */ fi.nFileIndexLow,
          /*.HighPart = */ fi.nFileIndexHigh
        }};
        zFileId = mprintf(
                    "%08x/%016llx",
                    fi.dwVolumeSerialNumber,(u64)FileId.QuadPart);
      }
    }
    CloseHandle(hFile);
  }
  fossil_path_free(wzFileName);
  return zFileId;
}
#endif /* _WIN32  -- This code is for win32 only */
Changes to src/winhttp.c.
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
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







+
+
+

+
+
+
+
+
+

-
+
-
-
-
-
-
-
-

-
-
-
-







  }

  /*
  ** The repository name is only needed if there was no open check-out.  This
  ** is designed to allow the open check-out for the interactive user to work
  ** with the local Fossil server started via the "ui" command.
  */
  aux = fossil_fopen(zCmdFName, "wb");
  if( aux==0 ) goto end_request;
  fprintf(aux, "%s--in %s\n", get_utf8_bom(0), zRequestFName);
  zIp = SocketAddr_toString(&p->addr);
  fprintf(aux, "--out %s\n--ipaddr %s\n", zReplyFName, zIp);
  fossil_free(zIp);
  fprintf(aux, "--as %s\n", g.zCmdName);
  if( g.zErrlog && g.zErrlog[0] ){
    fprintf(aux,"--errorlog %s\n", g.zErrlog);
  }
  if( (p->flags & HTTP_SERVER_HAD_CHECKOUT)==0 ){
    assert( g.zRepositoryName && g.zRepositoryName[0] );
    fprintf(aux,"%s",g.zRepositoryName);
    sqlite3_snprintf(sizeof(zCmd), zCmd, "%s--in %s\n--out %s\n--ipaddr %s\n%s",
      get_utf8_bom(0), zRequestFName, zReplyFName, zIp, g.zRepositoryName
    );
  }else{
    sqlite3_snprintf(sizeof(zCmd), zCmd, "%s--in %s\n--out %s\n--ipaddr %s",
      get_utf8_bom(0), zRequestFName, zReplyFName, zIp
    );
  }
  fossil_free(zIp);
  aux = fossil_fopen(zCmdFName, "wb");
  if( aux==0 ) goto end_request;
  fwrite(zCmd, 1, strlen(zCmd), aux);

  sqlite3_snprintf(sizeof(zCmd), zCmd,
    "\"%s\" http -args \"%s\"%s%s",
    g.nameOfExe, zCmdFName,
    g.httpUseSSL ? "" : " --nossl", p->zOptions
  );
  in = fossil_fopen(zReplyFName, "w+b");
805
806
807
808
809
810
811
812

813
814
815
816
817
818
819
803
804
805
806
807
808
809

810
811
812
813
814
815
816
817







-
+







           nErr,
           MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
           (LPWSTR) &tmp,
           0,
           NULL
         );
  if( !nMsg ){
    /* No english, get what the system has available. */
    /* No English, get what the system has available. */
    nMsg = FormatMessageW(
             FORMAT_MESSAGE_ALLOCATE_BUFFER |
             FORMAT_MESSAGE_FROM_SYSTEM     |
             FORMAT_MESSAGE_IGNORE_INSERTS,
             NULL,
             nErr,
             0,
996
997
998
999
1000
1001
1002
1003

1004
1005
1006
1007
1008
1009
1010
994
995
996
997
998
999
1000

1001
1002
1003
1004
1005
1006
1007
1008







-
+







**
**         Creates a service. Available options include:
**
**         -D|--display DISPLAY-NAME
**
**              Sets the display name of the service. This name is shown
**              by graphical interface programs. By default, the display name
**              equals to the service name.
**              is equal to the service name.
**
**         -S|--start TYPE
**
**              Sets the start type of the service. TYPE can be "manual",
**              which means you need to start the service yourself with the
**              'fossil winsrv start' command or with the "net start" command
**              from the operating system. If TYPE is set to "auto", the service
1019
1020
1021
1022
1023
1024
1025
1026

1027
1028
1029
1030
1031
1032
1033
1017
1018
1019
1020
1021
1022
1023

1024
1025
1026
1027
1028
1029
1030
1031







-
+







**              used.
**
**         -W|--password PASSWORD
**
**              Password for the user account.
**
**         The following options are more or less the same as for the "server"
**         command and influence the behaviour of the http server:
**         command and influence the behavior of the http server:
**
**         --baseurl URL
**
**              Use URL as the base (useful for reverse proxies)
**
**         -P|--port TCPPORT
**
1154
1155
1156
1157
1158
1159
1160
1161

1162
1163
1164
1165
1166
1167
1168
1152
1153
1154
1155
1156
1157
1158

1159
1160
1161
1162
1163
1164
1165
1166







-
+







    if( zPort && (atoi(zPort)<=0) ){
      winhttp_fatal("create", zSvcName,
                   "port number must be in the range 1 - 65535.");
    }
    if( !zRepository ){
      db_must_be_within_tree();
    }else if( file_isdir(zRepository, ExtFILE)==1 ){
      g.zRepositoryName = mprintf("%s", zRepository);
      g.zRepositoryName = fossil_strdup(zRepository);
      file_simplify_name(g.zRepositoryName, -1, 0);
    }else{
      db_open_repository(zRepository);
    }
    db_close(0);
    /* Build the fully-qualified path to the service binary file. */
    blob_zero(&binPath);
Changes to src/xfer.c.
50
51
52
53
54
55
56
57

58
59
60
61
62
63
64
50
51
52
53
54
55
56

57
58
59
60
61
62
63
64







-
+







  int nDanglingFile;  /* Number of dangling deltas received */
  int mxSend;         /* Stop sending "file" when pOut reaches this size */
  int resync;         /* Send igot cards for all holdings */
  u8 syncPrivate;     /* True to enable syncing private content */
  u8 nextIsPrivate;   /* If true, next "file" received is a private */
  u32 remoteVersion;  /* Version of fossil running on the other side */
  u32 remoteDate;     /* Date for specific client software edition */
  u32 remoteTime;     /* Time of date correspoding on remoteDate */
  u32 remoteTime;     /* Time of date corresponding on remoteDate */
  time_t maxTime;     /* Time when this transfer should be finished */
};


/*
** The input blob contains an artifact.  Convert it into a record ID.
** Create a phantom record if no prior record exists and
825
826
827
828
829
830
831
832

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

832
833
834
835
836
837
838
839







-
+







static 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
   || 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 */
  }
864
865
866
867
868
869
870
871

872
873
874
875
876
877
878
864
865
866
867
868
869
870

871
872
873
874
875
876
877
878







-
+







      ** again with the SHA1 password.
      */
      const char *zPw = db_column_text(&q, 0);
      char *zSecret = sha1_shared_secret(zPw, blob_str(pLogin), 0);
      blob_zero(&combined);
      blob_copy(&combined, pNonce);
      blob_append(&combined, zSecret, -1);
      free(zSecret);
      fossil_free(zSecret);
      sha1sum_blob(&combined, &hash);
      rc = blob_constant_time_cmp(&hash, pSig);
      blob_reset(&hash);
      blob_reset(&combined);
    }
    if( rc==0 ){
      const char *zCap;
1039
1040
1041
1042
1043
1044
1045

































1046
1047
1048
1049
1050
1051
1052
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







      pXfer->resync = db_column_int(&q, 1)-1;
    }
  }
  db_finalize(&q);
  if( cnt==0 ) pXfer->resync = 0;
  return cnt;
}

/*
** Send an igot message for every cluster artifact that is not a phantom,
** is not shunned, is not private, and that is not in the UNCLUSTERED table.
** Return the number of cards sent.
*/
static int send_all_clusters(Xfer *pXfer){
  Stmt q;
  int cnt = 0;
  const char *zExtra;
  if( db_table_exists("temp","onremote") ){
    zExtra = " AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)";
  }else{
    zExtra = "";
  }
  db_prepare(&q,
    "SELECT uuid"
    "  FROM tagxref JOIN blob ON tagxref.rid=blob.rid AND tagxref.tagid=%d"
    " 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 unclustered WHERE rid=blob.rid)"
    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s",
    TAG_CLUSTER, zExtra /*safe-for-%s*/
  );
  while( db_step(&q)==SQLITE_ROW ){
    if( cnt==0 ) blob_appendf(pXfer->pOut, "# sending-clusters\n");
    blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
    cnt++;
  }
  db_finalize(&q);
  if( cnt ) blob_appendf(pXfer->pOut, "# end-of-clusters\n");
  return cnt;
}

/*
** Send an igot message for every artifact.
*/
static void send_all(Xfer *pXfer){
  Stmt q;
  db_prepare(&q,
1081
1082
1083
1084
1085
1086
1087












1088
1089
1090
1091
1092
1093
1094

1095
1096
1097
1098
1099
1100
1101
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
1142
1143
1144
1145
1146







+
+
+
+
+
+
+
+
+
+
+
+






-
+







    int sz = db_column_int(&uvq,3);
    if( zHash==0 ){ sz = 0; zHash = "-"; }
    blob_appendf(pXfer->pOut, "uvigot %s %lld %s %d\n",
                 zName, mtime, zHash, sz);
  }
  db_finalize(&uvq);
}

/*
** Return a string that contains supplemental information about a
** "not authorized" error.  The string might be empty if no additional
** information is available.
*/
static char *whyNotAuth(void){
  if( g.useLocalauth && db_get_boolean("localauth",0)!=0 ){
    return "\\sbecause\\sthe\\s'localauth'\\ssetting\\sis\\senabled";
  }
  return "";
}

/*
** Called when there is an attempt to transfer private content to and
** from a server without authorization.
*/
static void server_private_xfer_not_authorized(void){
  @ error not\sauthorized\sto\ssync\sprivate\scontent
  @ error not\sauthorized\sto\ssync\sprivate\scontent%s(whyNotAuth())
}

/*
** Return the common TH1 code to evaluate prior to evaluating any other
** TH1 transfer notification scripts.
*/
const char *xfer_common_code(void){
1169
1170
1171
1172
1173
1174
1175
1176

1177
1178
1179
1180
1181
1182
1183
1214
1215
1216
1217
1218
1219
1220

1221
1222
1223
1224
1225
1226
1227
1228







-
+







*/
int xfer_run_common_script(void){
  return xfer_run_script(xfer_common_code(), 0, 0);
}

/*
** This routine makes a "syncwith:URL" entry in the CONFIG table to
** indicate that a sync is occuring with zUrl.
** indicate that a sync is occurring with zUrl.
**
** Add a "syncfrom:URL" entry instead of "syncwith:URL" if bSyncFrom is true.
*/
static void xfer_syncwith(const char *zUrl, int bSyncFrom){
  UrlData x;
  memset(&x, 0, sizeof(x));
  url_parse_local(zUrl, URL_OMIT_USER, &x);
1194
1195
1196
1197
1198
1199
1200















1201
1202
1203
1204
1205
1206
1207
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







}

/*
** If this variable is set, disable login checks.  Used for debugging
** only.
*/
static int disableLogin = 0;

/*
** Must be passed the version info from pragmas
** client-version/server-version cards. If the version info is "new
** enough" then the loginCardMode is ORd into the X-Fossil-Xfer-Login
** card flag, else this is a no-op.
*/
static void xfer_xflc_check(int iRemoteVersion, int iDate, int iTime,
                            int fLoginCardMode){
  if( iRemoteVersion>=22700
      && (iDate > 20250727
          || (iDate == 20250727 && iTime >= 110500)) ){
    g.syncInfo.fLoginCardMode |= fLoginCardMode;
  }
}

/*
** The CGI/HTTP preprocessor always redirects requests with a content-type
** of application/x-fossil or application/x-fossil-debug to this page,
** regardless of what path was specified in the HTTP header.  This allows
** clone clients to specify a URL that omits default pathnames, such
** as "http://fossil-scm.org/" instead of "http://fossil-scm.org/index.cgi".
1226
1227
1228
1229
1230
1231
1232

1233
1234
1235
1236
1237
1238
1239
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300







+







  const char *zScript = 0;
  char *zUuidList = 0;
  int nUuidList = 0;
  char **pzUuidList = 0;
  int *pnUuidList = 0;
  int uvCatalogSent = 0;
  int bSendLinks = 0;
  int nLogin = 0;

  if( fossil_strcmp(PD("REQUEST_METHOD","POST"),"POST") ){
     fossil_redirect_home();
  }
  g.zLogin = "anonymous";
  login_set_anon_nobody_capabilities();
  login_check_credentials();
1267
1268
1269
1270
1271
1272
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
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

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







+
+
+
+
+
+
+
+
+
+
+
+
+














-
+




















-
+







    @ error common\sscript\sfailed:\s%F(g.zErrMsg)
    nErr++;
  }
  zScript = xfer_push_code();
  if( zScript ){ /* NOTE: Are TH1 transfer hooks enabled? */
    pzUuidList = &zUuidList;
    pnUuidList = &nUuidList;
  }
  if( g.syncInfo.zLoginCard ){
    /* Login card received via HTTP Cookie header */
    blob_zero(&xfer.line);
    blob_append(&xfer.line, g.syncInfo.zLoginCard, -1);
    xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken,
                                count(xfer.aToken));
    fossil_free( g.syncInfo.zLoginCard );
    g.syncInfo.zLoginCard = 0;
    if( xfer.nToken==4
        && blob_eq(&xfer.aToken[0], "login") ){
      goto handle_login_card;
    }
  }
  while( blob_line(xfer.pIn, &xfer.line) ){
    if( blob_buffer(&xfer.line)[0]=='#' ) continue;
    if( blob_size(&xfer.line)==0 ) continue;
    xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));

    /*   file HASH SIZE \n CONTENT
    **   file HASH DELTASRC SIZE \n CONTENT
    **
    ** Server accepts a file from the client.
    */
    if( blob_eq(&xfer.aToken[0], "file") ){
      if( !isPush ){
        cgi_reset_content();
        @ error not\sauthorized\sto\swrite
        @ error not\sauthorized\sto\swrite%s(whyNotAuth())
        nErr++;
        break;
      }
      xfer_accept_file(&xfer, 0, pzUuidList, pnUuidList);
      if( blob_size(&xfer.err) ){
        cgi_reset_content();
        @ error %T(blob_str(&xfer.err))
        nErr++;
        break;
      }
    }else

    /*   cfile HASH USIZE CSIZE \n CONTENT
    **   cfile HASH DELTASRC USIZE CSIZE \n CONTENT
    **
    ** Server accepts a compressed file from the client.
    */
    if( blob_eq(&xfer.aToken[0], "cfile") ){
      if( !isPush ){
        cgi_reset_content();
        @ error not\sauthorized\sto\swrite
        @ error not\sauthorized\sto\swrite%s(whyNotAuth())
        nErr++;
        break;
      }
      xfer_accept_compressed_file(&xfer, pzUuidList, pnUuidList);
      if( blob_size(&xfer.err) ){
        cgi_reset_content();
        @ error %T(blob_str(&xfer.err))
1426
1427
1428
1429
1430
1431
1432
1433

1434
1435
1436
1437
1438
1439
1440
1441
1442

1443
1444
1445

1446
1447
1448
1449
1450
1451
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
1500
1501
1502
1503
1504
1505
1506

1507
1508
1509
1510
1511
1512
1513
1514
1515

1516
1517
1518

1519
1520
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







-
+








-
+


-
+

















-
+



















+







        nErr++;
        break;
      }
      login_check_credentials();
      if( blob_eq(&xfer.aToken[0], "pull") ){
        if( !g.perm.Read ){
          cgi_reset_content();
          @ error not\sauthorized\sto\sread
          @ error not\sauthorized\sto\sread%s(whyNotAuth())
          nErr++;
          break;
        }
        isPull = 1;
      }else{
        if( !g.perm.Write ){
          if( !isPull ){
            cgi_reset_content();
            @ error not\sauthorized\sto\swrite
            @ error not\sauthorized\sto\swrite%s(whyNotAuth())
            nErr++;
          }else{
            @ message pull\sonly\s-\snot\sauthorized\sto\spush
            @ message pull\sonly\s-\snot\sauthorized\sto\spush%s(whyNotAuth())
          }
        }else{
          isPush = 1;
        }
      }
    }else

    /*    clone   ?PROTOCOL-VERSION?  ?SEQUENCE-NUMBER?
    **
    ** The client knows nothing.  Tell all.
    */
    if( blob_eq(&xfer.aToken[0], "clone") ){
      int iVers;
      login_check_credentials();
      if( !g.perm.Clone ){
        cgi_reset_content();
        @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
        @ error not\sauthorized\sto\sclone
        @ error not\sauthorized\sto\sclone%s(whyNotAuth())
        nErr++;
        break;
      }
      if( db_get_boolean("uv-sync",0) && !uvCatalogSent ){
        @ pragma uv-pull-only
        send_unversioned_catalog(&xfer);
        uvCatalogSent = 1;
      }
      if( xfer.nToken==3
       && blob_is_int(&xfer.aToken[1], &iVers)
       && iVers>=2
      ){
        int seqno, max;
        if( iVers>=3 ){
          cgi_set_content_type("application/x-fossil-uncompressed");
        }
        blob_is_int(&xfer.aToken[2], &seqno);
        if( seqno<=0 ){
          xfer_fatal_error("invalid clone sequence number");
          db_rollback_transaction();
          return;
        }
        max = db_int(0, "SELECT max(rid) FROM blob");
        while( xfer.mxSend>(int)blob_size(xfer.pOut) && seqno<=max){
          if( time(NULL) >= xfer.maxTime ) break;
          if( iVers>=3 ){
            send_compressed_file(&xfer, seqno);
1502
1503
1504
1505
1506
1507
1508


1509



1510
1511
1512
1513


1514
1515





1516
1517
1518
1519
1520
1521
1522
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







+
+
-
+
+
+




+
+


+
+
+
+
+







      @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))
    }else

    /*    login  USER  NONCE  SIGNATURE
    **
    ** The client has sent login credentials to the server.
    ** Validate the login.  This has to happen before anything else.
    **
    ** For many years, Fossil would accept multiple login cards with
    ** The client can send multiple logins.  Permissions are cumulative.
    ** cumulative permissions.  But that feature was never used.  Hence
    ** it is now prohibited.  Any login card after the first generates
    ** a fatal error.
    */
    if( blob_eq(&xfer.aToken[0], "login")
     && xfer.nToken==4
    ){
    handle_login_card:
      nLogin++;
      if( disableLogin ){
        g.perm.Read = g.perm.Write = g.perm.Private = g.perm.Admin = 1;
      }else if( nLogin > 1 ){
        cgi_reset_content();
        @ error multiple\slogin\cards
        nErr++;
        break;
      }else{
        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++;
1551
1552
1553
1554
1555
1556
1557

1558
1559
1560
1561
1562
1563
1564

1565
1566
1567
1568
1569
1570
1571
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650

1651
1652
1653
1654
1655
1656
1657
1658







+






-
+







    */
    if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3
        && blob_is_int(&xfer.aToken[2], &size) ){
      const char *zName = blob_str(&xfer.aToken[1]);
      Blob content;
      if( size<0 ){
        xfer_fatal_error("invalid config record");
        db_rollback_transaction();
        return;
      }
      blob_zero(&content);
      blob_extract(xfer.pIn, size, &content);
      if( !g.perm.Admin ){
        cgi_reset_content();
        @ error not\sauthorized\sto\spush\sconfiguration
        @ error not\sauthorized\sto\spush\sconfiguration%s(whyNotAuth())
        nErr++;
        break;
      }
      configure_receive(zName, &content, CONFIGSET_ALL);
      blob_reset(&content);
      blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
    }else
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1689
1690
1691
1692
1693
1694
1695

1696
1697
1698
1699
1700
1701
1702







-







      if( !g.perm.Private ){
        server_private_xfer_not_authorized();
      }else{
        xfer.nextIsPrivate = 1;
      }
    }else


    /*    pragma NAME VALUE...
    **
    ** The client issues pragmas to try to influence the behavior of the
    ** server.  These are requests only.  Unknown pragmas are silently
    ** ignored.
    */
    if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
1645
1646
1647
1648
1649
1650
1651

1652

1653
1654
1655
1656
1657
1658


1659
1660
1661
1662
1663
1664
1665
1731
1732
1733
1734
1735
1736
1737
1738

1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754







+
-
+






+
+







      /*   pragma client-version VERSION ?DATE? ?TIME?
      **
      ** The client announces to the server what version of Fossil it
      ** is running.  The DATE and TIME are a pure numeric ISO8601 time
      ** for the specific check-in of the client.
      */
      if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "client-version") ){
        xfer.remoteVersion = g.syncInfo.remoteVersion =
        xfer.remoteVersion = atoi(blob_str(&xfer.aToken[2]));
          atoi(blob_str(&xfer.aToken[2]));
        if( xfer.nToken>=5 ){
          xfer.remoteDate = atoi(blob_str(&xfer.aToken[3]));
          xfer.remoteTime = atoi(blob_str(&xfer.aToken[4]));
          @ pragma server-version %d(RELEASE_VERSION_NUMBER) \
          @ %d(MANIFEST_NUMERIC_DATE) %d(MANIFEST_NUMERIC_TIME)
        }
        xfer_xflc_check( xfer.remoteVersion, xfer.remoteDate,
                         xfer.remoteTime, 0x04 );
      }else

      /*   pragma uv-hash HASH
      **
      ** The client wants to make sure that unversioned files are all synced.
      ** If the HASH does not match, send a complete catalog of
      ** "uvigot" cards.
1779
1780
1781
1782
1783
1784
1785









1786
1787
1788
1789
1790
1791
1792
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890







+
+
+
+
+
+
+
+
+







      /*    pragma req-links
      **
      ** The client sends this message to the server to ask the server
      ** to tell it about alternative repositories  in the reply.
      */
      if( blob_eq(&xfer.aToken[1], "req-links") ){
        bSendLinks = 1;
      }else

      /*   pragma req-clusters
      **
      ** This pragma requests that the server send igot cards for every
      ** cluster artifact that it knows about.
      */
      if( blob_eq(&xfer.aToken[1], "req-clusters") ){
        send_all_clusters(&xfer);
      }

    }else

    /* Unknown message
    */
    {
1949
1950
1951
1952
1953
1954
1955


1956
1957
1958
1959
1960
1961
1962
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062







+
+







#define SYNC_UV_DRYRUN      0x00800    /* Do not actually exchange files */
#define SYNC_IFABLE         0x01000    /* Inability to sync is not fatal */
#define SYNC_CKIN_LOCK      0x02000    /* Lock the current check-in */
#define SYNC_NOHTTPCOMPRESS 0x04000    /* Do not compression HTTP messages */
#define SYNC_ALLURL         0x08000    /* The --all flag - sync to all URLs */
#define SYNC_SHARE_LINKS    0x10000    /* Request alternate repo links */
#define SYNC_XVERBOSE       0x20000    /* Extra verbose.  Network traffic */
#define SYNC_PING           0x40000    /* Verify server is alive */
#define SYNC_QUIET          0x80000    /* No output */
#endif

/*
** Floating-point absolute value
*/
static double fossil_fabs(double x){
  return x>0.0 ? x : -x;
1979
1980
1981
1982
1983
1984
1985
1986

1987
1988
1989
1990
1991
1992
1993
2079
2080
2081
2082
2083
2084
2085

2086
2087
2088
2089
2090
2091
2092
2093







-
+







){
  int go = 1;             /* Loop until zero */
  int nCardSent = 0;      /* Number of cards sent */
  int nCardRcvd = 0;      /* Number of cards received */
  int nCycle = 0;         /* Number of round trips to the server */
  int size;               /* Size of a config value or uvfile */
  int origConfigRcvMask;  /* Original value of configRcvMask */
  int nFileRecv;          /* Number of files received */
  int nFileRecv = 0;      /* Number of files received */
  int mxPhantomReq = 200; /* Max number of phantoms to request per comm */
  const char *zCookie;    /* Server cookie */
  i64 nUncSent, nUncRcvd; /* Bytes sent and received (before compression) */
  i64 nSent, nRcvd;       /* Bytes sent and received (after compression) */
  int cloneSeqno = 1;     /* Sequence number for clones */
  Blob send;              /* Text we are sending to the server */
  Blob recv;              /* Reply we got back from the server */
2005
2006
2007
2008
2009
2010
2011

2012
2013
2014
2015
2016
2017
2018
2019
2020
2021


2022
2023
2024
2025
2026
2027
2028
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







+









-
+
+







  const char *zOpType = 0;/* Push, Pull, Sync, Clone */
  double rSkew = 0.0;     /* Maximum time skew */
  int uvHashSent = 0;     /* The "pragma uv-hash" message has been sent */
  int uvDoPush = 0;       /* Generate uvfile messages to send to server */
  int uvPullOnly = 0;     /* 1: pull-only.  2: pull-only warning issued */
  int nUvGimmeSent = 0;   /* Number of uvgimme cards sent on this cycle */
  int nUvFileRcvd = 0;    /* Number of uvfile cards received on this cycle */
  int nGimmeRcvd = 0;     /* Number of gimme cards recevied on the prev cycle */
  sqlite3_int64 mtime;    /* Modification time on a UV file */
  int autopushFailed = 0; /* Autopush following commit failed if true */
  const char *zCkinLock;  /* Name of check-in to lock.  NULL for none */
  const char *zClientId;  /* A unique identifier for this check-out */
  unsigned int mHttpFlags;/* Flags for the http_exchange() subsystem */
  const int bOutIsTty = fossil_isatty(fossil_fileno(stdout));

  if( pnRcvd ) *pnRcvd = 0;
  if( db_get_boolean("dont-push", 0) ) syncFlags &= ~SYNC_PUSH;
  if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE|SYNC_UNVERSIONED))==0
  if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE|SYNC_UNVERSIONED|SYNC_PING))
            ==0
     && configRcvMask==0
     && configSendMask==0
  ){
    return 0;  /* Nothing to do */
  }

  /* Compute an appropriate project code.  zPCode is the project code
2179
2180
2181
2182
2183
2184
2185



2186
2187
2188
2189
2190



2191
2192
2193
2194
2195
2196
2197
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







+
+
+





+
+
+








    /* Client sends gimme cards for phantoms
    */
    if( (syncFlags & SYNC_PULL)!=0
     || ((syncFlags & SYNC_CLONE)!=0 && cloneSeqno==1)
    ){
      request_phantoms(&xfer, mxPhantomReq);
      if( xfer.nGimmeSent>0 && nCycle==2 && (syncFlags & SYNC_PULL)!=0 ){
        blob_appendf(&send, "pragma req-clusters\n");
      }
    }
    if( syncFlags & SYNC_PUSH ){
      send_unsent(&xfer);
      nCardSent += send_unclustered(&xfer);
      if( syncFlags & SYNC_PRIVATE ) send_private(&xfer);
      if( nGimmeRcvd>0 && nCycle==2 ){
        send_all_clusters(&xfer);
      }
    }

    /* Client sends 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) ){
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
2337
2338
2339
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
2444


2445
2446
2447
2448
2449
2450
2451







-

-
+




-
+




















+
+
+















+
+




+
+

-
-







        db_lset("client-id", zClientId);
      }
      blob_appendf(&send, "pragma ci-lock %s %s\n", zCkinLock, zClientId);
      zCkinLock = 0;
    }else if( zClientId ){
      blob_appendf(&send, "pragma ci-unlock %s\n", zClientId);
    }

    /* Append randomness to the end of the uplink message.  This makes all
    ** messages unique so that that the login-card nonce will always
    ** messages unique so that the login-card nonce will always
    ** be unique.
    */
    zRandomness = db_text(0, "SELECT hex(randomblob(20))");
    blob_appendf(&send, "# %s\n", zRandomness);
    free(zRandomness);
    fossil_free(zRandomness);

    if( (syncFlags & SYNC_VERBOSE)!=0
     && (syncFlags & SYNC_XVERBOSE)==0
    ){
      fossil_print("waiting for server...");
    }
    fflush(stdout);
    /* Exchange messages with the server */
    if( (syncFlags & SYNC_CLONE)!=0 && nCycle==0 ){
      /* Do not send a login card on the first round-trip of a clone */
      mHttpFlags = 0;
    }else{
      mHttpFlags = HTTP_USE_LOGIN;
    }
    if( syncFlags & SYNC_NOHTTPCOMPRESS ){
      mHttpFlags |= HTTP_NOCOMPRESS;
    }
    if( syncFlags & SYNC_XVERBOSE ){
      mHttpFlags |= HTTP_VERBOSE;
    }
    if( syncFlags & SYNC_QUIET ){
      mHttpFlags |= HTTP_QUIET;
    }

    /* Do the round-trip to the server */
    if( http_exchange(&send, &recv, mHttpFlags, MAX_REDIRECTS, 0) ){
      nErr++;
      go = 2;
      break;
    }

    /* Remember the URL of the sync target in the config file on the
    ** first successful round-trip */
    if( nCycle==0 && db_is_writeable("repository") ){
      xfer_syncwith(g.url.canonical, 0);
    }

    /* Output current stats */
    nRoundtrip++;
    nArtifactSent += xfer.nFileSent + xfer.nDeltaSent;
    if( syncFlags & SYNC_VERBOSE ){
      fossil_print(zValueFormat /*works-like:"%s%d%d%d%d"*/, "Sent:",
                   blob_size(&send), nCardSent+xfer.nGimmeSent+xfer.nIGotSent,
                   xfer.nFileSent, xfer.nDeltaSent);
    }else if( syncFlags & SYNC_QUIET ){
      /* No-op */
    }else{
      nRoundtrip++;
      nArtifactSent += xfer.nFileSent + xfer.nDeltaSent;
      if( bOutIsTty!=0 ){
        fossil_print(zBriefFormat /*works-like:"%d%d%d"*/,
                     nRoundtrip, nArtifactSent, nArtifactRcvd);
      }
    }
    nCardSent = 0;
    nCardRcvd = 0;
2367
2368
2369
2370
2371
2372
2373

2374
2375
2376
2377
2378
2379
2380
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493







+







    if( syncFlags & SYNC_PUSH ){
      blob_appendf(&send, "push %s %s\n", zSCode, zPCode);
      nCardSent++;
    }
    go = 0;
    nUvGimmeSent = 0;
    nUvFileRcvd = 0;
    nGimmeRcvd = 0;
    nPriorArtifact = nArtifactRcvd;

    /* Process the reply that came back from the server */
    while( blob_line(&recv, &xfer.line) ){
      if( blob_buffer(&xfer.line)[0]=='#' ){
        const char *zLine = blob_buffer(&xfer.line);
        if( memcmp(zLine, "# timestamp ", 12)==0 ){
2447
2448
2449
2450
2451
2452
2453

2454



2455
2456
2457
2458
2459
2460
2461
2560
2561
2562
2563
2564
2565
2566
2567

2568
2569
2570
2571
2572
2573
2574
2575
2576
2577







+
-
+
+
+







      if( blob_eq(&xfer.aToken[0], "gimme")
       && xfer.nToken==2
       && blob_is_hname(&xfer.aToken[1])
      ){
        remote_unk(&xfer.aToken[1]);
        if( syncFlags & SYNC_PUSH ){
          int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
          if( rid ){
          if( rid ) send_file(&xfer, rid, &xfer.aToken[1], 0);
            send_file(&xfer, rid, &xfer.aToken[1], 0);
            nGimmeRcvd++;
          }
        }
      }else

      /*   igot HASH  ?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
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2772
2773
2774
2775
2776
2777
2778



2779
2780
2781
2782
2783
2784
2785







-
-
-







        }
      }else

      /*   message MESSAGE
      **
      ** A message is received from the server.  Print it.
      ** 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
            && sqlite3_strglob("pull only *", zMsg)==0 ){
          syncFlags &= ~SYNC_PUSH;
2688
2689
2690
2691
2692
2693
2694

2695

2696
2697
2698
2699


2700
2701
2702
2703
2704
2705
2706
2801
2802
2803
2804
2805
2806
2807
2808

2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822







+
-
+




+
+







        /*   pragma server-version VERSION ?DATE? ?TIME?
        **
        ** The server announces to the server what version of Fossil it
        ** is running.  The DATE and TIME are a pure numeric ISO8601 time
        ** for the specific check-in of the client.
        */
        if( xfer.nToken>=3 && blob_eq(&xfer.aToken[1], "server-version") ){
          xfer.remoteVersion = g.syncInfo.remoteVersion =
          xfer.remoteVersion = atoi(blob_str(&xfer.aToken[2]));
            atoi(blob_str(&xfer.aToken[2]));
          if( xfer.nToken>=5 ){
            xfer.remoteDate = atoi(blob_str(&xfer.aToken[3]));
            xfer.remoteTime = atoi(blob_str(&xfer.aToken[4]));
          }
          xfer_xflc_check( xfer.remoteVersion, xfer.remoteDate,
                           xfer.remoteTime, 0x08 );
        }

        /*   pragma uv-pull-only
        **   pragma uv-push-ok
        **
        ** If the server is unwilling to accept new unversioned content (because
        ** this client lacks the necessary permissions) then it sends a
2827
2828
2829
2830
2831
2832
2833
2834

2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850


2851
2852
2853
2854
2855
2856
2857
2943
2944
2945
2946
2947
2948
2949

2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975







-
+
















+
+







          fossil_warning(
            "server replies with HTML instead of fossil sync protocol:\n%b",
            &recv
          );
          nErr++;
          break;
        }
        blob_appendf(&xfer.err, "unknown command: [%b]\n", &xfer.aToken[0]);
        blob_appendf(&xfer.err, "unknown command: [%b]\n", &xfer.line);
      }

      if( blob_size(&xfer.err) ){
        fossil_force_newline();
        fossil_warning("%b", &xfer.err);
        nErr++;
        break;
      }
      blobarray_reset(xfer.aToken, xfer.nToken);
      blob_reset(&xfer.line);
    }
    origConfigRcvMask = 0;
    if( nCardRcvd>0 && (syncFlags & SYNC_VERBOSE) ){
      fossil_print(zValueFormat /*works-like:"%s%d%d%d%d"*/, "Received:",
                   blob_size(&recv), nCardRcvd,
                   xfer.nFileRcvd, xfer.nDeltaRcvd + xfer.nDanglingFile);
    }else if( syncFlags & SYNC_QUIET ){
      /* No-op */
    }else{
      if( bOutIsTty!=0 ){
        fossil_print(zBriefFormat /*works-like:"%d%d%d"*/,
                     nRoundtrip, nArtifactSent, nArtifactRcvd);
      }
    }
    nUncRcvd += blob_size(&recv);
2894
2895
2896
2897
2898
2899
2900
2901

2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913

2914
2915
2916
2917
2918
2919
2920



2921
2922
2923
2924
2925
2926
2927
3012
3013
3014
3015
3016
3017
3018

3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030

3031
3032
3033
3034
3035
3036
3037

3038
3039
3040
3041
3042
3043
3044
3045
3046
3047







-
+











-
+






-
+
+
+







    if( go ){
      manifest_crosslink_end(MC_PERMIT_HOOKS);
    }else{
      manifest_crosslink_end(MC_PERMIT_HOOKS);
      content_enable_dephantomize(1);
    }
    db_end_transaction(0);
  };
  }; /* while(go) */
  transport_stats(&nSent, &nRcvd, 1);
  if( pnRcvd ) *pnRcvd = nArtifactRcvd;
  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;
  }
  if( bOutIsTty==0 ){
  if( bOutIsTty==0 && (syncFlags & SYNC_QUIET)==0 ){
    fossil_print(zBriefFormat /*works-like:"%d%d%d"*/,
                 nRoundtrip, nArtifactSent, nArtifactRcvd);
    fossil_force_newline();
  }
  fossil_force_newline();
  if( g.zHttpCmd==0 ){
    if( syncFlags & SYNC_VERBOSE ){
    if( syncFlags & SYNC_QUIET ){
      /* no-op */
    }else if( syncFlags & SYNC_VERBOSE ){
      fossil_print(
        "%s done, wire bytes sent: %lld  received: %lld  remote: %s%s\n",
        zOpType, nSent, nRcvd,
        (g.url.name && g.url.name[0]!='\0') ? g.url.name : "",
        (g.zIpAddr && g.zIpAddr[0]!='\0'
          && fossil_strcmp(g.zIpAddr, g.url.name))
          ? mprintf(" (%s)", g.zIpAddr) : "");
Added src/xsystem.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
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
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
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
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
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
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/*
** Copyright (c) 2025 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@sqlite.org
**
*******************************************************************************
**
** This file contains code used to implement "fossil system ..." command.
**
** Fossil is frequently used by people familiar with Unix but who must
** sometimes also work on Windows systems.  The "fossil sys ..." command
** provides a few work-arounds for command unix command-line utilities to
** help make development on Windows more habitable for long-time unix
** users.  The commands provided here are normally cheap substitutes to
** their more feature-reach unix counterparts.  But they are sufficient to
** get the job done.
**
** This source code file is called "xsystem.c" with the 'x' up front because
** if it were called "system.c", then makeheaders would generate a "system.h"
** header file, and that might be confused with an actual system header
** file.
*/
#include "config.h"
#include "xsystem.h"
#include "qrf.h"
#include <time.h>
#ifdef _WIN32
# include <windows.h>
#endif


/* Date and time */
void xsystem_date(int argc, char **argv){
  (void)argc;
  (void)argv;
  fossil_print("%z = ", cgi_iso8601_datestamp());
  fossil_print("%z\n", cgi_rfc822_datestamp(time(0)));
}

/* Present working directory */
void xsystem_pwd(int argc, char **argv){
  char *zPwd = file_getcwd(0, 0);
  fossil_print("%z\n", zPwd);
}

/* Implement "stty size" */
void xsystem_stty(int argc, char **argv){
  TerminalSize ts;
  if( argc!=2 || strcmp(argv[1],"size")!=0 ){
    fossil_print("ERROR: only \"stty size\" is supported\n");
  }else{
    terminal_get_size(&ts);
    fossil_print("%d %d\n", ts.nLines, ts.nColumns);
  }
}

/* Show where an executable is located on PATH */
void xsystem_which(int argc, char **argv){
  int ePrint = 1;
  int i;
  for(i=1; i<argc; i++){
    const char *z = argv[i];
    if( z[0]!='-' ){
      fossil_app_on_path(z, ePrint);
    }else{
      if( z[1]=='-' && z[2]!=0 ) z++;
      if( fossil_strcmp(z,"-a")==0 ){
        ePrint = 2;
      }else
      {
        fossil_fatal("unknown option \"%s\"", argv[i]);
      }
    }
  }
}

/*
** Bit values for the mFlags paramater to "ls"
*/
#define LS_LONG         0x001   /* -l  Long format - one object per line */
#define LS_REVERSE      0x002   /* -r  Reverse the sort order */
#define LS_MTIME        0x004   /* -t  Sort by mtime, newest first */
#define LS_SIZE         0x008   /* -S  Sort by size, largest first */
#define LS_COMMA        0x010   /* -m  Comma-separated list */
#define LS_DIRONLY      0x020   /* -d  Show just directory name, not content */
#define LS_ALL          0x040   /* -a  Show all entries */
#define LS_COLOR        0x080   /*     Colorize the output */
#define LS_COLUMNS      0x100   /* -C  Split column output */

/* xWrite() callback from QRF
*/
static int xsystem_write(void *NotUsed, const char *zText, sqlite3_int64 n){
  fossil_puts(zText, 0, (int)n);
  return SQLITE_OK;
}

/* Helper function for xsystem_ls():  Make entries in the LS table
** for every file or directory zName.
**
** If zName is a directory, load all files contained within that directory.
** If zName is just a file, load only that file.
*/
static void xsystem_ls_insert(
  sqlite3_stmt *pStmt,
  const char *zName,
  int mFlags
){
  char *aList[2];
  char **azList;
  int nList;
  int i;
  const char *zPrefix;
  switch( file_isdir(zName, ExtFILE) ){
    case 1: {  /* A directory */
      if( (mFlags & LS_DIRONLY)==0 ){
        int omitDots = (mFlags & LS_ALL)!=0 ? 2 : 1;
        azList = 0;
        nList = file_directory_list(zName, 0, omitDots, 0, &azList);
        zPrefix = fossil_strcmp(zName,".") ? zName : 0;
        break;
      }
    }
    case 2: {  /* A file */
      aList[0] = (char*)zName;
      aList[1] = 0;
      azList = aList;
      nList = 1;
      zPrefix = 0;
      break;
    }
    default: {  /* Does not exist */
      return;
    }
  }
  for(i=0; i<nList; i++){
    char *zFile = zPrefix ? mprintf("%s/%s",zPrefix,azList[i]) : azList[i];
    int mode = file_mode(zFile, ExtFILE);
    sqlite3_int64 sz = file_size(zFile, ExtFILE);
    sqlite3_int64 mtime = file_mtime(zFile, ExtFILE);
#ifdef _WIN32
    if( (mFlags & LS_ALL)==0 ){
      wchar_t *zMbcs = fossil_utf8_to_path(zFile, 1);
      DWORD attr = GetFileAttributesW(zMbcs);
      fossil_path_free(zMbcs);
      if( attr & FILE_ATTRIBUTE_HIDDEN ){
        if( zPrefix ) fossil_free(zFile);
        continue;
      }
    }
#endif
    sqlite3_bind_text(pStmt, 1, azList[i], -1, SQLITE_TRANSIENT);
    sqlite3_bind_int64(pStmt, 2, mtime);
    sqlite3_bind_int64(pStmt, 3, sz);
    sqlite3_bind_int(pStmt, 4, mode);
    sqlite3_bind_int64(pStmt, 5, strlen(zFile));
        /* TODO:  wcwidth()------^^^^^^ */
    sqlite3_step(pStmt);
    sqlite3_reset(pStmt);
    if( zPrefix ) fossil_free(zFile);
  }
  if( azList!=aList ){
    file_directory_list_free(azList);
  }
}

/*
** Return arguments to ORDER BY that will correctly sort the entries.
*/
static const char *xsystem_ls_orderby(int mFlags){
  static const char *zSortTypes[] = {
    "fn COLLATE NOCASE",
    "mtime DESC",
    "size DESC",
    "fn COLLATE NOCASE DESC",
    "mtime",
    "size"
  };
  int i = 0;
  if( mFlags & LS_MTIME ) i = 1;
  if( mFlags & LS_SIZE )  i = 2;
  if( mFlags & LS_REVERSE ) i += 3;
  return zSortTypes[i];
}

/*
** color(fn,mode)
**
** SQL function to colorize a filename based on its mode.
*/
static void colorNameFunc(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  const char *zName = (const char*)sqlite3_value_text(argv[0]);
  int iMode = sqlite3_value_int(argv[1]);
  sqlite3_str *pOut;
  if( zName==0 ) return;
  pOut = sqlite3_str_new(0);
#ifdef _WIN32
  if( sqlite3_strlike("%.exe",zName,0)==0 ) iMode |= 0111;
#endif
  if( iMode & 040000 ){
    /* A directory */
    sqlite3_str_appendall(pOut, "\033[1;34m");
  }else if( iMode & 0100 ){
    /* Executable */
    sqlite3_str_appendall(pOut, "\033[1;32m");
  }
  sqlite3_str_appendall(pOut, zName);
  if( (iMode & 040100)!=0 ){
    sqlite3_str_appendall(pOut, "\033[0m");
  }
  sqlite3_result_text(context, sqlite3_str_value(pOut), -1, SQLITE_TRANSIENT);
  sqlite3_str_free(pOut);
}
/* Alternative implementation that does *not* introduce color */
static void nocolorNameFunc(
  sqlite3_context *context,
  int argc,
  sqlite3_value **argv
){
  sqlite3_result_value(context, argv[0]);
}



/*
** Show ls output information for content in the LS table
*/
static void xsystem_ls_render(
  sqlite3 *db,
  int mFlags
){
  sqlite3_stmt *pStmt;
  if( mFlags & LS_COLOR ){
    sqlite3_create_function(db, "color",2,SQLITE_UTF8,0,colorNameFunc,0,0);
  }else{
    sqlite3_create_function(db, "color",2,SQLITE_UTF8,0,nocolorNameFunc,0,0);
  }
  if( (mFlags & LS_LONG)!=0 ){
    /* Long mode */
    char *zSql;
    int szSz = 8;
    sqlite3_prepare_v2(db, "SELECT length(max(size)) FROM ls", -1, &pStmt, 0);
    if( sqlite3_step(pStmt)==SQLITE_ROW ){
      szSz = sqlite3_column_int(pStmt, 0);
    }
    sqlite3_finalize(pStmt);
    pStmt = 0;
    zSql = mprintf(
         "SELECT mode, size, datetime(mtime,'unixepoch'), color(fn,mode)"
         " FROM ls ORDER BY %s",
         xsystem_ls_orderby(mFlags));
    sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
    while( sqlite3_step(pStmt)==SQLITE_ROW ){
      char zMode[12];
      const char *zName = (const char*)sqlite3_column_text(pStmt, 3);
      int mode = sqlite3_column_int(pStmt, 0);
#ifdef _WIN32
      memcpy(zMode, "-rw-", 5);
      if( mode & 040000 ){
        zMode[0] = 'd';
        zMode[3] = 'x';
      }else if( sqlite3_strlike("%.EXE",zName,0)==0 ){
        zMode[3] = 'x';
      }
#else
      memcpy(zMode, "----------", 11);
      if( mode & 040000 ) zMode[0] = 'd';
      if( mode & 0400 ) zMode[1] = 'r';
      if( mode & 0200 ) zMode[2] = 'w';
      if( mode & 0100 ) zMode[3] = 'x';
      if( mode & 0040 ) zMode[4] = 'r';
      if( mode & 0020 ) zMode[5] = 'w';
      if( mode & 0010 ) zMode[6] = 'x';
      if( mode & 0004 ) zMode[7] = 'r';
      if( mode & 0002 ) zMode[8] = 'w';
      if( mode & 0001 ) zMode[9] = 'x';
#endif
      fossil_print("%s %*lld %s %s\n",
         zMode,
         szSz,
         sqlite3_column_int64(pStmt, 1),
         sqlite3_column_text(pStmt, 2),
         zName);
    }
    sqlite3_finalize(pStmt);
  }else if( (mFlags & LS_COMMA)!=0 ){
    /* Comma-separate list */
    int mx = terminal_get_width(80);
    int sumW = 0;
    char *zSql;
    zSql = mprintf("SELECT color(fn,mode), dlen FROM ls ORDER BY %s",
                   xsystem_ls_orderby(mFlags));
    sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
    while( sqlite3_step(pStmt)==SQLITE_ROW ){
      const char *z = (const char*)sqlite3_column_text(pStmt, 0);
      int w = sqlite3_column_int(pStmt, 1);
      if( sumW==0 ){
        fossil_print("%s", z);
        sumW = w;
      }else if( sumW + w + 2 >= mx ){
        fossil_print("\n%s", z);
        sumW = w;
      }else{
        fossil_print(", %s", z);
        sumW += w+2;
      }
    }
    fossil_free(zSql);
    sqlite3_finalize(pStmt);
    if( sumW>0 ) fossil_print("\n");
  }else{
    /* Column mode with just filenames */
    sqlite3_qrf_spec spec;
    char *zSql;
    memset(&spec, 0, sizeof(spec));
    spec.iVersion = 1;
    spec.xWrite = xsystem_write;
    spec.eStyle = QRF_STYLE_Column;
    spec.bTitles = QRF_No;
    spec.eEsc = QRF_No;
    if( mFlags & LS_COLUMNS ){
      spec.nScreenWidth = terminal_get_width(80);
      spec.bSplitColumn = QRF_Yes;
    }
    zSql = mprintf("SELECT color(fn,mode) FROM ls ORDER BY %s",
                   xsystem_ls_orderby(mFlags));
    sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
    fossil_free(zSql);
    sqlite3_format_query_result(pStmt, &spec, 0);
    sqlite3_finalize(pStmt);
  }
  sqlite3_exec(db, "DELETE FROM ls;", 0, 0, 0);
}

/* List files "ls"
** Options:
**
**    -a            Show files that begin with "."
**    -C            List by columns
**    --color=WHEN  Colorize output?
**    -d            Show just directory names, not content
**    -l            Long listing
**    -m            Comma-separated list
**    -r            Reverse sort
**    -S            Sort by size, largest first
**    -t            Sort by mtime, newest first
*/
void xsystem_ls(int argc, char **argv){
  int i, rc;
  sqlite3 *db;
  sqlite3_stmt *pStmt = 0;
  int mFlags = 0;
  int nFile = 0;
  int nDir = 0;
  int bAutoColor = 1;
  int needBlankLine = 0;
  rc = sqlite3_open(":memory:", &db);
  if( rc || db==0 ){
    fossil_fatal("Cannot open in-memory database");
  }
  sqlite3_exec(db, "CREATE TABLE ls(fn,mtime,size,mode,dlen);", 0,0,0);
  rc = sqlite3_prepare_v2(db, "INSERT INTO ls VALUES(?1,?2,?3,?4,?5)",
                          -1, &pStmt, 0);
  if( rc || db==0 ){
    fossil_fatal("Cannot prepare INSERT statement");
  }
  for(i=1; i<argc; i++){
    const char *z = argv[i];
    if( z[0]=='-' ){
      if( z[1]=='-' ){
        if( strncmp(z,"--color",7)==0 ){
          if( z[7]==0 || strcmp(&z[7],"=always")==0 ){
            mFlags |= LS_COLOR;
          }else if( strcmp(&z[7],"=never")==0 ){
            bAutoColor = 0;
          }
        }else{
          fossil_fatal("unknown option: %s", z);
        }
      }else{
        int k;
        for(k=1; z[k]; k++){
          switch( z[k] ){
            case 'a':   mFlags |= LS_ALL;      break;
            case 'd':   mFlags |= LS_DIRONLY;  break;
            case 'l':   mFlags |= LS_LONG;     break;
            case 'm':   mFlags |= LS_COMMA;    break;
            case 'r':   mFlags |= LS_REVERSE;  break;
            case 'S':   mFlags |= LS_SIZE;     break;
            case 't':   mFlags |= LS_MTIME;    break;
            case 'C':   mFlags |= LS_COLUMNS;  break;
            default: {
              fossil_fatal("unknown option: -%c", z[k]);
            }
          }
        }
      }
    }else{
      if( (mFlags & LS_DIRONLY)==0 && file_isdir(z, ExtFILE)==1 ){
        nDir++;
      }else{
        nFile++;
        xsystem_ls_insert(pStmt, z, mFlags);
      }
    }
  }
  if( fossil_isatty(1) ){
    if( bAutoColor ) mFlags |= LS_COLOR;
    mFlags |= LS_COLUMNS;
  }
  if( nFile>0 ){
    xsystem_ls_render(db, mFlags);
    needBlankLine = 1;
  }else if( nDir==0 ){
    xsystem_ls_insert(pStmt, ".", mFlags);
    xsystem_ls_render(db, mFlags);
  }
  if( nDir>0 ){
    for(i=1; i<argc; i++){
      const char *z = argv[i];
      if( z[0]=='-' ) continue;
      if( file_isdir(z, ExtFILE)!=1 ) continue;
      if( needBlankLine ){
        fossil_print("\n");
        needBlankLine = 0;
      }
      fossil_print("%s:\n", z);
      xsystem_ls_insert(pStmt, z, mFlags);
      xsystem_ls_render(db, mFlags);
    }
  }
  sqlite3_finalize(pStmt);
  sqlite3_close(db);
}

/*
** unzip [-l] ZIPFILE
*/
void xsystem_unzip(int argc, char **argv){
  const char *zZipfile = 0;
  int doList = 0;
  int i;
  char *a[5];
  int n;
  extern int sqlite3_shell(int, char**);

  for(i=1; i<argc; i++){
    const char *z = argv[i];
    if( z[0]=='-' ){
      if( z[1]=='-' && z[2]!=0 ) z++;
      if( strcmp(z,"-l")==0 ){
        doList = 1;
      }else
      {
        fossil_fatal("unknown option: %s", argv[i]);
      }
    }else if( zZipfile!=0 ){
      fossil_fatal("extra argument: %s", z);
    }else{
      zZipfile = z;
    }
  }
  if( zZipfile==0 ){
    fossil_fatal("Usage: fossil sys unzip [-l] ZIPFILE");
  }else if( file_size(zZipfile, ExtFILE)<0 ){
    fossil_fatal("No such file: %s\n", zZipfile);
  }
  g.zRepositoryName = 0;
  g.zLocalDbName = 0;
  g.zConfigDbName = 0;
  sqlite3_shutdown();
  a[0] = argv[0];
  a[1] = (char*)zZipfile;
  if( doList ){
    a[2] = ".mode column";
    a[3] = "SELECT sz AS Size, date(mtime,'unixepoch') AS Date,"
                "  time(mtime,'unixepoch') AS Time, name AS Name"
                " FROM zip;";
    n = 4;
  }else{
    a[2] = ".mode list";
    a[3] = "SELECT if(writefile(name,data,mode,mtime) IS NULL,"
                   "'error: '||name,'extracting: '||name) FROM zip;";
    n = 4;
  }
  a[n] = 0;
  sqlite3_shell(n,a);
}

/*
** zip [OPTIONS] ZIPFILE FILE ...
*/
void xsystem_zip(int argc, char **argv){
  int i;
  for(i=0; i<argc; i++){
    g.argv[i+1] = argv[i];
  }
  g.argc = argc+1;
  filezip_cmd();
}

/*
** Available system commands.
*/
typedef struct XSysCmd XSysCmd;
static struct XSysCmd {
  const char *zName;
  void (*xFunc)(int,char**);
  const char *zHelp;
} aXSysCmd[] = {
  { "date", xsystem_date,
    "\n"
    "Show the current system time and date\n"
  },
  { "ls", xsystem_ls,
    "[OPTIONS] [PATH] ...\n"
    "Options:\n"
    "   -a   Show files that begin with '.'\n"
    "   -C   Split columns\n"
    "   -d   Show just directory names, not content\n"
    "   -l   Long listing\n"
    "   -m   Comma-separated list\n"
    "   -r   Reverse sort order\n"
    "   -S   Sort by size, largest first\n"
    "   -t   Sort by mtime, newest first\n"
    "   --color[=WHEN]  Colorize output?\n"
  },
  { "pwd", xsystem_pwd,
    "\n"
    "Show the Present Working Directory name\n"
  },
  { "stty", xsystem_stty,
    "\n"
    "Show the size of the TTY\n"
  },
  { "unzip", xsystem_unzip,
    "[-l] ZIPFILE\n\n"
    "Extract content from ZIPFILE, or list the content if the -l option\n"
    "is used.\n"
  },
  { "which", xsystem_which,
    "EXE ...\n"
    "Show the location on PATH of executables EXE\n"
    "Options:\n"
    "   -a     Show all path locations rather than just the first\n"
  },
  { "zip", xsystem_zip,
    "ZIPFILE FILE ...\n\n"
    "Create a new ZIP archive named ZIPFILE using listed files as content\n"
  },
};

/*
** COMMAND: system
**
** Usage: %fossil system COMMAND ARGS...
**
** Often abbreviated as just "fossil sys", this command provides primitive,
** low-level unix-like commands for use on systems that lack those commands
** natively.
**
** Type "fossil sys help" for a list of available commands.
**
** Type "fossil sys help COMMAND" for detailed help on a particular
** command.
*/
void xsystem_cmd(void){
  int i;
  const char *zCmd;
  int bHelp = 0;
  if( g.argc<=2 || (g.argc==3 && fossil_strcmp(g.argv[2],"help")==0) ){
    fossil_print("Available commands:\n");
    for(i=0; i<count(aXSysCmd); i++){
      if( (i%4)==3 || i==count(aXSysCmd)-1 ){
        fossil_print("  %s\n", aXSysCmd[i].zName);
      }else{
        fossil_print("  %-12s", aXSysCmd[i].zName);
      }
    }
    return;
  }
  zCmd = g.argv[2];
  if( fossil_strcmp(zCmd, "help")==0 ){
    bHelp = 1;
    zCmd = g.argv[3]; 
  }
  for(i=0; i<count(aXSysCmd); i++){
    if( fossil_strcmp(zCmd,aXSysCmd[i].zName)==0 ){
      if( !bHelp ){
        aXSysCmd[i].xFunc(g.argc-2, g.argv+2);
      }else{
        fossil_print("Usage: fossil system %s %s", zCmd, aXSysCmd[i].zHelp);
      }
      return;
    }
  }
  fossil_fatal("Unknown system command \"%s\"."
          " Use \"%s system help\" for a list of available commands",
          zCmd, g.argv[0]);
}
Changes to src/zip.c.
228
229
230
231
232
233
234
235

236
237
238
239
240
241
242
228
229
230
231
232
233
234

235
236
237
238
239
240
241
242







-
+







  M = atoi(&zDate[14]);
  S = atoi(&zDate[17]);
  dosTime = (H<<11) + (M<<5) + (S>>1);
  dosDate = ((y-1980)<<9) + (m<<5) + d;
}

/*
** Set the date and time from a julian day number.
** 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 = (int)((rDate - 2440587.5)*86400.0);
}
265
266
267
268
269
270
271
272

273
274
275
276
277
278
279
265
266
267
268
269
270
271

272
273
274
275
276
277
278
279







-
+







  int iMode = 0644;          /* Access permissions */
  char *z;
  char zHdr[30];
  char zExTime[13];
  char zBuf[100];
  char zOutBuf[100000];

  /* Fill in as much of the header as we know.
  /* Fill inasmuch of the header as we know.
  */
  nameLen = (int)strlen(zName);
  if( nameLen==0 ) return;
  nBlob = pFile ? blob_size(pFile) : 0;
  if( pFile ){ /* This is a file, possibly empty... */
    iMethod = (nBlob>0) ? 8 : 0; /* Cannot compress zero bytes. */
    switch( mPerm ){
404
405
406
407
408
409
410
411
412
413
414
415
416






417
418
419
420
421
422
423
404
405
406
407
408
409
410






411
412
413
414
415
416
417
418
419
420
421
422
423







-
-
-
-
-
-
+
+
+
+
+
+







    assert( p->db );
    blob_zero(&p->tmp);
    sqlite3_exec(p->db,
        "PRAGMA page_size=512;"
        "PRAGMA journal_mode = off;"
        "PRAGMA cache_spill = off;"
        "BEGIN;"
        "CREATE TABLE sqlar("
          "name TEXT PRIMARY KEY,  -- name of the file\n"
          "mode INT,               -- access permissions\n"
          "mtime INT,              -- last modification time\n"
          "sz INT,                 -- original file size\n"
          "data BLOB               -- compressed content\n"
        "CREATE TABLE sqlar(\n"
          "  name TEXT PRIMARY KEY,  -- name of the file\n"
          "  mode INT,               -- access permissions\n"
          "  mtime INT,              -- last modification time\n"
          "  sz INT,                 -- original file size\n"
          "  data BLOB               -- compressed content\n"
        ");", 0, 0, 0
    );
    sqlite3_prepare(p->db,
        "INSERT INTO sqlar VALUES(?, ?, ?, ?, ?)", -1,
        &p->pInsert, 0
    );
    assert( p->pInsert );
494
495
496
497
498
499
500
501

502
503
504
505
506
507
508
494
495
496
497
498
499
500

501
502
503
504
505
506
507
508







-
+







      zName[i+1] = 0;
      for(j=0; j<nDir; j++){
        if( fossil_strcmp(zName, azDir[j])==0 ) break;
      }
      if( j>=nDir ){
        nDir++;
        azDir = fossil_realloc(azDir, sizeof(azDir[0])*nDir);
        azDir[j] = mprintf("%s", zName);
        azDir[j] = fossil_strdup(zName);
        zip_add_file(p, zName, 0, 0);
      }
      zName[i+1] = c;
    }
  }
}

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
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
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







+
+
+
+



+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
-
-

-
-
-
-
+
+
+
-
-
-



+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    fossil_free(azDir[i]);
  }
  fossil_free(azDir);
  nDir = 0;
  azDir = 0;
}

/* Functions found in shell.c */
extern int sqlite3_fileio_init(sqlite3*,char**,const sqlite3_api_routines*);
extern int sqlite3_zipfile_init(sqlite3*,char**,const sqlite3_api_routines*);

/*
** COMMAND: test-filezip
**
** Usage: %fossil test-filezip [OPTIONS] ZIPFILE [FILENAME...]
**
** This command uses Fossil infrastructure or read or create a ZIP
** archive named by the ZIPFILE argument.  With no options, a new
** Generate a ZIP archive specified by the first argument that
** contains files given in the second and subsequent arguments.
** ZIP archive is created and there must be at least one FILENAME
** argument. If the -l option is used, the contents of the named ZIP
** archive are listed on standard output.  With the -x argument, the
** contents of the ZIP archive are extracted.
**
** There are two purposes for this command:  (1) To server as a test
** platform for the Fossil ZIP archive generator, and (2) to provide
** rudimentary ZIP archive creation capabilities on platforms that do
** not have the "zip" command installed.
**
** Options:
**
**    -h|--dereference    Follow symlinks
**    -l|--list           List the contents of the ZIP archive
**    -x|--extract        Extract files from a ZIP archive
*/
void filezip_cmd(void){
  int i;
  Blob zip;
  Blob file;
  int eFType = SymFILE;
  Archive sArchive;
  memset(&sArchive, 0, sizeof(Archive));
  sArchive.eType = ARCHIVE_ZIP;
  sArchive.pBlob = &zip;
  int doList = 0;
  int doExtract = 0;
  char *zArchiveName;
  if( g.argc<3 ){
    usage("ARCHIVE FILE....");
  }
  if( find_option("dereference","h",0)!=0 ){
    eFType = ExtFILE;
  }
  if( find_option("list","l",0)!=0 ){
    doList = 1;
  }
  if( find_option("extract","x",0)!=0 ){
    if( doList ){
      fossil_fatal("incompatible options: -l and -x");
    }
    doExtract = 1;
  }
  if( g.argc<3 ){
    usage("ARCHIVE FILES...");
  }
  zArchiveName = g.argv[2];
  sqlite3_open(":memory:", &g.db);
  if( doList ){
    /* Do a content listing of a ZIP archive */
    Stmt q;
    int nRow = 0;
    i64 szTotal = 0;
    if( file_size(zArchiveName, eFType)<0 ){
      fossil_fatal("No such ZIP archive: %s", zArchiveName);
    }
    if( g.argc>3 ){
      fossil_fatal("extra arguments after \"fossil test-filezip -l ARCHIVE\"");
    }
    sqlite3_zipfile_init(g.db, 0, 0);
    db_multi_exec("CREATE VIRTUAL TABLE z1 USING zipfile(%Q)", zArchiveName);
    db_prepare(&q, "SELECT sz, datetime(mtime,'unixepoch'), name FROM z1");
    while( db_step(&q)==SQLITE_ROW ){
      int sz = db_column_int(&q, 0);
      szTotal += sz;
      if( nRow==0 ){
        fossil_print("  Length      Date    Time    Name\n");
        fossil_print("---------  ---------- -----   ----\n");
      }
      nRow++;
      fossil_print("%9d  %.16s   %s\n", sz, db_column_text(&q,1),
                   db_column_text(&q,2));
    }
    if( nRow ){
      fossil_print("---------                     --------\n");
      fossil_print("%9lld  %16s   %d files\n", szTotal, "", nRow);
    }
    db_finalize(&q);
  }else if( doExtract ){
    /* Extract files from an existing ZIP archive */
    if( file_size(zArchiveName, eFType)<0 ){
      fossil_fatal("No such ZIP archive: %s", zArchiveName);
    }
    if( g.argc>3 ){
      fossil_fatal("extra arguments after \"fossil test-filezip -x ARCHIVE\"");
    }
    sqlite3_zipfile_init(g.db, 0, 0);
    sqlite3_fileio_init(g.db, 0, 0);
    db_multi_exec("CREATE VIRTUAL TABLE z1 USING zipfile(%Q)", zArchiveName);
    db_multi_exec("SELECT writefile(name,data) FROM z1");
  }else{
    /* Without the -x or -l options, construct a new ZIP archive */
    int i;
    Blob zip;
    Blob file;
    Archive sArchive;
    memset(&sArchive, 0, sizeof(Archive));
    sArchive.eType = ARCHIVE_ZIP;
    sArchive.pBlob = &zip;
    if( file_size(zArchiveName, eFType)>0 ){
      fossil_fatal("ZIP archive %s already exists", zArchiveName);
    }
  zip_open();
  for(i=3; i<g.argc; i++){
    blob_zero(&file);
    blob_read_from_file(&file, g.argv[i], eFType);
    zip_add_file(&sArchive, g.argv[i], &file, file_perm(0,eFType));
    blob_reset(&file);
  }
  zip_close(&sArchive);
  blob_write_to_file(&zip, g.argv[2]);
    zip_open();
    for(i=3; i<g.argc; i++){
      double rDate;
      i64 iDate;
      blob_zero(&file);
      blob_read_from_file(&file, g.argv[i], eFType);
      iDate = file_mtime(g.argv[i], eFType);
      rDate = ((double)iDate)/86400.0 + 2440587.5;
      zip_set_timedate(rDate);
      zip_add_file(&sArchive, g.argv[i], &file, file_perm(0,eFType));
      blob_reset(&file);
    }
    zip_close(&sArchive);
    blob_write_to_file(&zip, g.argv[2]);
  }
}

/*
** Given the RID for a manifest, construct a ZIP archive containing
** all files in the corresponding baseline.
**
** If RID is for an object that is not a real manifest, then the
650
651
652
653
654
655
656
657

658
659
660
661
662
663
664
738
739
740
741
742
743
744

745
746
747
748
749
750
751
752







-
+







  nPrefix = blob_size(&filename);

  pManifest = manifest_get(rid, CFTYPE_MANIFEST, 0);
  if( pManifest ){
    int flg, eflg = 0;
    char *zName = 0;
    zip_set_timedate(pManifest->rDate);
    flg = db_get_manifest_setting();
    flg = db_get_manifest_setting(blob_str(&hash));
    if( flg ){
      /* eflg is the effective flags, taking include/exclude into account */
      if( (pInclude==0 || glob_match(pInclude, "manifest"))
       && !glob_match(pExclude, "manifest")
       && (flg & MFESTFLG_RAW) ){
        eflg |= MFESTFLG_RAW;
      }
774
775
776
777
778
779
780
781

782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
862
863
864
865
866
867
868

869








870
871
872
873
874
875
876







-
+
-
-
-
-
-
-
-
-







  }
  zOut = g.argv[3];
  if( fossil_strcmp(zOut,"")==0 || fossil_strcmp(zOut,"/dev/null")==0 ){
    zOut = 0;
  }

  if( zName==0 ){
    zName = db_text("default-name",
    zName = archive_base_name(rid);
       "SELECT replace(%Q,' ','_') "
          " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) "
          " || substr(blob.uuid, 1, 10)"
       "  FROM event, blob"
       " WHERE event.objid=%d"
       "   AND blob.rid=%d",
       db_get("project-name", "unnamed"), rid, rid
    );
  }
  zip_of_checkin(eType, rid, zOut ? &zip : 0,
                 zName, pInclude, pExclude, listFlag);
  glob_free(pInclude);
  glob_free(pExclude);
  if( zOut ){
    blob_write_to_file(&zip, zOut);
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
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
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
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







-
+
+












-
+
+





-
+
+





-
+
+

-
+






+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

















+












-
+







**     /zip/[VERSION/]NAME.zip
**     /sqlar/[VERSION/]NAME.sqlar
**
** Generate a ZIP Archive or an SQL Archive for the check-in specified by
** VERSION.  The archive is called NAME.zip or NAME.sqlar and has a top-level
** directory called NAME.
**
** The optional VERSION element defaults to "trunk" per the r= rules below.
** The optional VERSION element defaults to the name of the main branch
** (usually "trunk") per the r= rules below.
** All of the following URLs are equivalent:
**
**      /zip/release/xyz.zip
**      /zip?r=release&name=xyz.zip
**      /zip/xyz.zip?r=release
**      /zip?name=release/xyz.zip
**
** Query parameters:
**
**   name=[CKIN/]NAME    The optional CKIN component of the name= parameter
**                       identifies the check-in from which the archive is
**                       constructed.  If CKIN is omitted and there is no
**                       r= query parameter, then use "trunk".  NAME is the
**                       r= query parameter, then use the name of the main
**                       branch (usually "trunk").  NAME is the
**                       name of the download file.  The top-level directory
**                       in the generated archive is called by NAME with the
**                       file extension removed.
**
**   r=TAG               TAG identifies the check-in that is turned into an
**                       SQL or ZIP archive.  The default value is "trunk".
**                       SQL or ZIP archive.  The default value is the name
**                       of the main branch (usually "trunk").
**                       If r= is omitted and if the name= query parameter
**                       contains one "/" character then the of part the
**                       name= value before the / becomes the TAG and the
**                       part of the name= value  after the / is the download
**                       filename.  If no check-in is specified by either
**                       name= or r=, then "trunk" is used.
**                       name= or r=, then the name of the main branch
**                       (usually "trunk") is used.
**
**   in=PATTERN          Only include files that match the comma-separate
**   in=PATTERN          Only include files that match the comma-separated
**                       list of GLOB patterns in PATTERN, as with ex=
**
**   ex=PATTERN          Omit any file that match PATTERN.  PATTERN is a
**                       comma-separated list of GLOB patterns, where each
**                       pattern can optionally be quoted using ".." or '..'.
**                       Any file matching both ex= and in= is excluded.
**
** Robot Defenses:
**
**   *    If "zip" appears in the robot-restrict setting, then robots are
**        not allowed to access this page.  Suspected robots will be
**        presented with a captcha.
**
**   *    If "zipX" appears in the robot-restrict setting, then robots are
**        restricted in the same way as with "zip", but with exceptions.
**        If the check-in for which an archive is requested is a leaf check-in
**        and if the robot-zip-leaf setting is true, then the request is
**        allowed.  Or if the check-in has a tag that matches any of the
**        GLOB patterns on the list in the robot-zip-tag setting, then the
**        request is allowed.  Otherwise, the usual robot defenses are
**        activated.
*/
void baseline_zip_page(void){
  int rid;
  const char *z;
  char *zName, *zRid, *zKey;
  int nName, nRid;
  const char *zInclude;         /* The in= query parameter */
  const char *zExclude;         /* The ex= query parameter */
  Blob cacheKey;                /* The key to cache */
  Glob *pInclude = 0;           /* The compiled in= glob pattern */
  Glob *pExclude = 0;           /* The compiled ex= glob pattern */
  Blob zip;                     /* ZIP archive accumulated here */
  int eType = ARCHIVE_ZIP;      /* Type of archive to generate */
  char *zType;                  /* Human-readable archive type */

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(g.anon.Zip); return; }
  if( robot_restrict("zip") ) return;
  if( fossil_strcmp(g.zPath, "sqlar")==0 ){
    eType = ARCHIVE_SQLAR;
    zType = "SQL";
  }else{
    eType = ARCHIVE_ZIP;
    zType = "ZIP";
  }
  fossil_nice_default();
  zName = fossil_strdup(PD("name",""));
  z = P("r");
  if( z==0 ) z = P("uuid");
  if( z==0 ) z = tar_uuid_from_name(&zName);
  if( z==0 ) z = "trunk";
  if( z==0 ) z = fossil_strdup(db_main_branch());
  nName = strlen(zName);
  g.zOpenRevision = zRid = fossil_strdup(z);
  nRid = strlen(zRid);
  zInclude = P("in");
  if( zInclude ) pInclude = glob_create(zInclude);
  zExclude = P("ex");
  if( zExclude ) pExclude = glob_create(zExclude);
975
976
977
978
979
980
981

982
983
984
985
986
987
988
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089







+







  }
  rid = symbolic_name_to_rid(nRid?zRid:zName, "ci");
  if( rid<=0 ){
    cgi_set_status(404, "Not Found");
    @ Not found
    return;
  }
  if( robot_restrict_zip(rid) ) return;
  if( nRid==0 && nName>10 ) zName[10] = 0;

  /* Compute a unique key for the cache entry based on query parameters */
  blob_init(&cacheKey, 0, 0);
  blob_appendf(&cacheKey, "/%s/%z", g.zPath, rid_to_uuid(rid));
  blob_appendf(&cacheKey, "/%q", zName);
  if( zInclude ) blob_appendf(&cacheKey, ",in=%Q", zInclude);
Deleted test/comment.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
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
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


























































































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#
# 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/
#
############################################################################
#
# Test comment formatting and printing.
#

test_setup ""

###############################################################################

fossil test-comment-format "" ""
test comment-1 {$RESULT eq "\n(1 lines output)"}

###############################################################################

fossil test-comment-format --decode "" ""
test comment-2 {$RESULT eq "\n(1 lines output)"}

###############################################################################

fossil test-comment-format --width 26 " " "this is a short comment."
test comment-3 {$RESULT eq " this is a short comment.\n(1 lines output)"}

###############################################################################

fossil test-comment-format --width 26 --decode " " "this is a short comment."
test comment-4 {$RESULT eq " this is a short comment.\n(1 lines output)"}

###############################################################################

fossil test-comment-format --width 26 "*PREFIX* " "this is a short comment."
test comment-5 {$RESULT eq "*PREFIX* this is a short c\n         omment.\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 26 --decode "*PREFIX* " "this is a short comment."
test comment-6 {$RESULT eq "*PREFIX* this is a short c\n         omment.\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 26 "" "this\\sis\\sa\\sshort\\scomment."
test comment-7 {$RESULT eq "this\\sis\\sa\\sshort\\scommen\nt.\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 26 --decode "" "this\\sis\\sa\\sshort\\scomment."
test comment-8 {$RESULT eq "this is a short comment.\n(1 lines output)"}

###############################################################################

fossil test-comment-format --width 78 --decode --trimspace "HH:MM:SS " "this is a long comment that should span multiple lines if the test is working correctly."
test comment-9 {$RESULT eq "HH:MM:SS this is a long comment that should span multiple lines if the test is\n         working correctly.\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 78 --decode --trimspace "HH:MM:SS " "this is a long comment that should span multiple lines if the test is working correctly.  more text here describing the issue.\\nanother line here..................................................................................*"
test comment-10 {$RESULT eq "HH:MM:SS this is a long comment that should span multiple lines if the test is\n         working correctly.  more text here describing the issue.\n         another line here....................................................\n         ..............................*\n(4 lines output)"}

###############################################################################

fossil test-comment-format --width 78 "HH:MM:SS " "....................................................................................*"
test comment-11 {$RESULT eq "HH:MM:SS .....................................................................\n         ...............*\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 78 "HH:MM:SS " ".....................................................................*" 78
test comment-12 {$RESULT eq "HH:MM:SS .....................................................................\n         *\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 26 "*TEST* " "this\tis a test."
test comment-13 {$RESULT eq "*TEST* this\tis a te\n       st.\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 60 "*TEST* " "this is a test......................................................................................................................."
test comment-14 {$RESULT eq "*TEST* this is a test.......................................\n       .....................................................\n       ...........................\n(3 lines output)"}

###############################################################################

fossil test-comment-format --width 60 --wordbreak "*TEST* " "this is a test......................................................................................................................."
test comment-15 {$RESULT eq "*TEST* this is a\n       test.................................................\n       .....................................................\n       .................\n(4 lines output)"}

###############################################################################

fossil test-comment-format --width 60 "*TEST* " "this	is	a	test......................................................................................................................."
test comment-16 {$RESULT eq "*TEST* this	is	a\n       test.................................................\n       .....................................................\n       .................\n(4 lines output)"}

###############################################################################

fossil test-comment-format --width 60 --wordbreak "*TEST* " "this	is	a	test......................................................................................................................."
test comment-17 {$RESULT eq "*TEST* this	is	a\n       test.................................................\n       .....................................................\n       .................\n(4 lines output)"}

###############################################################################

fossil test-comment-format --width 60 "*TEST* " "one two three four five six seven eight nine ten eleven twelve"
test comment-18 {$RESULT eq "*TEST* one two three four five six seven eight nine ten elev\n       en twelve\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 60 --wordbreak "*TEST* " "one two three four five six seven eight nine ten eleven twelve"
test comment-19 {$RESULT eq "*TEST* one two three four five six seven eight nine ten\n       eleven twelve\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 60 "*TEST* " "one	two	three	four	five	six	seven	eight	nine	ten	eleven	twelve"
test comment-20 {$RESULT eq "*TEST* one	two	three	four	five\n       six	seven	eight	nine	ten\n       eleven	twelve\n(3 lines output)"}

###############################################################################

fossil test-comment-format --width 60 --wordbreak "*TEST* " "one	two	three	four	five	six	seven	eight	nine	ten	eleven	twelve"
test comment-21 {$RESULT eq "*TEST* one	two	three	four	five\n       six	seven	eight	nine	ten\n       eleven	twelve\n(3 lines output)"}

###############################################################################

fossil test-comment-format --legacy "" ""
test comment-22 {$RESULT eq "\n(1 lines output)"}

###############################################################################

fossil test-comment-format --legacy --decode "" ""
test comment-23 {$RESULT eq "\n(1 lines output)"}

###############################################################################

fossil test-comment-format --width 26 --legacy " " "this is a short comment."
test comment-24 {$RESULT eq " this is a short comment.\n(1 lines output)"}

###############################################################################

fossil test-comment-format --width 26 --legacy --decode " " "this is a short comment."
test comment-25 {$RESULT eq " this is a short comment.\n(1 lines output)"}

###############################################################################

fossil test-comment-format --width 25 --legacy "*PREFIX* " "this is a short comment."
test comment-26 {$RESULT eq "*PREFIX* this is a short\n         comment.\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 25 --legacy --decode "*PREFIX* " "this is a short comment."
test comment-27 {$RESULT eq "*PREFIX* this is a short\n         comment.\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 26 --legacy "" "this\\sis\\sa\\sshort\\scomment."
test comment-28 {$RESULT eq "this\\sis\\sa\\sshort\\scommen\nt.\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 26 --legacy --decode "" "this\\sis\\sa\\sshort\\scomment."
test comment-29 {$RESULT eq "this is a short comment.\n(1 lines output)"}

###############################################################################

fossil test-comment-format --width 78 --legacy --decode "HH:MM:SS " "this is a long comment that should span multiple lines if the test is working correctly."
test comment-30 {$RESULT eq "HH:MM:SS this is a long comment that should span multiple lines if the test\n         is working correctly.\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 78 --legacy --decode "HH:MM:SS " "this is a long comment that should span multiple lines if the test is working correctly.  more text here describing the issue.\\nanother line here..................................................................................*"
test comment-31 {$RESULT eq "HH:MM:SS this is a long comment that should span multiple lines if the test\n         is working correctly. more text here describing the issue. another\n         line\n         here.................................................................\n         .................*\n(5 lines output)"}

###############################################################################

fossil test-comment-format --width 78 --legacy "HH:MM:SS " "....................................................................................*"
test comment-32 {$RESULT eq "HH:MM:SS .....................................................................\n         ...............*\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 78 --legacy "HH:MM:SS " ".....................................................................*"
test comment-33 {$RESULT eq "HH:MM:SS .....................................................................\n         *\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 26 --legacy "*TEST* " "this\tis a test."
test comment-34 {$RESULT eq "*TEST* this is a test.\n(1 lines output)"}

###############################################################################

fossil test-comment-format --width 60 --legacy "*TEST* " "this is a test......................................................................................................................."
test comment-35 {$RESULT eq "*TEST* this is a\n       test.................................................\n       .....................................................\n       .................\n(4 lines output)"}

###############################################################################

fossil test-comment-format --width 60 --legacy --wordbreak "*TEST* " "this is a test......................................................................................................................."
test comment-36 {$RESULT eq "*TEST* this is a\n       test.................................................\n       .....................................................\n       .................\n(4 lines output)"}

###############################################################################

fossil test-comment-format --width 60 --legacy "*TEST* " "this	is	a	test......................................................................................................................."
test comment-37 {$RESULT eq "*TEST* this is a\n       test.................................................\n       .....................................................\n       .................\n(4 lines output)"}

###############################################################################

fossil test-comment-format --width 60 --legacy --wordbreak "*TEST* " "this	is	a	test......................................................................................................................."
test comment-38 {$RESULT eq "*TEST* this is a\n       test.................................................\n       .....................................................\n       .................\n(4 lines output)"}

###############################################################################

fossil test-comment-format --width 60 --legacy "*TEST* " "one two three four five six seven eight nine ten eleven twelve"
test comment-39 {$RESULT eq "*TEST* one two three four five six seven eight nine ten\n       eleven twelve\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 60 --legacy --wordbreak "*TEST* " "one two three four five six seven eight nine ten eleven twelve"
test comment-40 {$RESULT eq "*TEST* one two three four five six seven eight nine ten\n       eleven twelve\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 60 --legacy "*TEST* " "one	two	three	four	five	six	seven	eight	nine	ten	eleven	twelve"
test comment-41 {$RESULT eq "*TEST* one two three four five six seven eight nine ten\n       eleven twelve\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 60 --legacy --wordbreak "*TEST* " "one	two	three	four	five	six	seven	eight	nine	ten	eleven	twelve"
test comment-42 {$RESULT eq "*TEST* one two three four five six seven eight nine ten\n       eleven twelve\n(2 lines output)"}

###############################################################################

set orig "xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\\nxxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\\nxxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\\nxxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\\nxxxxxxx."
fossil test-comment-format --width 73 --decode --origbreak "" $orig
test comment-43 {$RESULT eq "xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\nxxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\nxxxxxxx.\n(5 lines output)"}

###############################################################################

fossil test-comment-format --width 73 --decode --origbreak "" $orig $orig
test comment-44 {$RESULT eq "xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\nxxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\nxxxxxxx.\n(5 lines output)"}

###############################################################################

fossil test-comment-format --width 73 --decode --origbreak "" "00:00:00 \[0000000000\] *CURRENT* $orig" $orig
test comment-45 {$RESULT eq "00:00:00 \[0000000000\] *CURRENT* \nxxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\nxxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\nxxxxxxx.\n(6 lines output)"}

###############################################################################

fossil test-comment-format --width 82 --indent 9 --decode --origbreak "         " $orig
test comment-46 {$RESULT eq "         xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n         xxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\n         xxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\n         xxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\n         xxxxxxx.\n(5 lines output)"}

###############################################################################

fossil test-comment-format --width 82 --indent 9 --decode --origbreak "         " $orig $orig
test comment-47 {$RESULT eq "         xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n         xxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\n         xxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\n         xxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\n         xxxxxxx.\n(5 lines output)"}

###############################################################################

fossil test-comment-format --width 82 --indent 9 --decode --origbreak "00:00:00 " "\[0000000000\] *CURRENT* $orig" $orig
test comment-48 {$RESULT eq "00:00:00 \[0000000000\] *CURRENT* \n         xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n         xxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\n         xxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\n         xxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\n         xxxxxxx.\n(6 lines output)"}

###############################################################################

fossil test-comment-format --width 72 --decode --trimspace --origbreak "" $orig
test comment-49 {$RESULT eq "xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\nxxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\nxxxxxxx.\n(5 lines output)"}

###############################################################################

fossil test-comment-format --width 72 --decode --trimspace --origbreak "" $orig $orig
test comment-50 {$RESULT eq "xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\nxxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\nxxxxxxx.\n(5 lines output)"}

###############################################################################

fossil test-comment-format --width 72 --decode --trimspace --origbreak "" "00:00:00 \[0000000000\] *CURRENT* $orig" $orig
test comment-51 {$RESULT eq "00:00:00 \[0000000000\] *CURRENT* \nxxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\nxxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\nxxxxxxx.\n(6 lines output)"}

###############################################################################

fossil test-comment-format --width 81 --indent 9 --decode --trimspace --origbreak "         " $orig
test comment-52 {$RESULT eq "         xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n         xxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\n         xxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\n         xxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\n         xxxxxxx.\n(5 lines output)"}

###############################################################################

fossil test-comment-format --width 81 --indent 9 --decode --trimspace --origbreak "         " $orig $orig
test comment-53 {$RESULT eq "         xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n         xxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\n         xxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\n         xxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\n         xxxxxxx.\n(5 lines output)"}

###############################################################################

fossil test-comment-format --width 81 --indent 9 --decode --trimspace --origbreak "00:00:00 " "\[0000000000\] *CURRENT* $orig" $orig
test comment-54 {$RESULT eq "00:00:00 \[0000000000\] *CURRENT* \n         xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n         xxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\n         xxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\n         xxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\n         xxxxxxx.\n(6 lines output)"}

###############################################################################

fossil test-comment-format --width 72 --decode --trimcrlf --origbreak "" $orig
test comment-55 {$RESULT eq "xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\nxxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\nxxxxxxx.\n(5 lines output)"}

###############################################################################

fossil test-comment-format --width 72 --decode --trimcrlf --origbreak "" $orig $orig
test comment-56 {$RESULT eq "xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\nxxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\nxxxxxxx.\n(5 lines output)"}

###############################################################################

fossil test-comment-format --width 72 --decode --trimcrlf --origbreak "" "00:00:00 \[0000000000\] *CURRENT* $orig" $orig
test comment-57 {$RESULT eq "00:00:00 \[0000000000\] *CURRENT* \nxxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\nxxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\nxxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\nxxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\nxxxxxxx.\n(6 lines output)"}

###############################################################################

fossil test-comment-format --width 81 --indent 9 --decode --trimcrlf --origbreak "         " $orig
test comment-58 {$RESULT eq "         xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n         xxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\n         xxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\n         xxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\n         xxxxxxx.\n(5 lines output)"}

###############################################################################

fossil test-comment-format --width 81 --indent 9 --decode --trimcrlf --origbreak "         " $orig $orig
test comment-59 {$RESULT eq "         xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n         xxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\n         xxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\n         xxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\n         xxxxxxx.\n(5 lines output)"}

###############################################################################

fossil test-comment-format --width 81 --indent 9 --decode --trimcrlf --origbreak "00:00:00 " "\[0000000000\] *CURRENT* $orig" $orig
test comment-60 {$RESULT eq "00:00:00 \[0000000000\] *CURRENT* \n         xxxx xx xxxxxxx xxxx xxxxxx xxxxxxx, xxxxxxx, x xxxx xxxxxx xx xxxx xxxx\n         xxxxxxx xxxxx xxxx  xxxx xx xxxxxxx xxxxxxx (xxxxxx  xxxxxxxxx x xxxxx).\n         xxx'x xxx xxx xx  xxxxx xxxx xxx xxx --xxxxxxxxxxx xxxxxx  xx xx xxxx. x\n         xxxxx  x xxxxxx  xxxx xxxx  xxxx xxxx  xxxx x  xxxxx xx  xxx x  xxxxxxxx\n         xxxxxxx.\n(6 lines output)"}

###############################################################################

fossil test-comment-format --width 72 --file "" [file join $testdir "utf8-comment.txt"]
test comment-61 {$RESULT eq "The comment formatter handles fullwidth and multi-byte \[äöü\] an\nd symbols \[☃\] and emoji \[💾\] characters!\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 72 --wordbreak --file "" [file join $testdir "utf8-comment.txt"]
test comment-62 {$RESULT eq "The comment formatter handles fullwidth and multi-byte \[äöü\]\nand symbols \[☃\] and emoji \[💾\] characters!\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 72 --legacy --file "" [file join $testdir "utf8-comment.txt"]
test comment-63 {$RESULT eq "The comment formatter handles fullwidth and multi-byte \[äöü\]\nand symbols \[☃\] and emoji \[💾\] characters!\n(2 lines output)"}

###############################################################################

fossil test-comment-format --width 72 --legacy --wordbreak --file "" [file join $testdir "utf8-comment.txt"]
test comment-64 {$RESULT eq "The comment formatter handles fullwidth and multi-byte \[äöü\]\nand symbols \[☃\] and emoji \[💾\] characters!\n(2 lines output)"}

###############################################################################

test_cleanup
Changes to test/commit-warning.test.
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
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
158
159
160
161
162
163
164











































































































































165
166







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-


1\tutf-nobom-16be.txt\tbinary data
1\tutf-nobom-16le.txt\tbinary data
1}]]}


###############################################################################

# TODO: Change to a collection of test-case crafted files
#       rather than depend on this list of files that will
#       be fragile as development progresses.
#
# Unless the real goal of this test is to document a collection
# of source files that MUST NEVER BE TEXT.
#
test_block_in_checkout pre-commit-warnings-fossil-1 {
  fossil test-commit-warning --no-settings
} {
  test pre-commit-warnings-fossil-1 {[normalize_result] eq \
      [subst -nocommands -novariables [string trim {
1\tcompat/zlib/contrib/blast/test.pk\tbinary data
1\tcompat/zlib/contrib/dotzlib/DotZLib.build\tCR/LF line endings
1\tcompat/zlib/contrib/dotzlib/DotZLib.chm\tbinary data
1\tcompat/zlib/contrib/dotzlib/DotZLib.sln\tCR/LF line endings
1\tcompat/zlib/contrib/dotzlib/DotZLib/AssemblyInfo.cs\tCR/LF line endings
1\tcompat/zlib/contrib/dotzlib/DotZLib/ChecksumImpl.cs\tinvalid UTF-8
1\tcompat/zlib/contrib/dotzlib/DotZLib/CircularBuffer.cs\tinvalid UTF-8
1\tcompat/zlib/contrib/dotzlib/DotZLib/CodecBase.cs\tinvalid UTF-8
1\tcompat/zlib/contrib/dotzlib/DotZLib/Deflater.cs\tinvalid UTF-8
1\tcompat/zlib/contrib/dotzlib/DotZLib/DotZLib.cs\tinvalid UTF-8
1\tcompat/zlib/contrib/dotzlib/DotZLib/DotZLib.csproj\tCR/LF line endings
1\tcompat/zlib/contrib/dotzlib/DotZLib/GZipStream.cs\tinvalid UTF-8
1\tcompat/zlib/contrib/dotzlib/DotZLib/Inflater.cs\tinvalid UTF-8
1\tcompat/zlib/contrib/dotzlib/DotZLib/UnitTests.cs\tCR/LF line endings
1\tcompat/zlib/contrib/dotzlib/LICENSE_1_0.txt\tCR/LF line endings
1\tcompat/zlib/contrib/dotzlib/readme.txt\tCR/LF line endings
1\tcompat/zlib/contrib/gcc_gvmat64/gvmat64.S\tCR/LF line endings
1\tcompat/zlib/contrib/puff/zeros.raw\tbinary data
1\tcompat/zlib/contrib/testzlib/testzlib.c\tCR/LF line endings
1\tcompat/zlib/contrib/testzlib/testzlib.txt\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/readme.txt\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/miniunz.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/miniunz.vcxproj.filters\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/minizip.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/minizip.vcxproj.filters\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/testzlib.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/testzlib.vcxproj.filters\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/testzlibdll.vcxproj.filters\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/zlib.rc\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/zlibstat.vcxproj.filters\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.def\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.sln\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc10/zlibvc.vcxproj.filters\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/miniunz.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/minizip.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/testzlib.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/testzlibdll.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/zlib.rc\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/zlibstat.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.def\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.sln\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc11/zlibvc.vcxproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc12/zlibvc.def\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc14/zlibvc.def\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/miniunz.vcproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/minizip.vcproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/testzlib.vcproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/testzlibdll.vcproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/zlib.rc\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/zlibstat.vcproj\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.def\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.sln\tCR/LF line endings
1\tcompat/zlib/contrib/vstudio/vc9/zlibvc.vcproj\tCR/LF line endings
1\tcompat/zlib/win32/zlib.def\tCR/LF line endings
1\tcompat/zlib/zlib.3.pdf\tbinary data
1\tcompat/zlib/zlib.map\tCR/LF line endings
1\textsrc/pikchr.wasm\tbinary data
1\tskins/blitz/arrow_project.png\tbinary data
1\tskins/blitz/dir.png\tbinary data
1\tskins/blitz/file.png\tbinary data
1\tskins/blitz/fossil_100.png\tbinary data
1\tskins/blitz/fossil_80_reversed_darkcyan.png\tbinary data
1\tskins/blitz/fossil_80_reversed_darkcyan_text.png\tbinary data
1\tskins/blitz/rss_20.png\tbinary data
1\tsrc/alerts/bflat2.wav\tbinary data
1\tsrc/alerts/bflat3.wav\tbinary data
1\tsrc/alerts/bloop.wav\tbinary data
1\tsrc/alerts/plunk.wav\tbinary data
1\tsrc/sounds/0.wav\tbinary data
1\tsrc/sounds/1.wav\tbinary data
1\tsrc/sounds/2.wav\tbinary data
1\tsrc/sounds/3.wav\tbinary data
1\tsrc/sounds/4.wav\tbinary data
1\tsrc/sounds/5.wav\tbinary data
1\tsrc/sounds/6.wav\tbinary data
1\tsrc/sounds/7.wav\tbinary data
1\tsrc/sounds/8.wav\tbinary data
1\tsrc/sounds/9.wav\tbinary data
1\tsrc/sounds/a.wav\tbinary data
1\tsrc/sounds/b.wav\tbinary data
1\tsrc/sounds/c.wav\tbinary data
1\tsrc/sounds/d.wav\tbinary data
1\tsrc/sounds/e.wav\tbinary data
1\tsrc/sounds/f.wav\tbinary data
1\ttest/th1-docs-input.txt\tCR/LF line endings
1\ttest/th1-hooks-input.txt\tCR/LF line endings
1\ttest/utf16be.txt\tUnicode
1\ttest/utf16le.txt\tUnicode
1\twin/buildmsvc.bat\tCR/LF line endings
1\twin/fossil.ico\tbinary data
1\twin/fossil.rc\tinvalid UTF-8
1\twww/apple-touch-icon.png\tbinary data
1\twww/background.jpg\tbinary data
1\twww/build-icons/linux.gif\tbinary data
1\twww/build-icons/linux64.gif\tbinary data
1\twww/build-icons/mac.gif\tbinary data
1\twww/build-icons/openbsd.gif\tbinary data
1\twww/build-icons/src.gif\tbinary data
1\twww/build-icons/win32.gif\tbinary data
1\twww/copyright-release.pdf\tbinary data
1\twww/encode1.gif\tbinary data
1\twww/encode2.gif\tbinary data
1\twww/encode3.gif\tbinary data
1\twww/encode4.gif\tbinary data
1\twww/encode5.gif\tbinary data
1\twww/encode6.gif\tbinary data
1\twww/encode7.gif\tbinary data
1\twww/encode8.gif\tbinary data
1\twww/encode9.gif\tbinary data
1\twww/fossil.gif\tbinary data
1\twww/fossil2.gif\tbinary data
1\twww/fossil3.gif\tbinary data
1\twww/fossil_logo_small.gif\tbinary data
1\twww/fossil_logo_small2.gif\tbinary data
1\twww/fossil_logo_small3.gif\tbinary data
1\twww/server/windows/cgi-bin-perm.png\tbinary data
1\twww/server/windows/cgi-exec-perm.png\tbinary data
1\twww/server/windows/cgi-install-iis.png\tbinary data
1\twww/server/windows/cgi-script-map.png\tbinary data
1\twww/xkcd-git.gif\tbinary data
1}]]}
}

###############################################################################

test_cleanup
Changes to test/json.test.
174
175
176
177
178
179
180
181

182
183
184
185
186
187
188
174
175
176
177
178
179
180

181
182
183
184
185
186
187
188







-
+







# specific fields and that it lacks specific fields.
proc test_json_payload {testname okfields badfields} {
  test_dict_keys $testname [dict get $::JR payload] $okfields $badfields
}

#### VERSION AKA HAI

# The JSON API generally assumes we have a respository, so let it have one.
# The JSON API generally assumes we have a repository, so let it have one.

# Set FOSSIL_USER to ensure consistent results in "json user list"
set _fossil_user ""
if [info exists env(FOSSIL_USER)] {
  set _fossil_user $env(FOSSIL_USER)
}
set ::env(FOSSIL_USER) "JSON-TEST-USER"
Added test/link-tester.html.


















































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<!DOCTYPE html>
<head><!--
   This file is intended to be loaded from a fossil
   repository, either using:

   fossil ui --extpage test/link-tester.html

   or by adding test/link-tester.* to uv and then:

   fossil ui -page uv/link-tester.html
--></head>
<style>
  body {
      width: 100%;
      height: 100%;
      margin: 0;
      padding: 0;
      display: flex;
      flex-direction: column;
  }
  header {
      margin: 0.5em 0 0 0;
      padding: 0 1em 0 1em;
      z-index: 1;
  }
  #controlWrapper {
      display: flex;
      flex-direction: row;
      border-bottom: 2px dotted;
      padding-bottom: 0.5em;
  }
  #controlWrapper > button {
      flex-grow: 1;
      margin: 0.5em;
  }
  #selectWrapper {
      display: flex;
      flex-direction: column;
      flex-grow: 8;
  }
  #selectPage {
      flex-grow: 1;
      margin: 1em;
      padding: 1em;
  }
  #currentUrl {
      font-family: monospace;
      text-align: center;
  }
  #iframe {
      flex-grow: 1; border: none; margin: 0; padding: 0;
      display: block;
      /* Absolute positioning is apparently the only way to get
         the iframe to stretch to fill the page, but we have to
         set its Y coordinate to something a bit below #controls. */
      width: 100%;
      height: calc(100% - 5em);
      position: absolute;
      top: 4em;
  }
</style>
<body>
  <header>
    Fossil link test app. Select links from the list below to load
    them. Use the arrow keys to cycle through the list. The links are
    loaded within an iframe, so navigation within it will stay within
    that frame.
  </header>
  <header id='controlWrapper'>
    <button id='btn-prev'>&larr;</button>
    <div id='selectWrapper'>
      <select id='selectPage'>
        <option>/timeline</option>
        <option>/dir</option>
      </select>
      <a target='_blank' id='currentUrl'></a>
    </div>
    <button id='btn-next'>&rarr;</button>
  </header>
  <iframe id='iframe'><!--populated via the UI--></iframe>
  <script src='link-tester.js'></script>
<body>
Added test/link-tester.js.
































































































































































































































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
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/**
   JS code for link-tester.html. We cannot host this JS inline in that
   file because fossil's default Content Security Policy won't let it
   run that way.
*/
window.addEventListener("DOMContentLoaded", function(){
  const E = function(s){
    const e = document.querySelector(s);
    if( !e ) throw new Error("Missing element: "+s);
    return e;
  };
  const EAll = function(s){
    const e = document.querySelectorAll(s);
    if( !e || !e.length ) throw new Error("Missing elements: "+s);
    return e;
  };
  const eIframe = E('#iframe');
  const eSelect = E('#selectPage');
  const eCurrentUrl = E('#currentUrl');

  /*
    Prepend the fossil instance's URL to each link. We have to guess
    which part of the URL is the fossil CGI/server instance.  The
    following works when run (A) from under /uv or /ext and (B) from
    /doc/branchname/test/link-tester.html.
  */
  let urlTop;
  let loc = (''+window.location);
  let aLoc = loc.split('/')
  aLoc.pop(); /* file name */
  const thisDir = aLoc.join('/');
  const rxDoc = /.*\/doc\/[^/]+\/.*/;
  //console.log(rxDoc, loc, aLoc);
  if( loc.match(rxDoc) ){
    /* We're hopefully now at the top-most fossil-served
       URL. */
    aLoc.pop(); aLoc.pop(); /* /doc/foo */
    aLoc.pop(); /* current dir name */
  }else{
    aLoc.pop(); /* current dir name */
  }
  urlTop = aLoc.join('/');
  //console.log(urlTop, aLoc);
  for( const o of eSelect.options ){
    o.value = urlTop + (o.value || o.innerText);
  }

  const updateUrl = function(opt){
    if( opt ){
      let url = (opt.value || opt.innerText);
      eCurrentUrl.innerText = url.replace(urlTop,'');
      eCurrentUrl.setAttribute('href', url);
    }else{
      eCurrentUrl.innerText = '';
    }
  };

  eSelect.addEventListener('change',function(ev){
    const so = ev.target.options[ev.target.selectedIndex];
    if( so ){
      eIframe.setAttribute('src', so.value || so.innerText);
      updateUrl(so);
    }
  });

  /** Select the entry at the given ndx and fire a change event. */
  const selectEntry = function(ndx){
    if( ndx>=0 ){
      eSelect.selectedIndex = ndx;
      eSelect.dispatchEvent(new Event('change',{target:eSelect}));
    }
  };

  /* Cycle to the next link in the list, accounting for separators and
     wrapping around at either end. */
  const cycleLink = function(dir/*<0 = prev, >0 = next*/){
    let n = eSelect.selectedIndex + dir;
    if( n < 0 ) n = eSelect.options.length-1;
    else if( n>=eSelect.options.length ){
      n = 0;
    }
    const opt = eSelect.options[n];
    if( opt && opt.disabled ){
      /* If that OPTION element is disabled, skip over it. */
      eSelect.selectedIndex = n;
      cycleLink(dir);
    }else{
      selectEntry(n);
    }
  };

  E('#btn-prev').addEventListener('click', ()=>cycleLink(-1), false);
  E('#btn-next').addEventListener('click', ()=>cycleLink(1), false);

  /**
     We have to adjust the iframe's size dynamically to account for
     other widgets around it. iframes don't simply like to fill up all
     available space without some help. If #controlWrapper only
     contained the one SELECT element, CSS would be sufficient, but
     once we add text around it, #controlWrapper's size becomes
     unpredictable and we need JS to calculate it. We do this every
     time the window size changes.
  */
  const effectiveHeight = function f(e){
    // Copied from fossil.dom.js
    if(!e) return 0;
    if(!f.measure){
      f.measure = function callee(e, depth){
        if(!e) return;
        const m = e.getBoundingClientRect();
        if(0===depth){
          callee.top = m.top;
          callee.bottom = m.bottom;
        }else{
          callee.top = m.top ? Math.min(callee.top, m.top) : callee.top;
          callee.bottom = Math.max(callee.bottom, m.bottom);
        }
        Array.prototype.forEach.call(e.children,(e)=>callee(e,depth+1));
        if(0===depth){
          //console.debug("measure() height:",e.className, callee.top, callee.bottom, (callee.bottom - callee.top));
          f.extra += callee.bottom - callee.top;
        }
        return f.extra;
      };
    }
    f.extra = 0;
    f.measure(e,0);
    return f.extra;
  };

  /* Helper for the window-resized event handler below, to avoid
     handling the resize until after it's finished. */
  const debounce = function f(func, waitMs, immediate) {
    // Copied from fossil.bootstrap.js
    var timeoutId;
    if(!waitMs) waitMs = f.$defaultDelay;
    return function() {
      const context = this, args = Array.prototype.slice.call(arguments);
      const later = function() {
        timeoutId = undefined;
        if(!immediate) func.apply(context, args);
      };
      const callNow = immediate && !timeoutId;
      clearTimeout(timeoutId);
      timeoutId = setTimeout(later, waitMs);
      if(callNow) func.apply(context, args);
    };
  };

  /**
     Resize eConstrained (the ifame element) so that it fits within
     the page space not occupied by the list of elements eToAvoid.
  */
  const ForceResizeKludge = (function(eToAvoid, eConstrained){
    const resized = function f(){
      if( f.$disabled ) return;
      const wh = window.innerHeight;
      let ht;
      let extra = 0;
      eToAvoid.forEach((e)=>e ? extra += effectiveHeight(e) : false);
      ht = wh - extra;
      if( ht < 100 ) ht = 100;
      eConstrained.style.top = 'calc('+extra+'px + 2em)';
      eConstrained.style.height =
        eConstrained.style.maxHeight = "calc("+ ht+ "px - 2em)";
    };
    resized.$disabled = true/* gets deleted later */;
    window.addEventListener('resize', debounce(resized, 250), false);
    return resized;
  })(
    EAll('body > *:not(iframe)'),
    eIframe
  );

  delete ForceResizeKludge.$disabled;
  ForceResizeKludge();

  selectEntry(0);

  /**
     Read link-tester.json, which should live in the same directory
     as this file. It's expected to be an array with entries
     in one of the following forms:

     - "string"   = Separator label (disabled)
     - ["/path"]  = path with itself as a label
     - ["label", "/path"] = path with the given label

     All paths are expected to have a "/" prefix and this script
     accounts for mapping that to the fossil part of this script's
     URL.
  */
  window.fetch(thisDir+'/link-tester.json').then((r)=>r.json()).then(j=>{
    //console.log("fetched",j);
    eSelect.innerHTML = '';
    const opt = function(arg){
      const o = document.createElement('option');
      //console.warn(arguments);
      let rc = true;
      if( 'string' === typeof arg ){
        /* Grouping separator */
        o.innerText = "--- " + arg + " ---";
        o.setAttribute('disabled','');
        rc = false;
      }else if( 1===arg.length ){
        o.innerText = arg[0];
        o.value = urlTop + arg[0];
      }else if( 2==arg.length ){
        o.innerText = arg[0];
        o.value = urlTop + arg[1];
      }
      eSelect.appendChild(o);
      return rc;
    };
    let ndx = -1/*index of first non-disabled entry*/, i = 0;
    for(const e of j){
      if( opt(e) && ndx<0 ){
        ndx = i;
      }
      ++i;
    }
    selectEntry(ndx);
  });
});
Added test/link-tester.json.




































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[
  "Timelines",
  ["Default", "/timeline"],
  ["anonymous", "/timeline?u=anonymous&y=a"],
  ["after date/time", "/timeline?n=12&y=ci&a=2024-12-31T20:29Z"],
  ["after hash", "/timeline?n=12&y=ci&a=3cb092c0e2f0ff26"],
  ["before date/time", "/timeline?n=12&y=ci&b=2024-12-31T20:30Z"],
  ["before hash", "/timeline?n=12&y=ci&b=3cb092c0e2f0ff26"],
  ["circa date/time", "/timeline?n=12&y=ci&c=2024-12-31T20:29Z"],
  ["circa hash", "/timeline?n=12&y=ci&c=3cb092c0e2f0ff26"],
  ["d=,p=", "/timeline?d=version-2.25&p=version-2.26"],
  ["from=,ft=", "/timeline?from=2765f04694d36e68&ft=release"],
  ["from=,ft=,min", "/timeline?from=2765f04694d36e68&ft=release&min"],
  ["from=,to=", "/timeline?from=version-2.25&to=version-2.26"],
  ["from=,to=,min", "/timeline?from=version-2.25&to=version-2.26&min"],
  ["omit-cr branch", "/timeline?r=omit-cr&m&c=7e97f4999b16ab75"],
  ["diff-eolws branch", "/timeline?r=diff-eolws&n=50"],
  ["Shortest path (from=,to=)", 
      "/timeline?from=e663bac6f7&to=a298a0e2f9&shortest"],
  ["Common Ancestor (me=,you=)",
      "/timeline?me=e663bac6f7&you=a298a0e2f9"],

  "Diff",
  ["Multiple edits on a single line", "/info/030035345c#chunk73"],
  ["Tricky alignment, multiple edits per line",
      "/fdiff?v1=6da016415dc52d61&v2=af6df3466e3c4a88"],
  ["Column alignment with multibyte characters",
      "/fdiff?v1=d1c60722e0b9d775&v2=58d1a8991bacb113"],
  ["Large diff of sqlite3.c - was once very slow",
     "/fdiff?v1=57b0d8183cab0e3d&v2=37b3ef49d73cdfe6"],
  ["A difficult indentation change", "/info/bda00cbada#chunk49"],
  ["Inverse of the previous",
     "/fdiff?v1=bc8100c9ee01b8c2&v2=1d2acc1a2a65c2bf#chunk42"],
  ["Another tricky indentation",
     "/fdiff?v1=955cc67ace8fb622&v2=e2e1c87b86664b45#chunk13"],
  ["Inverse of the previous",
     "/fdiff?v2=955cc67ace8fb622&v1=e2e1c87b86664b45#chunk13"],
  ["A tricky alignment",
     "/fdiff?v1=955cc67ace8fb622&v2=e2e1c87b86664b45#chunk24"],
  ["sqlite3.c changes that are difficult to align",
     "/fdiff?v1=21f9a00fe2fa4a17&v2=d5c4ff0532bd89c3#chunk5"],
  ["Lorem Ipsum in Greek", "/fdiff?v1=4f70c682e44f&v2=55659c6e062994f"],
  ["Inverted Greek Lorem Ipsum", "/fdiff?v2=4f70c682e44f&v1=55659c6e062994f"],

  "Infos",
  ["Merge riser coalescing #1", "/info/eed3946bd92a499?diff=0"],
  ["Merge riser coalescing #2", "/info/ef6979eac9abded?diff=0"],
  ["Merge riser coalescing #3", "/info/9e1fa626e47f147?diff=0"],
  ["Merge riser coalescing #4", "/info/68bd2e7bedb8d05?diff=0"],
  ["Merge riser coalescing #5", "/info/7766e689926c703?diff=0"],
  ["Merge riser coalescing #6", "/info/3ea66260b5555d2?diff=0"],
  ["Merge riser coalescing #7", "/info/66ae70a54b20656?diff=0"],
  ["Context graph #1", "/info/b0f2a0ac53926c9?diff=0"],
  ["Context graph #2", "/info/303e7af7c31866c?diff=0"],
  ["Context graph #3", "/info/b31afcc2cab1dc4?diff=0"],
  ["Context graph #4", "/info/1a164e5fb76a46b?diff=0"],
  ["Context graph #5", "/info/2d75e87b760c0a9?diff=0"],
  ["Context graph #6", "/info/76442af7e13267bd?diff=0"],
  ["Info about the tip", "/info/tip"],
  ["/info/tip"],

  "Admin",
  ["Users", "/setup_ulist"],

  "Misc.",
  ["/skins"],
  ["/chat"]
]
Changes to test/many-www.tcl.
22
23
24
25
26
27
28
29

30
31
32
33
34
35
36
22
23
24
25
26
27
28

29
30
31
32
33
34
35
36







-
+







  /taglist
  /reportlist
  /setup
  /dir
  /wcontent
  /attachlist
  /taglist
  /test_env
  /test-env
  /stat
  /rcvfromlist
  /urllist
  /modreq
  /info/d5c4
  /test-all-help
  /leaves
Changes to test/merge1.test.
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
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







+
+













+
+







  333 - This is a test of the merging algohm - 3333
  444 - If all goes well, we will be pleased - 4444
  555 - we think it well and other stuff too - 5555
}
write_file_indented t23 {
  <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1)
  111 - This is line ONE of the demo program - 1111
  ####### SUGGESTED CONFLICT RESOLUTION follows ###################
  111 - This is line ONE OF the demo program - 1111
  ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1)
  111 - This is line one of the demo program - 1111
  ======= MERGED IN content follows =============================== (line 1)
  111 - This is line one OF the demo program - 1111
  >>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  222 - The second line program line in code - 2222
  333 - This is a test of the merging algohm - 3333
  444 - If all goes well, we will be pleased - 4444
  555 - we think it well and other stuff too - 5555
}
write_file_indented t32 {
  <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1)
  111 - This is line one OF the demo program - 1111
  ####### SUGGESTED CONFLICT RESOLUTION follows ###################
  111 - This is line ONE OF the demo program - 1111
  ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1)
  111 - This is line one of the demo program - 1111
  ======= MERGED IN content follows =============================== (line 1)
  111 - This is line ONE of the demo program - 1111
  >>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  222 - The second line program line in code - 2222
  333 - This is a test of the merging algohm - 3333
157
158
159
160
161
162
163



164
165
166
167
168
169
170
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177







+
+
+







  222 - The second line program line in code - 2222
  333 - This is a test of the merging algohm - 3333
  444 - If all goes well, we will be pleased - 4444
  555 - we think it well and other stuff too - 5555
}
write_file_indented t32 {
  <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1)
  ####### SUGGESTED CONFLICT RESOLUTION follows ###################
  000 - Zero lines added to the beginning of - 0000
  111 - This is line one of the demo program - 1111
  ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1)
  111 - This is line one of the demo program - 1111
  ======= MERGED IN content follows =============================== (line 1)
  000 - Zero lines added to the beginning of - 0000
  111 - This is line one of the demo program - 1111
  >>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  222 - The second line program line in code - 2222
303
304
305
306
307
308
309









310
311
312
313
314
315
316
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332







+
+
+
+
+
+
+
+
+







  efgh 2
  ijkl 2
  mnop 2
  qrst
  uvwx
  yzAB 2
  CDEF 2
  GHIJ 2
  ####### SUGGESTED CONFLICT RESOLUTION follows ###################
  efgh 2
  ijkl 2
  mnop 3
  qrst 3
  uvwx 3
  yzAB 3
  CDEF 2
  GHIJ 2
  ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 2)
  efgh
  ijkl
  mnop
  qrst
  uvwx
370
371
372
373
374
375
376









377
378
379
380
381
382
383
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408







+
+
+
+
+
+
+
+
+







  <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 2)
  efgh 2
  ijkl 2
  mnop 
  qrst
  uvwx
  yzAB 2
  CDEF 2
  GHIJ 2
  ####### SUGGESTED CONFLICT RESOLUTION follows ###################
  efgh 2
  ijkl 2
  mnop 3
  qrst 3
  uvwx 3
  yzAB 3
  CDEF 2
  GHIJ 2
  ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 2)
  efgh
  ijkl
  mnop
  qrst
Changes to test/merge3.test.
25
26
27
28
29
30
31



32
33
34
35
36
37
38
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41







+
+
+







  write_file t2 [join [string trim $v1] \n]\n
  write_file t3 [join [string trim $v2] \n]\n
  fossil 3-way-merge t1 t2 t3 t4 {*}$fossil_args
  set x [read_file t4]
  regsub -all \
    {<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <+ \(line \d+\)} \
    $x {MINE:} x
  regsub -all \
    {####### SUGGESTED CONFLICT RESOLUTION follows #+} \
    $x {BOT:} x
  regsub -all \
    {\|\|\|\|\|\|\| COMMON ANCESTOR content follows \|+ \(line \d+\)} \
    $x {COM:} x
  regsub -all \
    {======= MERGED IN content follows =+ \(line \d+\)} \
    $x {YOURS:} x
  regsub -all \
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
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







-
+








-
+








-
+








-
+








-
+








-
+







merge-test 3 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3b 4b 5b 6 7 8 9
} {
  1 2 3 4 5c 6 7 8 9
} {
  1 2 MINE: 3b 4b 5b COM: 3 4 5 YOURS: 3 4 5c END 6 7 8 9
  1 2 MINE: 3b 4b 5b BOT: 3b 4b 5c COM: 3 4 5 YOURS: 3 4 5c END 6 7 8 9
} -expectError
merge-test 4 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3b 4b 5b 6b 7 8 9
} {
  1 2 3 4 5c 6 7 8 9
} {
  1 2 MINE: 3b 4b 5b 6b COM: 3 4 5 6 YOURS: 3 4 5c 6 END 7 8 9
  1 2 MINE: 3b 4b 5b 6b BOT: 3b 4b 5b 5c 6 COM: 3 4 5 6 YOURS: 3 4 5c 6 END 7 8 9
} -expectError
merge-test 5 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3b 4b 5b 6b 7 8 9
} {
  1 2 3 4 5c 6c 7c 8 9
} {
  1 2 MINE: 3b 4b 5b 6b 7 COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8 9
  1 2 MINE: 3b 4b 5b 6b 7 BOT: 3b 4b 5b 5c 6c 7c COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8 9
} -expectError
merge-test 6 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3b 4b 5b 6b 7 8b 9
} {
  1 2 3 4 5c 6c 7c 8 9
} {
  1 2 MINE: 3b 4b 5b 6b 7 COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8b 9
  1 2 MINE: 3b 4b 5b 6b 7 BOT: 3b 4b 5b 5c 6c 7c COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8b 9
} -expectError
merge-test 7 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3b 4b 5b 6b 7 8b 9
} {
  1 2 3 4 5c 6c 7c 8c 9
} {
  1 2 MINE: 3b 4b 5b 6b 7 8b COM: 3 4 5 6 7 8 YOURS: 3 4 5c 6c 7c 8c END 9
  1 2 MINE: 3b 4b 5b 6b 7 8b BOT: 3b 4b 5b 5c 6c 7c 8c COM: 3 4 5 6 7 8 YOURS: 3 4 5c 6c 7c 8c END 9
} -expectError
merge-test 8 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3b 4b 5b 6b 7 8b 9b
} {
  1 2 3 4 5c 6c 7c 8c 9
} {
  1 2 MINE: 3b 4b 5b 6b 7 8b 9b COM: 3 4 5 6 7 8 9 YOURS: 3 4 5c 6c 7c 8c 9 END
  1 2 MINE: 3b 4b 5b 6b 7 8b 9b BOT: 3b 4b 5b 5c 6c 7c 8c 9b COM: 3 4 5 6 7 8 9 YOURS: 3 4 5c 6c 7c 8c 9 END
} -expectError
merge-test 9 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3b 4b 5 6 7 8b 9b
} {
  1 2 3 4 5c 6c 7c 8 9
144
145
146
147
148
149
150
151

152
153
154
155
156
157
158
147
148
149
150
151
152
153

154
155
156
157
158
159
160
161







-
+







merge-test 11 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3b 4b 5 6 7 8b 9b
} {
  1 2 3b 4c 5 6c 7c 8 9
} {
  1 2 MINE: 3b 4b COM: 3 4 YOURS: 3b 4c END 5 6c 7c 8b 9b
  1 2 MINE: 3b 4b BOT: 3b 4c COM: 3 4 YOURS: 3b 4c END 5 6c 7c 8b 9b
} -expectError
merge-test 12 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3b4b 5 6 7 8b 9b
} {
  1 2 3b4b 5 6c 7c 8 9
199
200
201
202
203
204
205
206

207
208
209
210
211
212
213
214
215

216
217
218
219
220
221
222
202
203
204
205
206
207
208

209
210
211
212
213
214
215
216
217

218
219
220
221
222
223
224
225







-
+








-
+







merge-test 24 {
  1 2 3 4 5 6 7 8 9
} {
  1 6 7 8 9
} {
  1 2 3 4 9
} {
  1 MINE: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9
  1 MINE: 6 7 8 BOT: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9
} -expectError
merge-test 25 {
  1 2 3 4 5 6 7 8 9
} {
  1 7 8 9
} {
  1 2 3 9
} {
  1 MINE: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9
  1 MINE: 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9
} -expectError

merge-test 30 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3 4 5 6 7 9
} {
254
255
256
257
258
259
260
261

262
263
264
265
266
267
268
269
270

271
272
273
274
275
276
277
257
258
259
260
261
262
263

264
265
266
267
268
269
270
271
272

273
274
275
276
277
278
279
280







-
+








-
+







merge-test 34 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3 4 9
} {
  1 6 7 8 9
} {
  1 MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END 9
  1 MINE: 2 3 4 BOT: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END 9
} -expectError
merge-test 35 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3 9
} {
  1 7 8 9
} {
  1 MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END 9
  1 MINE: 2 3 BOT: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END 9
} -expectError

merge-test 40 {
  2 3 4 5 6 7 8
} {
  3 4 5 6 7 8
} {
309
310
311
312
313
314
315
316

317
318
319
320
321
322
323
324
325

326
327
328
329
330
331
332
312
313
314
315
316
317
318

319
320
321
322
323
324
325
326
327

328
329
330
331
332
333
334
335







-
+








-
+







merge-test 44 {
  2 3 4 5 6 7 8
} {
  6 7 8
} {
  2 3 4
} {
  MINE: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END
  MINE: 6 7 8 BOT: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END
} -expectError
merge-test 45 {
  2 3 4 5 6 7 8
} {
  7 8
} {
  2 3
} {
  MINE: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END
  MINE: 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END
} -expectError

merge-test 50 {
  2 3 4 5 6 7 8
} {
  2 3 4 5 6 7
} {
363
364
365
366
367
368
369
370

371
372
373
374
375
376
377
378
379

380
381
382
383
384
385
386
366
367
368
369
370
371
372

373
374
375
376
377
378
379
380
381

382
383
384
385
386
387
388
389







-
+








-
+







merge-test 54 {
  2 3 4 5 6 7 8
} {
  2 3 4
} {
  6 7 8
} {
  MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END
  MINE: 2 3 4 BOT: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END
} -expectError
merge-test 55 {
  2 3 4 5 6 7 8
} {
  2 3
} {
  7 8
} {
  MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END
  MINE: 2 3 BOT: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END
} -expectError

merge-test 60 {
  1 2 3 4 5 6 7 8 9
} {
  1 2b 3 4 5 6 7 8 9
} {
418
419
420
421
422
423
424
425

426
427
428
429
430
431
432
433
434

435
436
437
438
439
440
441
421
422
423
424
425
426
427

428
429
430
431
432
433
434
435
436

437
438
439
440
441
442
443
444







-
+








-
+







merge-test 64 {
  1 2 3 4 5 6 7 8 9
} {
  1 2b 3b 4b 5b 6 7 8 9
} {
  1 2 3 4 9
} {
  1 MINE: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9
  1 MINE: 2b 3b 4b 5b 6 7 8 BOT: 2b 3b 4b 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9
} -expectError
merge-test 65 {
  1 2 3 4 5 6 7 8 9
} {
  1 2b 3b 4b 5b 6b 7 8 9
} {
  1 2 3 9
} {
  1 MINE: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9
  1 MINE: 2b 3b 4b 5b 6b 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9
} -expectError

merge-test 70 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3 4 5 6 7 9
} {
473
474
475
476
477
478
479
480

481
482
483
484
485
486
487
488
489

490
491
492
493
494
495
496
476
477
478
479
480
481
482

483
484
485
486
487
488
489
490
491

492
493
494
495
496
497
498
499







-
+








-
+







merge-test 74 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3 4 9
} {
  1 2b 3b 4b 5b 6 7 8 9
} {
  1 MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END 9
  1 MINE: 2 3 4 BOT: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END 9
} -expectError
merge-test 75 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3 9
} {
  1 2b 3b 4b 5b 6b 7 8 9
} {
  1 MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END 9
  1 MINE: 2 3 BOT: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END 9
} -expectError

merge-test 80 {
  2 3 4 5 6 7 8
} {
  2b 3 4 5 6 7 8
} {
528
529
530
531
532
533
534
535

536
537
538
539
540
541
542
543
544

545
546
547
548
549
550
551
531
532
533
534
535
536
537

538
539
540
541
542
543
544
545
546

547
548
549
550
551
552
553
554







-
+








-
+







merge-test 84 {
  2 3 4 5 6 7 8
} {
  2b 3b 4b 5b 6 7 8
} {
  2 3 4
} {
  MINE: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END
  MINE: 2b 3b 4b 5b 6 7 8 BOT: 2b 3b 4b 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END
} -expectError
merge-test 85 {
  2 3 4 5 6 7 8
} {
  2b 3b 4b 5b 6b 7 8
} {
  2 3
} {
  MINE: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END
  MINE: 2b 3b 4b 5b 6b 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END
} -expectError

merge-test 90 {
  2 3 4 5 6 7 8
} {
  2 3 4 5 6 7
} {
583
584
585
586
587
588
589
590

591
592
593
594
595
596
597
598
599

600
601
602
603
604
605
606
586
587
588
589
590
591
592

593
594
595
596
597
598
599
600
601

602
603
604
605
606
607
608
609







-
+








-
+







merge-test 94 {
  2 3 4 5 6 7 8
} {
  2 3 4
} {
  2b 3b 4b 5b 6 7 8
} {
  MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END
  MINE: 2 3 4 BOT: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END
} -expectError
merge-test 95 {
  2 3 4 5 6 7 8
} {
  2 3
} {
  2b 3b 4b 5b 6b 7 8
} {
  MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END
  MINE: 2 3 BOT: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END
} -expectError

merge-test 100 {
  1 2 3 4 5 6 7 8 9
} {
  1 2b 3 4 5 7 8 9 a b c d e
} {
629
630
631
632
633
634
635
636

637
638
639
640
641
642
643
644
645

646
647
648
649
650
632
633
634
635
636
637
638

639
640
641
642
643
644
645
646
647

648
649
650
651
652
653







-
+








-
+





merge-test 103 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3 4 5 7 8 9b
} {
  1 2 3 4 5 7 8 9b a b c d e
} {
  1 2 3 4 5 7 8 MINE: 9b COM: 9 YOURS: 9b a b c d e END
  1 2 3 4 5 7 8 MINE: 9b BOT: 9b a b c d e COM: 9 YOURS: 9b a b c d e END
} -expectError
merge-test 104 {
  1 2 3 4 5 6 7 8 9
} {
  1 2 3 4 5 7 8 9b a b c d e
} {
  1 2 3 4 5 7 8 9b
} {
  1 2 3 4 5 7 8 MINE: 9b a b c d e COM: 9 YOURS: 9b END
  1 2 3 4 5 7 8 MINE: 9b a b c d e BOT: 9b COM: 9 YOURS: 9b END
} -expectError

###############################################################################

test_cleanup
Changes to test/merge4.test.
24
25
26
27
28
29
30

31
32
33
34
35

36
37
38
39
40
41
42
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44







+





+







  write_file t1 [join [string trim $basis] \n]\n
  write_file t2 [join [string trim $v1] \n]\n
  write_file t3 [join [string trim $v2] \n]\n
  fossil 3-way-merge t1 t2 t3 t4 {*}$fossil_args
  fossil 3-way-merge t1 t3 t2 t5 {*}$fossil_args
  set x [read_file t4]
  regsub -all {<<<<<<< BEGIN MERGE CONFLICT.*<< \(line \d+\)} $x {>} x
  regsub -all {####### SUGGESTED CONFLICT RESOLUTION.*##} $x {#} x
  regsub -all {\|\|\|\|\|\|\|.*======= \(line \d+\)} $x {=} x
  regsub -all {>>>>>>> END MERGE CONFLICT.*>>>>} $x {<} x
  set x [split [string trim $x] \n]
  set y [read_file t5]
  regsub -all {<<<<<<< BEGIN MERGE CONFLICT.*<< \(line \d+\)} $y {>} y
  regsub -all {####### SUGGESTED CONFLICT RESOLUTION.*##} $y {#} y
  regsub -all {\|\|\|\|\|\|\|.*======= \(line \d+\)} $y {=} y
  regsub -all {>>>>>>> END MERGE CONFLICT.*>>>>} $y {<} y
  set y [split [string trim $y] \n]
  set result1 [string trim $result1]
  if {$x!=$result1} {
    protOut "  Expected \[$result1\]"
    protOut "       Got \[$x\]"
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
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







-
+

-
+



















-
+

-
+







merge-test 1000 {
  1 2 3 4 5 6 7 8 9
} {
  1 2b 3b 4b 5 6b 7b 8b 9
} {
  1 2 3 4c 5c 6c 7 8 9
} {
  1 > 2b 3b 4b 5 6b 7b 8b = 2 3 4c 5c 6c 7 8 < 9
  1 > 2b 3b 4b 5 6b 7b 8b # 2b 3b 4c 5c 6c 7b 8b = 2 3 4c 5c 6c 7 8 < 9
} {
  1 > 2 3 4c 5c 6c 7 8 = 2b 3b 4b 5 6b 7b 8b < 9
  1 > 2 3 4c 5c 6c 7 8 # 2b 3b 4b 5c 6b 7b 8b = 2b 3b 4b 5 6b 7b 8b < 9
} -expectError
merge-test 1001 {
  1 2 3 4 5 6 7 8 9
} {
  1 2b 3b 4 5 6 7b 8b 9
} {
  1 2 3 4c 5c 6c 7 8 9
} {
  1 2b 3b 4c 5c 6c 7b 8b 9
} {
  1 2b 3b 4c 5c 6c 7b 8b 9
}
merge-test 1002 {
  2 3 4 5 6 7 8
} {
  2b 3b 4b 5 6b 7b 8b
} {
  2 3 4c 5c 6c 7 8
} {
  > 2b 3b 4b 5 6b 7b 8b = 2 3 4c 5c 6c 7 8 <
  > 2b 3b 4b 5 6b 7b 8b # 2b 3b 4c 5c 6c 7b 8b = 2 3 4c 5c 6c 7 8 <
} {
  > 2 3 4c 5c 6c 7 8 = 2b 3b 4b 5 6b 7b 8b <
  > 2 3 4c 5c 6c 7 8 # 2b 3b 4b 5c 6b 7b 8b = 2b 3b 4b 5 6b 7b 8b <
} -expectError
merge-test 1003 {
  2 3 4 5 6 7 8
} {
  2b 3b 4 5 6 7b 8b
} {
  2 3 4c 5c 6c 7 8
Changes to test/set-manifest.test.
36
37
38
39
40
41
42
43

44
45
46
47
48
49
50
36
37
38
39
40
41
42

43
44
45
46
47
48
49
50







-
+







# On ActiveTcl, add it with teacup. On other platforms, YMMV.
# teacup install sha1
if {[catch {package require sha1}] != 0} {
  puts "The \"sha1\" package is not available."
  test_cleanup_then_return
}

# We need a respository, so let it have one.
# We need a repository, so let it have one.
test_setup

#### Verify classic behavior of the manifest setting

# Setting is off by default, and there are no extra files.
fossil settings manifest
test "set-manifest-1" {[regexp {^manifest *$} $RESULT]}
Deleted test/settings.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










































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#
# Copyright (c) 2016 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/
#
############################################################################
#
# The "settings" and "unset" commands.
#

set path [file dirname [info script]]; test_setup

###############################################################################
#
# Complete syntax as tested:
#
#     fossil settings ?PROPERTY? ?VALUE? ?OPTIONS?
#     fossil unset PROPERTY ?OPTIONS?
#
# Where the only supported options are "--global" and "--exact".
#
###############################################################################
#
# NOTE: The [get_all_settings] procedure from test/tester.tcl returns the list
#       of settings to test and needs to be manually updated when new settings
#       are added.
#
###############################################################################
#
# NOTE: The [extract_setting_names] procedure extracts the list of setting
#       names from the line-ending normalized output of the "fossil settings"
#       command.  It assumes that a setting name must begin with a lowercase
#       letter.  It also assumes that any output lines that start with a
#       lowercase letter contain a setting name starting at that same point.
#
proc extract_setting_names { data } {
  set names [list]

  foreach {dummy name} [regexp \
      -all -line -inline -- {^([a-z][a-z0-9\-]*) ?.*$} $data] {
    lappend names $name
  }

  return $names
}

###############################################################################

set all_settings [get_all_settings]

fossil settings
set local_settings [extract_setting_names [normalize_result_no_trim]]

fossil settings --global
set global_settings [extract_setting_names [normalize_result_no_trim]]

foreach name $all_settings {
  test settings-have-local-$name {
    [lsearch -exact $local_settings $name] != -1
  }

  test settings-have-global-$name {
    [lsearch -exact $global_settings $name] != -1
  }
}

foreach name $local_settings {
  test settings-valid-local-$name {
    [lsearch -exact $all_settings $name] != -1
  }
}

foreach name $global_settings {
  test settings-valid-global-$name {
    [lsearch -exact $all_settings $name] != -1
  }
}

###############################################################################

set pattern(1) {^%name%$}
set pattern(2) {^%name%[ ]+\((?:local|global)\)[ ]+[^ ]+$}

foreach name $all_settings {
  fossil settings $name --exact
  set data [normalize_result]

  test settings-query-local-$name {
    [regexp -- [string map [list %name% $name] $pattern(1)] $data] ||
    [regexp -- [string map [list %name% $name] $pattern(2)] $data]
  }

  if {$name eq "manifest"} {
    fossil settings $name --exact --global -expectError
  } else {
    fossil settings $name --exact --global
  }
  set data [normalize_result]

  if {$name eq "manifest"} {
    test settings-query-global-$name {
      $data eq "cannot set 'manifest' globally"
    }
  } else {
    test settings-query-global-$name {
      [regexp -- [string map [list %name% $name] $pattern(1)] $data] ||
      [regexp -- [string map [list %name% $name] $pattern(2)] $data]
    }
  }
}

###############################################################################

fossil settings bad-setting -expectError

test settings-query-bad-local {
  [normalize_result] eq "no such setting: bad-setting"
}

fossil settings bad-setting --global -expectError

test settings-query-bad-global {
  [normalize_result] eq "no such setting: bad-setting"
}

###############################################################################

test_cleanup
Added test/settings.test.off.










































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
#
# Copyright (c) 2016 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/
#
############################################################################
#
# The "settings" and "unset" commands.
#

set path [file dirname [info script]]; test_setup

###############################################################################
#
# Complete syntax as tested:
#
#     fossil settings ?PROPERTY? ?VALUE? ?OPTIONS?
#     fossil unset PROPERTY ?OPTIONS?
#
# Where the only supported options are "--global" and "--exact".
#
###############################################################################
#
# NOTE: The [get_all_settings] procedure from test/tester.tcl returns the list
#       of settings to test and needs to be manually updated when new settings
#       are added.
#
###############################################################################
#
# NOTE: The [extract_setting_names] procedure extracts the list of setting
#       names from the line-ending normalized output of the "fossil settings"
#       command.  It assumes that a setting name must begin with a lowercase
#       letter.  It also assumes that any output lines that start with a
#       lowercase letter contain a setting name starting at that same point.
#
proc extract_setting_names { data } {
  set names [list]

  foreach {dummy name} [regexp \
      -all -line -inline -- {^([a-z][a-z0-9\-]*) ?.*$} $data] {
    lappend names $name
  }

  return $names
}

###############################################################################

set all_settings [get_all_settings]

fossil settings
set local_settings [extract_setting_names [normalize_result_no_trim]]

fossil settings --global
set global_settings [extract_setting_names [normalize_result_no_trim]]

foreach name $all_settings {
  test settings-have-local-$name {
    [lsearch -exact $local_settings $name] != -1
  }

  test settings-have-global-$name {
    [lsearch -exact $global_settings $name] != -1
  }
}

foreach name $local_settings {
  test settings-valid-local-$name {
    [lsearch -exact $all_settings $name] != -1
  }
}

foreach name $global_settings {
  test settings-valid-global-$name {
    [lsearch -exact $all_settings $name] != -1
  }
}

###############################################################################

set pattern(1) {^%name%$}
set pattern(2) {^%name%[ ]+\((?:local|global)\)[ ]+[^ ]+$}

foreach name $all_settings {
  fossil settings $name --exact
  set data [normalize_result]

  test settings-query-local-$name {
    [regexp -- [string map [list %name% $name] $pattern(1)] $data] ||
    [regexp -- [string map [list %name% $name] $pattern(2)] $data]
  }

  if {$name eq "manifest"} {
    fossil settings $name --exact --global -expectError
  } else {
    fossil settings $name --exact --global
  }
  set data [normalize_result]

  if {$name eq "manifest"} {
    test settings-query-global-$name {
      $data eq "cannot set 'manifest' globally"
    }
  } else {
    test settings-query-global-$name {
      [regexp -- [string map [list %name% $name] $pattern(1)] $data] ||
      [regexp -- [string map [list %name% $name] $pattern(2)] $data]
    }
  }
}

###############################################################################

fossil settings bad-setting -expectError

test settings-query-bad-local {
  [normalize_result] eq "no such setting: bad-setting"
}

fossil settings bad-setting --global -expectError

test settings-query-bad-global {
  [normalize_result] eq "no such setting: bad-setting"
}

###############################################################################

test_cleanup
Changes to test/tester.tcl.
26
27
28
29
30
31
32
33

34
35
36
37
38
39
40
26
27
28
29
30
31
32

33
34
35
36
37
38
39
40







-
+







# scripts), append the script base names as arguments:
#
#     tclsh ../test/tester.tcl ../bld/fossil <script-basename>...
#

# We use some things introduced in 8.6 such as lmap.  auto.def should
# have found us a suitable Tcl installation.
package require Tcl 8.6
package require Tcl 8.6-

set testfiledir [file normalize [file dirname [info script]]]
set testrundir [pwd]
set testdir [file normalize [file dirname $argv0]]
set fossilexe [file normalize [lindex $argv 0]]
set is_windows [expr {$::tcl_platform(platform) eq "windows"}]
set is_cygwin [regexp {^CYGWIN} $::tcl_platform(os)]
330
331
332
333
334
335
336

337
338
339
340
341
342
343
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344







+







      email-url \
      empty-dirs \
      encoding-glob \
      exec-rel-paths \
      fileedit-glob \
      forbid-delta-manifests \
      forum-close-policy \
      forum-title \
      gdiff-command \
      gmerge-command \
      hash-digits \
      hooks \
      http-port \
      https-login \
      ignore-glob \
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
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







+

















+
+
+
+
+

+

+







      max-upload \
      mimetypes \
      mtime-changes \
      mv-rm-files \
      pgp-command \
      preferred-diff-type \
      proxy \
      raw-bgcolor \
      redirect-to-https \
      relative-paths \
      repo-cksum \
      repolist-skin \
      robot-restrict \
      robots-txt \
      safe-html \
      self-pw-reset \
      self-register \
      sitemap-extra \
      ssh-command \
      ssl-ca-location \
      ssl-identity \
      tclsh \
      th1-setup \
      th1-uri-regexp \
      ticket-default-report \
      timeline-hard-newlines \
      timeline-plaintext \
      timeline-truncate-at-blank \
      timeline-tslink-info \
      timeline-utc \
      user-color-map \
      verify-comments \
      uv-sync \
      vuln-report \
      web-browser]

  fossil test-th-eval "hasfeature legacyMvRm"

  if {[normalize_result] eq "1"} {
    lappend result mv-rm-files
  }
Added test/th1-taint.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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
#
# Copyright (c) 2025 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/
#
############################################################################
#
# TH1 Commands
#

set path [file dirname [info script]]; test_setup

proc taint-test {testnum th1script expected} {
  global fossilexe
  set rc [catch {exec $fossilexe test-th-eval $th1script} got]
  if {$rc} {
    test th1-taint-$testnum 0
    puts $got
    return
  }
  if {$got ne $expected} {
    test th1-taint-$testnum 0
    puts " Expected: $expected"
    puts " Got:      $got"
  } else {
    test th1-taint-$testnum 1
  }
}

taint-test 10 {string is tainted abcd} 0
taint-test 20 {string is tainted [taint abcd]} 1
taint-test 30 {string is tainted [untaint [taint abcd]]} 0
taint-test 40 {string is tainted [untaint abcde]} 0
taint-test 50 {string is tainted "abc[taint def]ghi"} 1
taint-test 60 {set t1 [taint abc]; string is tainted "123 $t1 456"} 1

taint-test 100 {set t1 [taint abc]; lappend t1 def; string is tainted $t1} 1
taint-test 110 {set t1 abc; lappend t1 [taint def]; string is tainted $t1} 1

taint-test 200 {string is tainted [list abc def ghi]} 0
taint-test 210 {string is tainted [list [taint abc] def ghi]} 1
taint-test 220 {string is tainted [list abc [taint def] ghi]} 1
taint-test 230 {string is tainted [list abc def [taint ghi]]} 1

taint-test 300 {
  set res {}
  foreach x [list abc [taint def] ghi] {
    lappend res [string is tainted $x]
  }
  set res
} {1 1 1}
taint-test 310 {
  set res {}
  foreach {x y} [list abc [taint def] ghi jkl] {
    lappend res [string is tainted $x] [string is tainted $y]
  }
  set res
} {1 1 1 1}

taint-test 400 {string is tainted [lindex "abc [taint def] ghi" 0]} 1
taint-test 410 {string is tainted [lindex "abc [taint def] ghi" 1]} 1
taint-test 420 {string is tainted [lindex "abc [taint def] ghi" 2]} 1
taint-test 430 {string is tainted [lindex "abc [taint def] ghi" 3]} 0

taint-test 500 {string is tainted [string index [taint abcdefg] 3]} 1
  
taint-test 600 {string is tainted [string range [taint abcdefg] 3 5]} 1

taint-test 700 {string is tainted [string trim [taint "  abcdefg  "]]} 1
taint-test 710 {string is tainted [string trimright [taint "  abcdefg  "]]} 1
taint-test 720 {string is tainted [string trimleft [taint "  abcdefg  "]]} 1
  

test_cleanup
Changes to test/th1.test.
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
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







-
-
+
+









-
-
+
+







test th1-defHeader-2 {[string match *<body> [normalize_result]] || \
    [string match "*<body class=\"\$current_feature\
                    rpage-\$requested_page\
                    cpage-\$canonical_page\">" [normalize_result]]}

###############################################################################

fossil test-th-eval "styleHeader {Page Title Here}"
test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}
#fossil test-th-eval "styleHeader {Page Title Here}"
#test th1-header-1 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

test_in_checkout th1-header-2 {
  fossil test-th-eval --open-config "styleHeader {Page Title Here}"
} {[regexp -- {<title>Fossil: Page Title Here</title>} $RESULT]}

###############################################################################

fossil test-th-eval "styleFooter"
test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}
#fossil test-th-eval "styleFooter"
#test th1-footer-1 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

fossil test-th-eval --open-config "styleFooter"
test th1-footer-2 {$RESULT eq {}}

###############################################################################
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
920
921
922
923
924
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
920
921
922
923
924







-
-
+
+









-
-
+
+








-
-
+
+









-
-
+
+








fossil test-th-eval "artifact"
test th1-artifact-1 {$RESULT eq \
    {TH_ERROR: wrong # args: should be "artifact ID ?FILENAME?"}}

###############################################################################

fossil test-th-eval "artifact tip"
test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}
#fossil test-th-eval "artifact tip"
#test th1-artifact-2 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

test_in_checkout th1-artifact-3 {
  fossil test-th-eval --open-config "artifact tip"
} {[regexp -- {F test/th1\.test [0-9a-f]{40,64}} $RESULT]}

###############################################################################

fossil test-th-eval "artifact 0000000000"
test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}
#fossil test-th-eval "artifact 0000000000"
#test th1-artifact-4 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

fossil test-th-eval --open-config "artifact 0000000000"
test th1-artifact-5 {$RESULT eq {TH_ERROR: name not found}}

###############################################################################

fossil test-th-eval "artifact tip test/th1.test"
test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}
#fossil test-th-eval "artifact tip test/th1.test"
#test th1-artifact-6 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

test_in_checkout th1-artifact-7 {
  fossil test-th-eval --open-config "artifact tip test/th1.test"
} {[regexp -- {th1-artifact-7} $RESULT]}

###############################################################################

fossil test-th-eval "artifact 0000000000 test/th1.test"
test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}
#fossil test-th-eval "artifact 0000000000 test/th1.test"
#test th1-artifact-8 {$RESULT eq {TH_ERROR: repository unavailable}}

###############################################################################

fossil test-th-eval --open-config "artifact 0000000000 test/th1.test"
test th1-artifact-9 {$RESULT eq {TH_ERROR: manifest not found}}

###############################################################################
945
946
947
948
949
950
951
952
953


954
955
956
957
958
959
960
945
946
947
948
949
950
951


952
953
954
955
956
957
958
959
960







-
-
+
+







    test th1-globalState-2 {$RESULT eq \
        [fossil test-th-eval --open-config checkout]}
  }
}

###############################################################################

fossil test-th-eval "globalState configuration"
test th1-globalState-3 {[string length $RESULT] == 0}
#fossil test-th-eval "globalState configuration"
#test th1-globalState-3 {[string length $RESULT] == 0}

###############################################################################

fossil test-th-eval --open-config "globalState configuration"
test th1-globalState-4 {[string length $RESULT] > 0}

###############################################################################
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
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







-
-
+
+













-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+







###############################################################################

fossil test-th-eval "globalState flags"
test th1-globalState-16 {$RESULT eq "0"}

###############################################################################

fossil test-th-eval "reinitialize; globalState configuration"
test th1-reinitialize-1 {$RESULT eq ""}
#fossil test-th-eval "reinitialize; globalState configuration"
#test th1-reinitialize-1 {$RESULT eq ""}

###############################################################################

fossil test-th-eval "reinitialize 1; globalState configuration"
test th1-reinitialize-2 {$RESULT ne ""}

###############################################################################

#
# NOTE: This test will fail if the command names are added to TH1, or
#       moved from Tcl builds to plain or the reverse. Sorting the
#       command lists eliminates a dependence on order.
#
fossil test-th-eval "info commands"
set sorted_result [lsort $RESULT]
protOut "Sorted: $sorted_result"
set base_commands {anoncap anycap array artifact break breakpoint \
      builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
      combobox continue copybtn date decorate defHeader dir enable_htmlify \
      enable_output encode64 error expr for foreach getParameter glob_match \
      globalState hascap hasfeature html htmlize http httpize if info \
      insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
      proc puts query randhex redirect regexp reinitialize rename render \
      repository return searchable set setParameter setting stime string \
      styleFooter styleHeader styleScript submenu tclReady trace unset \
      unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
if {$th1Tcl} {
  test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
} else {
  test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
#fossil test-th-eval "info commands"
#set sorted_result [lsort $RESULT]
#protOut "Sorted: $sorted_result"
#set base_commands {anoncap anycap array artifact break breakpoint \
#     builtin_request_js capexpr captureTh1 catch cgiHeaderLine checkout \
#      combobox continue copybtn date decorate defHeader dir \
#      enable_output encode64 error expr for foreach getParameter glob_match \
#      globalState hascap hasfeature html htmlize http httpize if info \
#      insertCsrf lappend lindex linecount list llength lsearch markdown nonce \
#      proc puts query randhex redirect regexp reinitialize rename render \
#      repository return searchable set setParameter setting stime string \
#      styleFooter styleHeader styleScript submenu tclReady trace unset \
#      unversioned uplevel upvar utime verifyCsrf verifyLogin wiki}
#set tcl_commands {tclEval tclExpr tclInvoke tclIsSafe tclMakeSafe}
#if {$th1Tcl} {
#  test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands $tcl_commands"]}
#} else {
#  test th1-info-commands-1 {$sorted_result eq [lsort "$base_commands"]}
}
#}

###############################################################################

fossil test-th-eval "info vars"

if {$th1Hooks} {
  test th1-info-vars-1 {[lsort $RESULT] eq \
1324
1325
1326
1327
1328
1329
1330
1331

1332
1333
1334
1335
1336
1337
1338
1324
1325
1326
1327
1328
1329
1330

1331
1332
1333
1334
1335
1336
1337
1338







-
+







test th1-string-is-3 {$RESULT eq \
{TH_ERROR: wrong # args: should be "string is class string"}}

###############################################################################

fossil test-th-eval {string is other 123}
test th1-string-is-4 {$RESULT eq \
"TH_ERROR: Expected alnum, double, integer, or list, got: other"}
"TH_ERROR: Expected alnum, double, integer, list, or tainted, got: other"}

###############################################################################

fossil test-th-eval {string is alnum 123}
test th1-string-is-5 {$RESULT eq "1"}

###############################################################################
Changes to test/update.test.
55
56
57
58
59
60
61
62

63
64
65
66
67
68
69
55
56
57
58
59
60
61

62
63
64
65
66
67
68
69







-
+







# Make sure we are not in an open repository and initialize new repository
test_setup

###############################################################################

fossil update --verbose
test update-already-up-to-date {
  [regexp {^-{79}\ncheckout: .*\nchanges: +None. Already up-to-date$} $RESULT]
  [regexp {^-{79}\ncheckout: .*\nchanges: +None. Already up-to-date.$} $RESULT]
}

# Remaining tests are carried out in the order update_cmd() performs checks.
#
# Common approach for tests below:
# 1. Set the testname
# 2. Set the file name, done by calling update_setup
Changes to tools/codecheck1.c.
441
442
443
444
445
446
447
448

449
450
451
452
453
454
455
441
442
443
444
445
446
447

448
449
450
451
452
453
454
455







-
+







static int fmtfunc_cmp(const void *pAA, const void *pBB){
  const struct FmtFunc *pA = (const struct FmtFunc*)pAA;
  const struct FmtFunc *pB = (const struct FmtFunc*)pBB;
  return strcmp(pA->zFName, pB->zFName);
}

/*
** Determine if the indentifier zIdent of length nIndent is a Fossil
** Determine if the identifier zIdent of length nIndent is a Fossil
** internal interface that uses a printf-style argument.  Return zero if not.
** Return the index of the format string if true with the left-most
** argument having an index of 1.
*/
static int isFormatFunc(const char *zIdent, int nIdent, unsigned *pFlags){
  int upr, lwr;
  lwr = 0;
Changes to tools/email-sender.tcl.
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
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







-
+












+
+
+
+
+
+
+

+

+

-
-
+
+

+
+
+
+
+
+
-
-
+
+






+


-
+
+
+


+
+
+
+
+
-
+
+

#!/usr/bin/tcl
#
# Monitor the database file named by the DBFILE variable
# looking for email messages sent by Fossil.  Forward each
# to /usr/sbin/sendmail.
#
set POLLING_INTERVAL 10000   ;# milliseconds
set DBFILE /home/www/fossil/emailqueue.db
set DBFILE /home/www/data/emailqueue.db
set PIPE "/usr/sbin/sendmail -ti"

package require sqlite3
# puts "SQLite version [sqlite3 -version]"
sqlite3 db $DBFILE
db timeout 5000
catch {db eval {PRAGMA journal_mode=WAL}}
db eval {
  CREATE TABLE IF NOT EXISTS email(
    emailid INTEGER PRIMARY KEY,
    msg TXT
  );
  CREATE TABLE IF NOT EXISTS sentlog(
    mtime INT,
    xto TEXT,
    xfrom TEXT,
    xsubject TEXT,
    xsize INT
  );
}
set ctr 0
while {1} {
  set n 0
  db transaction immediate {
    set n 0
    db eval {SELECT msg FROM email} {
    set emailid 0
    db eval {SELECT emailid, msg FROM email LIMIT 1} {
      set pipe $PIPE
      set to unk
      set subject none
      set size [string length $msg]
      regexp {To:[^\n]*<([^>]+)>} $msg all to
      regexp {\nSubject:[ ]*([^\r\n]+)} $msg all subject
      set subject [string trim $subject]
      if {[regexp {\nFrom:[^\n]*<([^>]+)>} $msg all addr]} {
        append pipe " -f $addr"
      if {[regexp {\nFrom:[^\n]*<([^>]+)>} $msg all from]} {
        append pipe " -f $from"
      }
      set out [open |$pipe w]
      puts -nonewline $out $msg
      flush $out
      close $out
      incr n
      incr ctr
    }
    if {$n>0} {
      db eval {DELETE FROM email}
      db eval {DELETE FROM email WHERE emailid=$emailid}
      db eval {INSERT INTO sentlog(mtime,xto,xfrom,xsubject,xsize)
               VALUES(unixepoch(),$to,$from,$subject,$size)}
    }
  }
  if {$n==0} {
    if {$ctr>100} {
      db eval {DELETE FROM sentlog WHERE mtime<unixepoch('now','-30 days')}
      set ctr 0
    }
  after $POLLING_INTERVAL
    after $POLLING_INTERVAL
  }
}
Changes to tools/emcc.sh.in.
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


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
-
+
+










-
+


-
+
+

+
-
+
+



-
-
-
+
+
+






-
+

-
+

-
+




-
-
+
+





+
+
+
-
+
-

-
+



-
+

-
+




-
+
#!/usr/bin/bash
#!/bin/sh
# ^^^^^^^ Please try to keep this script Bourne-compatible.
########################################################################
# WARNING: emcc.sh is generated from emcc.sh.in by the configure
# process. Do not edit emcc.sh directly, as it may be deleted or
# overwritten by the configure script.
#
# A wrapper around the emcc compiler which uses configure-time state
# to locate the Emscripten SDK and import the SDK's environment
# script, if needed.
########################################################################
# EMSDK_HOME comes from the configure --with-emsdk=/dir flag.
# EMSDK_ENV is ${thatDir}/emsdk_env.sh and is also set by the
# EMSDK_ENV_SH is ${thatDir}/emsdk_env.sh and is also set by the
# configure process.
EMSDK_HOME="@EMSDK_HOME@"
EMSDK_ENV="@EMSDK_ENV@"
EMSDK_ENV_SH="@EMSDK_ENV_SH@"
emcc="@BIN_EMCC@"

if [ x = "x${emcc}" ]; then
emcc=$(which emcc 2>/dev/null)
    emcc=`which emcc 2>/dev/null`
fi

if [ x = "x${emcc}" ]; then
  # If emcc is not found in the path, try to find it via an emsdk
  # installation. The SDK variant is the official installation
  # style supported by the Emscripten folks, but emcc is also
  # available via package managers on some OSes.
  # installation. The SDK variant is the official installation style
  # supported by the Emscripten project, but emcc is also available
  # via package managers on some OSes.
  if [ x = "x${EMSDK_HOME}" ]; then
    echo "EMSDK_HOME is not set. Pass --with-emsdk=/path/to/emsdk" \
         "to the configure script." 1>&2
    exit 1
  fi

  if [ x = "x${EMSDK_ENV}" ]; then
  if [ x = "x${EMSDK_ENV_SH}" ]; then
    if [ -f "${EMSDK_HOME}/emsdk_env.sh" ]; then
      EMSDK_ENV="${EMSDK_HOME}/emsdk_env.sh"
      EMSDK_ENV_SH="${EMSDK_HOME}/emsdk_env.sh"
    else
      echo "EMSDK_ENV is not set. Expecting configure script to set it." 1>&2
      echo "EMSDK_ENV_SH is not set. Expecting configure script to set it." 1>&2
      exit 2
    fi
  fi

  if [ ! -f "${EMSDK_ENV}" ]; then
    echo "emsdk_env script not found: $EMSDK_ENV" 1>&2
  if [ ! -f "${EMSDK_ENV_SH}" ]; then
    echo "emsdk_env script not found: $EMSDK_ENV_SH" 1>&2
    exit 3
  fi

  # $EMSDK is part of the state set by emsdk_env.sh.
  if [ x = "x${EMSDK}" ]; then
    EMSDK_QUIET=1
    export EMSDK_QUIET
    # ^^^ Squelches informational output from ${EMSDK_ENV_SH}.
    source "${EMSDK_ENV}" >/dev/null 2>&1 || {
    source "${EMSDK_ENV_SH}" || {
      # ^^^ unfortunately outputs lots of noise to stderr
      rc=$?
      echo "Error sourcing ${EMSDK_ENV}"
      echo "Error sourcing ${EMSDK_ENV_SH}"
      exit $rc
    }
  fi
  emcc=$(which emcc 2>/dev/null)
  emcc=`which emcc 2>/dev/null`
  if [ x = "x${emcc}" ]; then
    echo "emcc not found in PATH. Normally that's set up by EMSDK_ENV." 1>&2
    echo "emcc not found in PATH. Normally that's set up by ${EMSDK_ENV_SH}." 1>&2
    exit 4
  fi
fi

exec emcc "$@"
exec $emcc "$@"
Added tools/fake-smtpd.tcl.




















































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
#!/usr/bin/tclsh
#
# This script is a testing aid for working on the Relay notification method
# in Fossil.
#
# This script listens for connections on port 25 or probably some other TCP
# port specified by the "--port N" option.  It pretend to be an SMTP server,
# though it does not actually relay any email.  Instead, it just prints the
# SMTP conversation on stdout.
#
# If the "--max N" option is used, then the fake SMTP server shuts down
# with an error after receiving N messages from the client.  This can be
# used to test retry capabilities in the client.
#
# Suggested Test Procedure For Fossil Relay Notifications
#
#    1.  Bring up "fossil ui"
#    2.  Configure notification for relay to localhost:8025
#    3.  Start this script in a separate window.  Something like:
#             tclsh fake-smtpd.tcl -port 8025 -max 100
#    4.  Send test messages using Fossil
#
proc conn_puts {chan txt} {
  puts "S: $txt"
  puts $chan $txt
  flush $chan
}
set mxMsg 100000000
proc connection {chan ip port} {
  global mxMsg
  set nMsg 0
  puts "*** begin connection from $ip:$port ***"
  conn_puts $chan "220 localhost fake-SMTPD"
  set inData 0
  while {1} {
    set line [string trimright [gets $chan]]
    if {$line eq ""} {
      if {[eof $chan]} break
    }
    puts "C: $line"
    incr nMsg
    if {$inData} {
      if {$line eq "."} {
        set inData 0
        conn_puts $chan "250 Ok"
      }
    } elseif {$nMsg>$mxMsg} {
      conn_puts $chan "999 I'm done!"
      break
    } elseif {[string match "HELO *" $line]} {
      conn_puts $chan "250 Ok"
    } elseif {[string match "EHLO *" $line]} {
      conn_puts $chan "250-SIZE 100000"
      conn_puts $chan "250 HELP"
    } elseif {[string match "DATA*" $line]} {
      conn_puts $chan "354 End data with <CR><LF>.<CR><LF>"
      set inData 1
    } elseif {[string match "QUIT*" $line]} {
      conn_puts $chan "221 Bye"
      break
    } else {
      conn_puts $chan "250 Ok"
    }
  }
  puts "*** connection closed ($nMsg messages) ***"
  close $chan
}
set port 25
set argc [llength $argv]
for {set i 0} {$i<$argc-1} {incr i} {
   set arg [lindex $argv $i]
   if {$arg eq "-port" || $arg eq "--port"} {
     incr i
     set port [lindex $argv $i]
   }
   if {$arg eq "-max" || $arg eq "--max"} {
     incr i
     set mxMsg [lindex $argv $i]
   }
}
puts "listening on localhost:$port"
socket -server connection $port
set forever 0
vwait forever
Added tools/find-fossil-cgis.tcl.













































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
#!/usr/bin/tclsh
#
# This script scans a directory hierarchy looking for Fossil CGI files -
# the files that are used to launch Fossil as a CGI program.  For each
# such file found, in prints the name of the file and also the file
# content, indented, if the --print option is used.
#
#     tclsh find-fossil-cgis.tcl [OPTIONS] DIRECTORY
#
# The argument is the directory from which to begin the search.
#
# OPTIONS can be zero or more of the following:
#
#    --has REGEXP               Only show the CGI if the body matches REGEXP.
#                               May be repeated multiple times, in which case
#                               all must match.
#
#    --hasnot REGEXP            Only show the CGI if it does NOT match the
#                               REGEXP.
#
#    --print                    Show the content of the CGI, indented by
#                               three spaces
#
#    --symlink                  Process DIRECTORY arguments that are symlinks.
#                               Normally symlinks are silently ignored.
#
#    -v                         Show progress information for debugging
#
# EXAMPLE USE CASES:
#
# Find all CGIs that do not have the "errorlog:" property set
#
#    find-fossil-cgis.tcl *.website --has '\nrepository:' \
#           --hasnot '\nerrorlog:'
#
# Add the errorlog: property to any CGI that does not have it:
#
#    find-fossil-cgis.tcl *.website --has '\nrepository:' \
#           --hasnot '\nerrorlog:' | while read x
#    do
#      echo 'errorlog: /logs/errors.txt' >>$x
#    done
#
# Find and print all CGIs that do redirects
#
#    find-fossil-cgis.tcl *.website --has '\nredirect:' --print
#


# Find the CGIs in directory $dir.  Invoke recursively to
# scan subdirectories.
#
proc find_in_one_dir {dir} {
  global HAS HASNOT PRINT V
  if {$V>0} {
    puts "# $dir"
  }
  foreach obj [lsort [glob -nocomplain -directory $dir *]] {
    if {[file isdir $obj]} {
      find_in_one_dir $obj
      continue
    }
    if {![file isfile $obj]} continue
    if {[file size $obj]>5000} continue
    if {![file exec $obj]} continue
    if {![file readable $obj]} continue
    set fd [open $obj rb]
    set txt [read $fd]
    close $fd
    if {![string match #!* $txt]} continue
    if {![regexp {fossil} $txt]} continue
    if {![regexp {\nrepository: } $txt] &&
        ![regexp {\ndirectory: } $txt] &&
        ![regexp {\nredirect: } $txt]} continue
    set ok 1
    foreach re $HAS {
      if {![regexp $re $txt]} {set ok 0; break;}
    }
    if {!$ok} continue
    foreach re $HASNOT {
      if {[regexp $re $txt]} {set ok 0; break;}
    }
    if {!$ok} continue
    # 
    # At this point assume we have found a CGI file.
    #
    puts $obj
    if {$PRINT} {
      regsub -all {\n} [string trim $txt] "\n   " out
      puts "   $out"
    }
  }
}
set HAS [list]
set HASNOT [list]
set PRINT 0
set SYMLINK 0
set V 0
set N [llength $argv]
set DIRLIST [list]

# First pass:  Gather all the command-line arguments but do no
# processing.
#
for {set i 0} {$i<$N} {incr i} {
  set dir [lindex $argv $i]
  if {($dir eq "-has" || $dir eq "--has") && $i<[expr {$N-1}]} {
    incr i
    lappend HAS [lindex $argv $i]
    continue
  }
  if {($dir eq "-hasnot" || $dir eq "--hasnot") && $i<[expr {$N-1}]} {
    incr i
    lappend HASNOT [lindex $argv $i]
    continue
  }
  if {$dir eq "-print" || $dir eq "--print"} {
    set PRINT 1
    continue
  }
  if {$dir eq "-symlink" || $dir eq "--symlink"} {
    set SYMLINK 1
    continue
  }
  if {$dir eq "-v"} {
    set V 1
    continue
  }
  if {[file type $dir]=="directory"} {
    lappend DIRLIST $dir
  }
}

# Second pass: Process the non-option arguments.
#
foreach dir $DIRLIST {
  set type [file type $dir]
  if {$type eq "directory" || ($SYMLINK && $type eq "link")} {
    find_in_one_dir $dir
  }
}
Changes to tools/fossil-autocomplete.zsh.
158
159
160
161
162
163
164

165

166
167
168
169
170
171
172
158
159
160
161
162
163
164
165

166
167
168
169
170
171
172
173







+
-
+







  # Scaffolding code for common options can be generated with `__fossil_format_options -o`.
  _common_options=(
    "(--help --args)"--args'[FILENAME Read additional arguments and options from FILENAME]:file:_files'
    "(--help --cgitrace)"--cgitrace'[Active CGI tracing]'
    "(--help --comfmtflags --comment-format)"--comfmtflags'[VALUE Set comment formatting flags to VALUE]:value:'
    "(--help --comment-format --comfmtflags)"--comment-format'[VALUE Alias for --comfmtflags]:value:'
    "(--help --errorlog)"--errorlog'[FILENAME Log errors to FILENAME]:file:_files'
    "(- -? --help)"{-?,--help}
    "(- --help)"--help'[Show help on the command rather than running it]'
    '[Show help on the command rather than running it]'
    "(--help --httptrace)"--httptrace'[Trace outbound HTTP requests]'
    "(--help --localtime)"--localtime'[Display times using the local timezone]'
    "(--help --no-th-hook)"--no-th-hook'[Do not run TH1 hooks]'
    "(--help --quiet)"--quiet'[Reduce the amount of output]'
    "(--help --sqlstats)"--sqlstats'[Show SQL usage statistics when done]'
    "(--help --sqltrace)"--sqltrace'[Trace all SQL commands]'
    "(--help --sshtrace)"--sshtrace'[Trace SSH activity]'
Added tools/fossil-makeinfo.









































































































































































































































































































































































































































































































































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
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
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
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
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
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
#!/usr/bin/env bash
# v0.1 Quick and dirty script to grab fossil's help and stuff it into a file.
# v0.2 Now I have working sed expressions, I can grab the help keywords.
# v0.3 This gets me the keywords and only the keywords.
# v0.4 Glue all relevant things together and produce an info file.
# v0.5 Working out sed/head/tail differences between GNU and BSD.
# v0.6 Replaced "head -n -1" with "sed '$d'" which does the same thing.
# v0.7 Adding help in separately for normal/auxil commands.
# v0.8 Added common options and Features. Got rid of some obvious bugs.
# v0.9 Added @bye at end.
# v0.10 Retitled Features to Fossil.
# v0.11 Added entries relevant to system-wide dir entry.
# v0.12 Reworded section introductions.
# v0.13 Added testing commands to texi output.
# v0.14 Added web commands to texi output. Note added far later.
# v0.15 Added Fossil settings.
#        Builds a correct texinfo file (finally) on OpenBSD and NetBSD.
#
# Requires fossil, tail, GNU sed and makeinfo
#

##### Header #####
# put header, then common node, finish menu, then uncommon node then finish menu
echo "Create Header"
printf "\\input texinfo
@settitle Fossil
@setfilename fossil.info
@c @author brickviking
@dircategory Development
@direntry
* Fossil: (fossil).        A distributed version control system.
@end direntry

" > fossil.texi
printf "@c Initial rendition to convert fossil help -a -v into texinfo for further
@c massaging by makeinfo. Scripts to do this automatically may come
@c later. Don't expect this to conform to GNU guidelines.

" >> fossil.texi

# Add a title page
printf "@titlepage
@title Fossil
@subtitle The Fossil Source Code Manager (fossil-scm)
@subtitle A distributed version control system
@author The fossil committers

@page
@vskip 0pt plus 1filll

@end titlepage

" >> fossil.texi

# Add @contents
printf "@contents\n

" >> fossil.texi

# Check fossil version. Only thing wrong with this is which fossil binary is picked up first.
# Older versions of fossil didn't have a version number, just a truncated commit hash.
fossil version | sed 's/This is fossil version /@set VERSION /' | cut -c1-17  >> fossil.texi

# Insert repeat of Top node for PDF. We have to hack to do this
printf "@ifnotinfo
@ifnothtml
@node Introduction
@top Introduction for Fossil - a distributed version control system

Fossil is a distributed version control system (DVCS) with built-in
forum, wiki, ticket tracker, CGI/HTTP interface, and HTTP server.
This file documents version @value{VERSION} of Fossil.

@end ifnothtml
@end ifnotinfo
" >> fossil.texi

printf "@node Top
@chapter Fossil

Fossil is a distributed version control system (DVCS) with built-in
forum, wiki, ticket tracker, CGI/HTTP interface, and HTTP server.
This file documents version @value{VERSION} of Fossil.

@c     @node menu
@menu
* Introduction::               Introduction and features listed from the website front page.
* Common commands::      These are the commands that are most likely to be used.
* Uncommon commands::    These aren't used as often, but they're still there when needed.
* Test commands::        These are definitely not recommended for production use.
* Fossil settings::      These describe fossil settings. 
* Web commands::         Available webpage help.
* Common arguments::     Arguments common to all commands.
* License::              The license agreement of the fossil project.
@end menu

You can get help for any of the available fossil commands by using:

@example
fossil help some-command
@end example

This will show you help on some-command. I've attempted to put all the available
help nodes from fossil into here, but it's vaguely possible I've missed some.

" >> fossil.texi

# Add in the Features from the front webpage
printf "@node Introduction,Common commands,Top,Top
@chapter Introduction

Fossil is a distributed version control system (DVCS) with built-in
forum, wiki, ticket tracker, CGI/HTTP interface, and HTTP server.
This file documents version @value{VERSION} of Fossil.

This is a quick breakdown of the things that you get when you run the fossil binary:

@enumerate
@item
Integrated Bug Tracking, Wiki, Forum, and Technotes
 - In addition to doing distributed version control like Git and Mercurial, Fossil also supports bug tracking, wiki, forum, and tech-notes.

@item
Built-in Web Interface
 - Fossil has a built-in and intuitive web interface that promotes project situational awareness. Type \"fossil ui\" and Fossil automatically opens a web browser to a page that shows detailed graphical history and status information on that project.

@item
Self-Contained
 -  Fossil is a single self-contained stand-alone executable. To install, simply download a precompiled binary for Linux, Mac, OpenBSD, or Windows and put it on your  \$PATH. Easy-to-compile source code is available for users on other platforms.

@item
Simple Networking
 - No custom protocols or TCP ports. Fossil uses plain old HTTP (or HTTPS or SSH) for all network communications, so it works fine from behind restrictive firewalls, including proxies. The protocol is bandwidth efficient to the point that Fossil can be used comfortably over dial-up or over the exceedingly slow Wifi on airliners.

@item
CGI/SCGI Enabled
 - No server is required, but if you want to set one up, Fossil supports four easy server configurations.

@item
Autosync
 -  Fossil supports \"autosync\" mode which helps to keep projects moving forward by reducing the amount of needless forking and merging often associated with distributed projects.

@item
Robust & Reliable
 - Fossil stores content using an enduring file format in an SQLite database so that transactions are atomic even if interrupted by a power loss or system crash. Automatic self-checks verify that all aspects of the repository are consistent prior to each commit.

@item
Free and Open-Source
 - Uses the 2-clause BSD license.
@end enumerate
" >> fossil.texi

###### Common commands
echo "List common commands"

printf "@node Common commands,Uncommon commands,Introduction,Top
@chapter Common commands

These are the more commonly used commands for the average fossil user. They're
listed by fossil when you run the command:

@example
fossil help
@end example

They are similar to commands that are available in other VCS programs such as
subversion, git or mercurial.

" >> fossil.texi

# begin menu for common keywords
printf "@menu\n" >> fossil.texi

# Slurp in Common keywords from fossil help
# WARNING: tail count is brittle
echo "Grab common keywords for menu"
for u in $(for t in $(fossil help | tail -n +11| sed '$d'); do echo "$t"; done | sort);  do echo "* ${u}::"; done >> fossil.texi

# Add end menu, add some space too
printf "@end menu

" >> fossil.texi

# Add in the actual help for common commands
# WARNING: tail count is brittle
# tail command pops off the first fourteen lines, sed commands remove the last two lines.
echo "Fossil output common help to workfile"

# I'd like if this could start at where the "Options:" is, and finish at the next
# pair of blank lines.
#printf "@table @option
#
#" >> fossil.texi

fossil help -v | tail -n +14 | sed '$d' | sed '$d' >workfile

# swap out @ with @@ so texinfo doesn't barf
echo "Doubling up the @'s"
sed -i -e 's/@/@@/g' workfile

echo "Swapping out # for @node ... \n@section ..."
# This swaps out "# keyword" with 
# @node keyword
# @section keyword
# breaks on *BSD's seds, needs gsed there

# Check the OS so we can use gsed if needed
MYOS="$(uname )"
if [[ ${MYOS} == "Linux" ]]; then
    sed -i -e 's/^##* \([a-z0-9-]\{1,\}\)/@node \1\n@section \1\n/' workfile
else
    # We'll assume we're on a BSD here, even though this won't always be true
    gsed -i -e 's/^##* \([a-z0-9-]\{1,\}\)/@node \1\n@section \1\n/' workfile
fi

# turns --switches into @option{--switches}
sed -i -e 's/--\([[:alnum:]-]\{1,\}\)/@option\{--\1\}/g' workfile
# now do the same for places where there's -f|--force
sed -i -e 's/|--\([[:alnum:]-]\{1,\}\)/|@option\{--\1\}/g' workfile
# turns -switches into @option{-switches}. Usually starts with space
sed -i -e 's/ -\([[:alnum:]-]\{1,\}\)/ @option\{-\1\}/g' workfile
# ... and adds it to the output file with some space
cat workfile >> fossil.texi

##### Uncommon commands ####
echo "List uncommon commands"
printf "
@node Uncommon commands,Test commands,Common commands,Top
@chapter Uncommon commands

These are auxiliary commands,  listed by fossil when you run the command:

@example
fossil help -x
@end example

They're not used quite as often, and are normally used in specific
circumstances, such as creating fossils suitable for hosting.

@menu
" >> fossil.texi

# Slurp in auxiliary/uncommon keywords - no need to remove last line here
echo "Grab uncommon keywords for menu"
for u in $(for t in $(fossil help -x); do echo "$t"; done | sort); do echo "* ${u}::"; done >> fossil.texi

echo "@end menu" >> fossil.texi
echo "" >> fossil.texi

# Now add all the help from "fossil help -x -v"
# WARNING: tail count is brittle
# sed comands remove the last two lines.
echo "Fossil output auxiliary help to workfile"
fossil help -x -v | tail -n +4 | sed '$d' | sed '$d' >workfile

# swap out @ with @@ so texinfo doesn't barf
echo "Doubling up the @'s"
sed -i -e 's/@/@@/g' workfile

# This swaps out "# keyword" with 
# @node keyword
# @unnumbered keyword
# breaks on *BSD's seds, needs gsed there
echo "Swapping out # for @node ... \n@unnumbered ..."

if [[ ${MYOS} == "Linux" ]]; then
    sed -i -e 's/^##* \([a-z0-9-]\{1,\}\)/@node \1\n@section \1\n/' workfile
else
    # We'll assume we're on a BSD here, even though this won't always be true
    gsed -i -e 's/^##* \([a-z0-9-]\{1,\}\)/@node \1\n@section \1\n/' workfile
fi

# turns --switches into @option{--switches}
sed -i -e 's/--\([[:alnum:]-]\{1,\}\)/@option\{--\1\}/g' workfile

# ... and adds it to the output file with a spacer line
cat workfile >> fossil.texi
echo "" >> fossil.texi

##### Now the test commands. Here be dragons. #####
echo "List test commands"
printf "@node Test commands,Fossil settings,Uncommon commands,Top
@chapter Testing commands

These are testing commands, listed by fossil when you run the command:

@example
fossil help -t
@end example

They're often used to solve specific little problems that didn't warrant a full
tool, but are useful enough to be kept around. They are most definitely not
supported, and the developers will expect to change these far more often. They
are not stable, so do not depend upon their behavior, or even their existence.

@menu\n" >> fossil.texi

# Insert test commands in here
echo "Grab test keywords for menu"
for u in $(for t in $(fossil help -t); do echo "$t"; done | sort); do echo "* ${u}::"; done >> fossil.texi

# Now end that menu (Test commands)
echo "@end menu

" >> fossil.texi

# Now add all the help from "fossil help -t -v"
# WARNING: tail count is brittle
# sed comands remove the last two lines.
echo "Fossil output test help to workfile"
fossil help -t -v | tail -n +4 | sed '$d' | sed '$d' >workfile

# swap out @ with @@ so texinfo doesn't barf
echo "Doubling up the @'s"
sed -i -e 's/@/@@/g' workfile

# This swaps out "# keyword" with 
# @node keyword
# @unnumbered keyword
# breaks on *BSD's seds, needs gsed there
echo "Swapping out # for @node ... \n@unnumbered ..."

if [[ ${MYOS} == "Linux" ]]; then
    sed -i -e 's/^##* \([a-z0-9-]\{1,\}\)/@node \1\n@section \1\n/' workfile
else
    # We'll assume we're on a BSD here, even though this won't always be true
    gsed -i -e 's/^##* \([a-z0-9-]\{1,\}\)/@node \1\n@section \1\n/' workfile
fi

# turns --switches into @option{--switches}
sed -i -e 's/--\([[:alnum:]-]\{1,\}\)/@option\{--\1\}/g' workfile

# ... and adds it to the output file with a spacer line
cat workfile >> fossil.texi
echo "" >> fossil.texi

##### Now, add in fossil settings #####
# The usual wyvern warnings.
echo "List settings"
printf "@node Fossil settings,Web commands,Test commands,Top
@chapter Fossil settings

These are help pages for settings within fossil, shown when you run:
@example
fossil help -s
@end example

@menu\n" >> fossil.texi

# Insert settings keywords in here
echo "Grab settings keywords for menu"
for u in $(for t in $(fossil help -s); do echo "$t"; done | sort); do echo "* ${u}::"; done >> fossil.texi

# and finish the menu
echo "@end menu

" >> fossil.texi
# Now add all the help from "fossil help -s -v"
# WARNING: tail count is brittle
# sed comands remove the last two lines.
echo "Fossil output test help to workfile"
fossil help -s -v | tail -n +4 | sed '$d' | sed '$d' >workfile

# swap out @ with @@ so texinfo doesn't barf
echo "Doubling up the @'s"
sed -i -e 's/@/@@/g' workfile

# This swaps out "# keyword" with 
# @node keyword
# @unnumbered keyword
# breaks on *BSD's seds, needs gsed there
echo "Swapping out # for @node ... \n@unnumbered ..."

if [[ ${MYOS} == "Linux" ]]; then
    sed -i -e 's/^##* \([a-z0-9-]\{1,\}\)/@node \1\n@section \1\n/' workfile
else
    # We'll assume we're on a BSD here, even though this won't always be true
    gsed -i -e 's/^##* \([a-z0-9-]\{1,\}\)/@node \1\n@section \1\n/' workfile
fi

# This has to be done before swapping --switches because the next command
# adds @option(--switches} and hence reuses the {}.
echo "Swapping out {} for @{ @}"
# swaps out {} for @{ @}
if [[ ${MYOS} == "Linux" ]]; then
    sed -i -e 's/{/@{/g' -e 's/}/@}/g' workfile
else
    gsed -i -e 's/{/@{/g' -e 's/}/@}/g' workfile
fi

# turns --switches into @option{--switches}
sed -i -e 's/--\([[:alnum:]-]\{1,\}\)/@option\{--\1\}/g' workfile

# ... and adds it to the output file with a spacer line
cat workfile >> fossil.texi
echo "" >> fossil.texi

# Now the webpage content. Here be wild wild web pages.
echo "List web commands"
printf "@node Web commands,Common arguments,Fossil settings,Top
@chapter Web commands

These are help pages for the internal web pages, listed by fossil when you run the command:

@example
fossil help -w
@end example

All of these can be provided to the URL line on the browser address input like the
example below, that starts from the final / (in this case, /timeline?ms=glob):

@example
https://fossil.example.com/fossil/timeline?ms=glob
@end example

@menu\n" >> fossil.texi

# Insert webpage keywords in here
echo "Grab web keywords for menu"
for u in $(for t in $(fossil help -w); do echo "$t"; done | sort); do echo "* ${u}::"; done >> fossil.texi


# Now end that menu (Webpages)
echo "@end menu

" >> fossil.texi

# Now add all the help from "fossil help -w -v"
# WARNING: tail count is brittle
# sed comands remove the last two lines.
echo "Fossil output webpage help to workfile"
fossil help -w -v | tail -n +4 | sed '$d' | sed '$d' >workfile

# swap out @ with @@ so texinfo doesn't barf
echo "Doubling up the @'s"
sed -i -e 's/@/@@/g' workfile

# This swaps out "# keyword" with 
# @node keyword
# @unnumbered keyword
# breaks on *BSD's seds, needs gsed there
echo "Swapping out # for @node ... \n@unnumbered ..."

if [[ ${MYOS} == "Linux" ]]; then
# use a different separator here, as we need to keep /_. in strings
    sed -i -e 's%^##* /\([_.a-z0-9-]\{1,\}\)%@node /\1\n@section /\1\n%' workfile
else
    # We'll assume we're on a BSD here, even though this won't always be true
    gsed -i -e 's%^##* /\([_.a-z0-9-]\{1,\}\)%@node /\1\n@section /\1\n%' workfile
fi

# This has to be done before swapping --switches because the next command
# adds @option(--switches} and hence reuses the {}.
echo "Swapping out {} for @{ @}"
# swaps out {} for @{ @}
if [[ ${MYOS} == "Linux" ]]; then
    sed -i -e 's/{/@{/g' -e 's/}/@}/g' workfile
else
    gsed -i -e 's/{/@{/g' -e 's/}/@}/g' workfile
fi

echo "Swapping out --switches for @option{--switches}"
# turns --switches into @option{--switches}
if [[ ${MYOS} == "Linux" ]]; then
    sed -i -e 's/--\([[:alnum:]-]\{1,\}\)/@option\{--\1\}/g' workfile
else
    gsed -i -e 's/--\([[:alnum:]-]\{1,\}\)/@option\{--\1\}/g' workfile
fi

# ... and adds it to the output file with a spacer line
cat workfile >> fossil.texi
echo "" >> fossil.texi

# Add in common args
echo "List common args"
echo "@node Common arguments,License,Web commands,Top
@chapter Common arguments

These are commandline arguments that are common to all fossil commands.

" >> fossil.texi

# Slurp in auxiliary/uncommon keywords
echo "Grab common args text"
# At the moment, this doesn't do lines, and also requires GNU sed
if [[ ${MYOS} == "Linux" ]]; then
    fossil help -o | sed -e '2,$s/^  /\n/' -e '2,$s/--\([[:alnum:]-]\{1,\}\)/@option\{--\1\}/g' >> fossil.texi
else
    fossil help -o | gsed -e '2,$s/^  /\n/' -e '2,$s/--\([[:alnum:]-]\{1,\}\)/@option\{--\1\}/g' >> fossil.texi
fi
# stray stuff that didn't work
# sed -e '/--/s/--\(.*\s+\)/@item --\1 /' >> fossil.texi
echo "" >> fossil.texi

# Add in licence. Look for it in two places, just in case we're in tools/ when
# we call this program.
if [[ -f COPYRIGHT-BSD2.txt ]]; then
  HERE="COPYRIGHT-BSD2.txt"
elif [[ -f ../COPYRIGHT-BSD2.txt ]]; then
  HERE="../COPYRIGHT-BSD2.txt"
else
	echo "Where's COPYRIGHT-BSD2.txt?"
fi
# TODO: This should fail if we couldn't find COPYRIGHT-BSD2.txt
printf "
@node License,,Common arguments,Top
@chapter License agreement

@include ${HERE}

" >> fossil.texi

# Every good thing has to end
echo "@bye" >> fossil.texi

# and now we make the final info file - commented out for now
# makeinfo fossil.texi

echo "Done ... for now. Please check fossil.texi file over for inconsistencies, and fill in descriptions."
echo "Once everything's good, you can run your system's makeinfo command to turn"
echo "your .texi file into a .info file to install where you need."
Changes to tools/fossil-stress.tcl.
91
92
93
94
95
96
97
98

99
100
101
102
103
104
105
91
92
93
94
95
96
97

98
99
100
101
102
103
104
105







-
+







  /vdiff?from=2015-01-01&to=trunk&diff=0
  /wcontent
  /fileage
  /dir
  /tree
  /uvlist
  /stat
  /test_env
  /test-env
  /sitemap
  /hash-collisions
  /artifact_stats
  /bloblist
  /bigbloblist
  /wiki_rules
  /md_rules
Changes to tools/makeheaders.c.
484
485
486
487
488
489
490
491

492
493
494
495
496
497
498

499
500
501
502
503
504
505
484
485
486
487
488
489
490

491
492
493
494
495
496
497

498
499
500
501
502
503
504
505







-
+






-
+







#define StringGet(S) ((S)->zText?(S)->zText:"")

/*
** Compute a hash on a string.  The number returned is a non-negative
** value between 0 and 2**31 - 1
*/
static int Hash(const char *z, int n){
  int h = 0;
  unsigned int h = 0;
  if( n<=0 ){
    n = strlen(z);
  }
  while( n-- ){
    h = h ^ (h<<5) ^ *z++;
  }
  return h & 0x7fffffff;
  return (int)(h & 0x7fffffff);
}

/*
** Given an identifier name, try to find a declaration for that
** identifier in the hash table.  If found, return a pointer to
** the Decl structure.  If not found, return 0.
*/
Changes to tools/makemake.tcl.
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
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







+



















+







  loadctrl
  login
  lookslike
  main
  manifest
  markdown
  markdown_html
  match
  md5
  merge
  merge3
  moderate
  name
  patch
  path
  piechart
  pikchrshow
  pivot
  popen
  pqueue
  printf
  publish
  purge
  rebuild
  regexp
  repolist
  report
  robot
  rss
  schema
  search
  security_audit
  setup
  setupuser
  sha1
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
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







+
















+







  vfile
  wiki
  wikiformat
  winfile
  winhttp
  xfer
  xfersetup
  xsystem
  zip
  http_ssl
}

# Source files which live under $srcDirExt, but only those for which
# we need to run makeheaders. External sources which have their own
# header files must not be in this list.
set src_ext {
  pikchr
}

# Additional resource files that get built into the executable.
# These paths are all resolved from the src/ directory, so must
# be relative to that.
set extra_files {
  diff.tcl
  merge.tcl
  markdown.md
  wiki.wiki
  *.js
  default.css
  style.*.css
  ../skins/*/*.txt
  sounds/*.wav
236
237
238
239
240
241
242

243
244


245
246


247
248
249
250
251
252
253
240
241
242
243
244
245
246
247
248
249
250
251


252
253
254
255
256
257
258
259
260







+


+
+
-
-
+
+







  -DSQLITE_OMIT_DEPRECATED
  -DSQLITE_OMIT_PROGRESS_CALLBACK
  -DSQLITE_OMIT_SHARED_CACHE
  -DSQLITE_OMIT_LOAD_EXTENSION
  -DSQLITE_MAX_EXPR_DEPTH=0
  -DSQLITE_ENABLE_LOCKING_STYLE=0
  -DSQLITE_DEFAULT_FILE_FORMAT=4
  -DSQLITE_ENABLE_DBSTAT_VTAB
  -DSQLITE_ENABLE_EXPLAIN_COMMENTS
  -DSQLITE_ENABLE_FTS4
  -DSQLITE_ENABLE_FTS5
  -DSQLITE_ENABLE_MATH_FUNCTIONS
  -DSQLITE_ENABLE_DBSTAT_VTAB
  -DSQLITE_ENABLE_FTS5
  -DSQLITE_ENABLE_PERCENTILE
  -DSQLITE_ENABLE_SETLK_TIMEOUT
  -DSQLITE_ENABLE_STMTVTAB
  -DSQLITE_HAVE_ZLIB
  -DSQLITE_ENABLE_DBPAGE_VTAB
  -DSQLITE_TRUSTED_SCHEMA=0
  -DHAVE_USLEEP
}
#lappend SQLITE_OPTIONS -DSQLITE_ENABLE_FTS3=1
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
408
409
410
411
412
413

414
415
416
417
418
419
420
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
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422

423
424
425
426
427
428
429
430







-
+








-
-
-

+



+



+



+



+



+















-
+







}

writeln [string map [list \
    <<<SQLITE_OPTIONS>>> [join $SQLITE_OPTIONS " \\\n                 "] \
    <<<SHELL_OPTIONS>>> [join $SHELL_OPTIONS " \\\n                "] \
    <<<PIKCHR_OPTIONS>>> [join $PIKCHR_OPTIONS " \\\n                "] \
    <<<NEXT_LINE>>> \\] {
all:	$(OBJDIR) $(APPNAME)
all:	$(APPNAME)

install:	all
	mkdir -p $(INSTALLDIR)
	cp $(APPNAME) $(INSTALLDIR)

codecheck:	$(TRANS_SRC) $(OBJDIR)/codecheck1
	$(OBJDIR)/codecheck1 $(TRANS_SRC)

$(OBJDIR):
	-mkdir $(OBJDIR)

$(OBJDIR)/translate:	$(SRCDIR_tools)/translate.c
	-mkdir -p $(OBJDIR)
	$(XBCC) -o $(OBJDIR)/translate $(SRCDIR_tools)/translate.c

$(OBJDIR)/makeheaders:	$(SRCDIR_tools)/makeheaders.c
	-mkdir -p $(OBJDIR)
	$(XBCC) -o $(OBJDIR)/makeheaders $(SRCDIR_tools)/makeheaders.c

$(OBJDIR)/mkindex:	$(SRCDIR_tools)/mkindex.c
	-mkdir -p $(OBJDIR)
	$(XBCC) -o $(OBJDIR)/mkindex $(SRCDIR_tools)/mkindex.c

$(OBJDIR)/mkbuiltin:	$(SRCDIR_tools)/mkbuiltin.c
	-mkdir -p $(OBJDIR)
	$(XBCC) -o $(OBJDIR)/mkbuiltin $(SRCDIR_tools)/mkbuiltin.c

$(OBJDIR)/mkversion:	$(SRCDIR_tools)/mkversion.c
	-mkdir -p $(OBJDIR)
	$(XBCC) -o $(OBJDIR)/mkversion $(SRCDIR_tools)/mkversion.c

$(OBJDIR)/codecheck1:	$(SRCDIR_tools)/codecheck1.c
	-mkdir -p $(OBJDIR)
	$(XBCC) -o $(OBJDIR)/codecheck1 $(SRCDIR_tools)/codecheck1.c

# Run the test suite.
# Other flags that can be included in TESTFLAGS are:
#
#  -halt     Stop testing after the first failed test
#  -keep     Keep the temporary workspace for debugging
#  -prot     Write a detailed log of the tests to the file ./prot
#  -verbose  Include even more details in the output
#  -quiet    Hide most output from the terminal
#  -strict   Treat known bugs as failures
#
# TESTFLAGS can also include names of specific test files to limit
# the run to just those test cases.
#
test:	$(OBJDIR) $(APPNAME)
test:	$(APPNAME)
	$(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME) $(TESTFLAGS)

$(OBJDIR)/VERSION.h:	$(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION $(OBJDIR)/mkversion $(OBJDIR)/phony.h
	$(OBJDIR)/mkversion $(SRCDIR)/../manifest.uuid <<<NEXT_LINE>>>
		$(SRCDIR)/../manifest <<<NEXT_LINE>>>
		$(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h

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
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







+



+



+



-
+


-
+


-
+

-
-
+
+




+







writeln "\$(OBJDIR)/shell.o:\t\$(SQLITE3_SHELL_SRC) \$(SRCDIR_extsrc)/sqlite3.h"
writeln "\t\$(XTCC) \$(SHELL_OPTIONS) \$(SHELL_CFLAGS) \$(SEE_FLAGS) \$(LINENOISE_DEF.\$(USE_LINENOISE)) -c \$(SQLITE3_SHELL_SRC) -o \$@\n"

writeln "\$(OBJDIR)/linenoise.o:\t\$(SRCDIR_extsrc)/linenoise.c \$(SRCDIR_extsrc)/linenoise.h"
writeln "\t\$(XTCC) -c \$(SRCDIR_extsrc)/linenoise.c -o \$@\n"

writeln "\$(OBJDIR)/th.o:\t\$(SRCDIR)/th.c"
writeln "\t-mkdir -p \$(OBJDIR)\n"
writeln "\t\$(XTCC) -c \$(SRCDIR)/th.c -o \$@\n"

writeln "\$(OBJDIR)/th_lang.o:\t\$(SRCDIR)/th_lang.c"
writeln "\t-mkdir -p \$(OBJDIR)\n"
writeln "\t\$(XTCC) -c \$(SRCDIR)/th_lang.c -o \$@\n"

writeln "\$(OBJDIR)/th_tcl.o:\t\$(SRCDIR)/th_tcl.c"
writeln "\t-mkdir -p \$(OBJDIR)\n"
writeln "\t\$(XTCC) -c \$(SRCDIR)/th_tcl.c -o \$@\n"

writeln [string map [list <<<NEXT_LINE>>> \\] {
$(OBJDIR)/pikchr.o:	$(SRCDIR_extsrc)/pikchr.c
$(OBJDIR)/pikchr.o:	$(SRCDIR_extsrc)/pikchr.c $(OBJDIR)/mkversion
	$(XTCC) $(PIKCHR_OPTIONS) -c $(SRCDIR_extsrc)/pikchr.c -o $@

$(OBJDIR)/cson_amalgamation.o: $(SRCDIR_extsrc)/cson_amalgamation.c
$(OBJDIR)/cson_amalgamation.o: $(SRCDIR_extsrc)/cson_amalgamation.c $(OBJDIR)/mkversion
	$(XTCC) -c $(SRCDIR_extsrc)/cson_amalgamation.c -o $@

$(SRCDIR_extsrc)/pikchr.js: $(SRCDIR_extsrc)/pikchr.c
$(SRCDIR_extsrc)/pikchr.js: $(SRCDIR_extsrc)/pikchr.c $(MAKEFILE_LIST)
	$(EMCC_WRAPPER) -o $@ $(EMCC_OPT) --no-entry <<<NEXT_LINE>>>
        -sEXPORTED_RUNTIME_METHODS=cwrap,setValue,getValue,stackSave,stackRestore <<<NEXT_LINE>>>
        -sEXPORTED_FUNCTIONS=_pikchr $(SRCDIR_extsrc)/pikchr.c <<<NEXT_LINE>>>
        -sEXPORTED_RUNTIME_METHODS=cwrap,ccall,setValue,getValue,stackSave,stackAlloc,stackRestore <<<NEXT_LINE>>>
        -sEXPORTED_FUNCTIONS=_pikchr,_pikchr_version $(SRCDIR_extsrc)/pikchr.c <<<NEXT_LINE>>>
        -sENVIRONMENT=web <<<NEXT_LINE>>>
        -sMODULARIZE <<<NEXT_LINE>>>
        -sEXPORT_NAME=initPikchrModule <<<NEXT_LINE>>>
        --minify 0
	$(TCLSH) $(TOPDIR)/tools/randomize-js-names.tcl $(SRCDIR_extsrc)
	@chmod -x $(SRCDIR_extsrc)/pikchr.wasm
wasm: $(SRCDIR_extsrc)/pikchr.js

#
# compile_commands.json support...
#
# We have to avoid applying compile_commands support to the in-tree
Changes to tools/mkindex.c.
15
16
17
18
19
20
21






















22
23
24
25
26

27
28
29
30
31
32
33
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





+







**
*******************************************************************************
**
** This utility program scans Fossil source text looking for specially
** formatted comments and generates C source code for constant tables
** that define the behavior of commands, webpages, and settings.
**
** USAGE:
**
**     mkindex *.c >page_index.h
**
** Run this command with arguments that are all input source files to
** scan.  Generated C code appears on standard output.  The generated
** C code includes structures that:
**
**    *   Map command names to the C-language functions that implement
**        those command.
**
**    *   Map webpage names to the C-language functions that implement
**        those web pages.
**
**    *   Map settings into attributes, such as they default value for
**        each setting, and the kind of value (boolean, multi-line, etc).
**
**    *   Provide help text for commands, webpages, settings, and other
**        miscellanous help topics.
**
** COMMENT TEXT THAT THIS PROGRAM LOOKS FOR:
**
** The source code is scanned for comment lines of the form:
**
**       WEBPAGE:  /abc/xyz
**       COMMAND:  cmdname
**       SETTING:  access-log
**       TOPIC:    help-topic
**
** The WEBPAGE and COMMAND comments should be followed by a function that
** implements the webpage or command.  The form of this function is:
**
**       void function_name(void){
**
** Command names can divided into three classes:  1st-tier, 2nd-tier,
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
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







+
+
+
+
+
+
+
+
+
+
+
+










-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







** default value does contain spaces, use a separate line like this:
**
**        SETTING: pgp-command
**        DEFAULT: gpg --clearsign -o
**
** If no default is supplied, the default is assumed to be an empty string
** or "off" in the case of a boolean.
**
** A TOPIC: is followed by help text for the named topic.
**
** OUTPUTS:
**
** The output is C-language text to define and initialize a constant
** array of CmdOrPage objects named "aCommand[]".  That array is a global
** variable.  The dispatch.c source file defines the CmdOrPage object and
** deals with the aCommand[] global variable.
**
** The output also contains a constant array of Setting objects named
** aSetting[].  The Setting object is defined in db.c.  
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

/***************************************************************************
** These macros must match similar macros in dispatch.c.
**
** Allowed values for CmdOrPage.eCmdFlags. */
#define CMDFLAG_1ST_TIER     0x0001     /* Most important commands */
#define CMDFLAG_2ND_TIER     0x0002     /* Obscure and seldom used commands */
#define CMDFLAG_TEST         0x0004     /* Commands for testing only */
#define CMDFLAG_WEBPAGE      0x0008     /* Web pages */
#define CMDFLAG_COMMAND      0x0010     /* A command */
#define CMDFLAG_SETTING      0x0020     /* A setting */
#define CMDFLAG_VERSIONABLE  0x0040     /* A versionable setting */
#define CMDFLAG_BLOCKTEXT    0x0080     /* Multi-line text setting */
#define CMDFLAG_BOOLEAN      0x0100     /* A boolean setting */
#define CMDFLAG_RAWCONTENT   0x0200     /* Do not interpret webpage content */
#define CMDFLAG_SENSITIVE    0x0400     /* Security-sensitive setting */
#define CMDFLAG_HIDDEN       0x0800     /* Elide from most listings */
#define CMDFLAG_LDAVG_EXEMPT 0x1000     /* Exempt from load_control() */
#define CMDFLAG_ALIAS        0x2000     /* Command aliases */
#define CMDFLAG_KEEPEMPTY    0x4000     /* Do not unset empty settings */
#define CMDFLAG_1ST_TIER     0x000001    /* Most important commands */
#define CMDFLAG_2ND_TIER     0x000002    /* Obscure and seldom used commands */
#define CMDFLAG_TEST         0x000004    /* Commands for testing only */
#define CMDFLAG_WEBPAGE      0x000008    /* Web pages */
#define CMDFLAG_COMMAND      0x000010    /* A command */
#define CMDFLAG_SETTING      0x000020    /* A setting */
#define CMDFLAG_VERSIONABLE  0x000040    /* A versionable setting */
#define CMDFLAG_BLOCKTEXT    0x000080    /* Multi-line text setting */
#define CMDFLAG_BOOLEAN      0x000100    /* A boolean setting */
#define CMDFLAG_RAWCONTENT   0x000200    /* Do not interpret webpage content */
#define CMDFLAG_SENSITIVE    0x000400    /* Security-sensitive setting */
#define CMDFLAG_HIDDEN       0x000800    /* Elide from most listings */
#define CMDFLAG_LDAVG_EXEMPT 0x001000    /* Exempt from load_control() */
#define CMDFLAG_ALIAS        0x002000    /* Command aliases */
#define CMDFLAG_KEEPEMPTY    0x004000    /* Do not unset empty settings */
#define CMDFLAG_ABBREVSUBCMD 0x008000    /* Abbreviated subcmd in help text */
#define CMDFLAG_TOPIC        0x010000    /* A help topic */
/**************************************************************************/

/*
** Each entry looks like this:
*/
typedef struct Entry {
  int eType;        /* CMDFLAG_* values */
278
279
280
281
282
283
284



285
286
287
288
289
290
291
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331







+
+
+







      aEntry[nUsed].zDflt = string_dup(&zLine[i+8], j-8);
    }else if( j>9 && strncmp(&zLine[i], "variable=", 9)==0 ){
      aEntry[nUsed].zVar = string_dup(&zLine[i+9], j-9);
    }else if( j==6 && strncmp(&zLine[i], "hidden", 6)==0 ){
      aEntry[nUsed].eType |= CMDFLAG_HIDDEN;
    }else if( j==14 && strncmp(&zLine[i], "loadavg-exempt", 14)==0 ){
      aEntry[nUsed].eType |= CMDFLAG_LDAVG_EXEMPT;
    }else if( (j==23 && strncmp(&zLine[i], "abbreviated-subcommands", 23)==0)
           || (j==12 && strncmp(&zLine[i], "abbrv-subcom", 12)==0) ){
      aEntry[nUsed].eType |= CMDFLAG_ABBREVSUBCMD;
    }else{
      fprintf(stderr, "%s:%d: unknown option: '%.*s'\n",
              zFile, nLine, j, &zLine[i]);
      nErr++;
    }
  }

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
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







-
+









+












-
-
+
+








/*
** Scan a line for a function that implements a web page or command.
*/
void scan_for_func(char *zLine){
  int i,j,k;
  char *z;
  int isSetting;
  int hasFunc;
  if( nUsed<=nFixed ) return;
  if( strncmp(zLine, "**", 2)==0
   && fossil_isspace(zLine[2])
   && strlen(zLine)<sizeof(zHelp)-nHelp-1
   && nUsed>nFixed
   && strncmp(zLine,"** COMMAND:",11)!=0
   && strncmp(zLine,"** WEBPAGE:",11)!=0
   && strncmp(zLine,"** SETTING:",11)!=0
   && strncmp(zLine,"** DEFAULT:",11)!=0
   && strncmp(zLine,"** TOPIC:",9)!=0
  ){
    if( zLine[2]=='\n' ){
      zHelp[nHelp++] = '\n';
    }else{
      if( strncmp(&zLine[3], "Usage: ", 6)==0 ) nHelp = 0;
      local_strcpy(&zHelp[nHelp], &zLine[3]);
      nHelp += strlen(&zHelp[nHelp]);
    }
    return;
  }
  for(i=0; fossil_isspace(zLine[i]); i++){}
  if( zLine[i]==0 ) return;
  isSetting = (aEntry[nFixed].eType & CMDFLAG_SETTING)!=0;
  if( !isSetting ){
  hasFunc = (aEntry[nFixed].eType & (CMDFLAG_SETTING|CMDFLAG_TOPIC))==0;
  if( hasFunc ){
    if( strncmp(&zLine[i],"void",4)!=0 ){
      if( zLine[i]!='*' ) goto page_skip;
      return;
    }
    i += 4;
    if( !fossil_isspace(zLine[i]) ) goto page_skip;
    while( fossil_isspace(zLine[i]) ){ i++; }
384
385
386
387
388
389
390
391

392
393
394
395
396

397
398
399
400
401
402
403
425
426
427
428
429
430
431

432
433
434
435
436

437
438
439
440
441
442
443
444







-
+




-
+







  if( k<nHelp ){
    z = string_dup(&zHelp[k], nHelp-k);
  }else{
    z = "";
  }
  for(k=nFixed; k<nUsed; k++){
    aEntry[k].zIf = zIf[0] ? string_dup(zIf, -1) : 0;
    aEntry[k].zFunc = isSetting ? "0" : string_dup(&zLine[i], j);
    aEntry[k].zFunc = hasFunc ? string_dup(&zLine[i], j) : "0";
    aEntry[k].zHelp = z;
    z = 0;
    aEntry[k].iHelp = nFixed;
  }
  if( !isSetting ){
  if( hasFunc ){
    i+=j;
    while( fossil_isspace(zLine[i]) ){ i++; }
    if( zLine[i]!='(' ) goto page_skip;
  }
  nFixed = nUsed;
  nHelp = 0;
  return;
436
437
438
439
440
441
442
443

444
445
446
447
448
449
450
477
478
479
480
481
482
483

484
485
486
487
488
489
490
491







-
+







    "** This file was generated by the mkindex.exe program based on\n"
    "** comments in other Fossil source files.\n"
    "*/\n"
  );

  /* Output declarations for all the action functions */
  for(i=0; i<nFixed; i++){
    if( aEntry[i].eType & CMDFLAG_SETTING ) continue;
    if( aEntry[i].eType & (CMDFLAG_SETTING|CMDFLAG_TOPIC) ) continue;
    if( aEntry[i].zIf ) printf("%s", aEntry[i].zIf);
    printf("extern void %s(void);\n", aEntry[i].zFunc);
    if( aEntry[i].zIf ) printf("#endif\n");
  }

  /* Output strings for all the help text */
  for(i=0; i<nFixed; i++){
473
474
475
476
477
478
479
480

481
482
483
484
485
486
487
514
515
516
517
518
519
520

521
522
523
524
525
526
527
528







-
+







    int n = strlen(z);
    if( n>mxLen ) mxLen = n;
    if( aEntry[i].zIf ){
      printf("%s", aEntry[i].zIf);
    }else if( (aEntry[i].eType & CMDFLAG_WEBPAGE)!=0 ){
      nWeb++;
    }
    printf("  { \"%.*s\",%*s%s,%*szHelp%03d, %3d, 0x%03x },\n",
    printf("  { \"%.*s\",%*s%s,%*szHelp%03d, %3d, 0x%05x },\n",
      n, z,
      25-n, "",
      aEntry[i].zFunc,
      (int)(29-strlen(aEntry[i].zFunc)), "",
      aEntry[i].iHelp,
      aEntry[i].iHelp,
      aEntry[i].eType
544
545
546
547
548
549
550

551
552
553
554
555
556
557
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599







+







    nLine++;
    scan_for_if(zLine);
    scan_for_label("WEBPAGE:",zLine,CMDFLAG_WEBPAGE);
    scan_for_label("COMMAND:",zLine,CMDFLAG_COMMAND);
    scan_for_func(zLine);
    scan_for_label("SETTING:",zLine,CMDFLAG_SETTING);
    scan_for_default(zLine);
    scan_for_label("TOPIC:",zLine,CMDFLAG_TOPIC);
  }
  fclose(in);
  nUsed = nFixed;
}

int main(int argc, char **argv){
  int i;
Added tools/randomize-js-names.tcl.




































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
#!/usr/bin/tclsh
#
# This script is run as part of "make wasm".  After emcc has
# run to generate extsrc/pikchr.wasm and extsrc/pikchr.js from
# extsrc/pikchr.c, we need to make changes to these filenames to
# work around caching problems.
#
#    (1)  in extsrc/pikchr.js ->  change "pikchr.wasm" into
#         "pikchr-vNNNNNNNN.wasm" where Ns are random digits.
#
#    (2)  in extsrc/pikchr-worker.js -> change "pikchr-vNNNNNNNN.js"
#         by altering the random digits N.
#
set DIR extsrc
if {[llength $argv]>0} {
  set DIR [lindex $argv 0]
}

set R [expr {int(rand()*10000000000)+1000000000}]
set in [open $DIR/pikchr.js rb]
set f1 [read $in]
close $in
set f1mod [regsub {\ypikchr(-v\d+)?\.wasm\y} $f1 "pikchr-v$R.wasm"]
set out [open $DIR/pikchr.js wb]
puts -nonewline $out $f1mod
close $out
puts "modified $DIR/pikchr.js to reference \"pikchr-v$R.wasm\""

set in [open $DIR/pikchr-worker.js rb]
set f1 [read $in]
close $in
set f1mod [regsub {\ypikchr(-v\d+)?\.js\y} $f1 "pikchr-v$R.js"]
set out [open $DIR/pikchr-worker.js wb]
puts -nonewline $out $f1mod
close $out
puts "modified $DIR/pikchr-worker.js to reference \"pikchr-v$R.js\""
Changes to tools/translate.c.
76
77
78
79
80
81
82











83
84
85
86
87
88
89
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







+
+
+
+
+
+
+
+
+
+
+







*/
static int inStr = 0;

/*
** Name of files being processed
*/
static const char *zInFile = "(stdin)";

/*
** The `fossil_isspace()' function copied from the Fossil source code.
** Some MSVC runtime library versions of `isspace()' break with an `assert()' if
** the input is smaller than -1 or greater than 255 in debug builds, due to sign
** extension when promoting `signed char' to `int' for non-ASCII characters. Use
** an `isspace()' replacement instead of explicit type casts to `unsigned char'.
*/
int fossil_isspace(char c){
  return c==' ' || (c<='\r' && c>='\t');
}

/*
** Terminate an active cgi_printf() or free string
*/
static void end_block(FILE *out){
  if( inPrint ){
    zArg[nArg] = 0;
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
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







-
+









-
+












-
+

















-
+







  int lineNo = 0;       /* Line number */
  char zLine[2000];     /* A single line of input */
  char zOut[4000];      /* The input line translated into appropriate output */

  c1 = c2 = '-';
  while( fgets(zLine, sizeof(zLine), in) ){
    lineNo++;
    for(i=0; zLine[i] && isspace(zLine[i]); i++){}
    for(i=0; zLine[i] && fossil_isspace(zLine[i]); i++){}
    if( zLine[i]!='@' ){
      if( inPrint || inStr ) end_block(out);
      fprintf(out,"%s",zLine);
                       /* 0123456789 12345 */
      if( strncmp(zLine, "/* @-comment: ", 14)==0 ){
        c1 = zLine[14];
        c2 = zLine[15];
      }
      i += strlen(&zLine[i]);
      while( i>0 && isspace(zLine[i-1]) ){ i--; }
      while( i>0 && fossil_isspace(zLine[i-1]) ){ i--; }
      lastWasEq    = i>0 && zLine[i-1]=='=';
      lastWasComma = i>0 && zLine[i-1]==',';
    }else if( lastWasEq || lastWasComma){
      /* If the last non-whitespace character before the first @ was
      ** an "="(var init/set) or a ","(const definition in list) then
      ** generate a string literal.  But skip comments
      ** consisting of all text between c1 and c2 (default "--")
      ** and end of line.
      */
      int indent, omitline;
      char *zNewline = "\\n";
      i++;
      if( isspace(zLine[i]) ){ i++; }
      if( fossil_isspace(zLine[i]) ){ i++; }
      indent = i - 2;
      if( indent<0 ) indent = 0;
      omitline = 0;
      for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
        if( zLine[i]==c1 && (c2==' ' || zLine[i+1]==c2) ){
           omitline = 1; break;
        }
        if( zLine[i]=='\\' && (zLine[i+1]==0 || zLine[i+1]=='\r'
                                 || zLine[i+1]=='\n') ){
          zLine[i] = 0;
          zNewline = "";
          /* fprintf(stderr, "%s:%d: omit newline\n", zInFile, lineNo); */
          break;
        }
        if( zLine[i]=='\\' || zLine[i]=='"' ){ zOut[j++] = '\\'; }
        zOut[j++] = zLine[i];
      }
      if( zNewline[0] ) while( j>0 && isspace(zOut[j-1]) ){ j--; }
      if( zNewline[0] ) while( j>0 && fossil_isspace(zOut[j-1]) ){ j--; }
      zOut[j] = 0;
      if( j<=0 && omitline ){
        fprintf(out,"\n");
      }else{
        fprintf(out,"%*s\"%s%s\"\n",indent, "", zOut, zNewline);
      }
    }else{
169
170
171
172
173
174
175
176

177
178
179
180
181
182
183
180
181
182
183
184
185
186

187
188
189
190
191
192
193
194







-
+







      */
      const char *zNewline = "\\n";
      int indent;
      int nC;
      int nParam;
      char c;
      i++;
      if( isspace(zLine[i]) ){ i++; }
      if( fossil_isspace(zLine[i]) ){ i++; }
      indent = i;
      for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
        if( zLine[i]=='\\' && (!zLine[i+1] || zLine[i+1]=='\r'
                                           || zLine[i+1]=='\n') ){
          zNewline = "";
          break;
        }
Changes to win/Makefile.dmc.
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
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







-
+

-
+



-
+

-
+


















-
+







SSL    =

CFLAGS = -o
BCC    = $(DMDIR)\bin\dmc $(CFLAGS)
TCC    = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL)
LIBS   = $(DMDIR)\extra\lib\ zlib wsock32 advapi32 dnsapi

SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -DHAVE_USLEEP
SQLITE_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_MATH_FUNCTIONS -DSQLITE_ENABLE_PERCENTILE -DSQLITE_ENABLE_SETLK_TIMEOUT -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -DHAVE_USLEEP

SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -DHAVE_USLEEP -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen
SHELL_OPTIONS = -DNDEBUG=1 -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS -DSQLITE_OMIT_DECLTYPE -DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_SHARED_CACHE -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_MAX_EXPR_DEPTH=0 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_ENABLE_DBSTAT_VTAB -DSQLITE_ENABLE_EXPLAIN_COMMENTS -DSQLITE_ENABLE_FTS4 -DSQLITE_ENABLE_FTS5 -DSQLITE_ENABLE_MATH_FUNCTIONS -DSQLITE_ENABLE_PERCENTILE -DSQLITE_ENABLE_SETLK_TIMEOUT -DSQLITE_ENABLE_STMTVTAB -DSQLITE_HAVE_ZLIB -DSQLITE_ENABLE_DBPAGE_VTAB -DSQLITE_TRUSTED_SCHEMA=0 -DHAVE_USLEEP -Dmain=sqlite3_shell -DSQLITE_SHELL_IS_UTF8=1 -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=sqlcmd_get_dbname -DSQLITE_SHELL_INIT_PROC=sqlcmd_init_proc -Daccess=file_access -Dsystem=fossil_system -Dgetenv=fossil_getenv -Dfopen=fossil_fopen

PIKCHR_OPTIONS = -DPIKCHR_TOKEN_LIMIT=10000

SRC   = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c chat_.c checkin_.c checkout_.c clearsign_.c clone_.c color_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.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 patch_.c path_.c piechart_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c zip_.c
SRC   = add_.c ajax_.c alerts_.c allrepo_.c attach_.c backlink_.c backoffice_.c bag_.c bisect_.c blob_.c branch_.c browse_.c builtin_.c bundle_.c cache_.c capabilities_.c captcha_.c cgi_.c chat_.c checkin_.c checkout_.c clearsign_.c clone_.c color_.c comformat_.c configure_.c content_.c cookies_.c db_.c delta_.c deltacmd_.c deltafunc_.c descendants_.c diff_.c diffcmd_.c dispatch_.c doc_.c encode_.c etag_.c event_.c export_.c extcgi_.c file_.c fileedit_.c finfo_.c foci_.c forum_.c fshell_.c fusefs_.c fuzz_.c glob_.c graph_.c gzip_.c hname_.c hook_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c interwiki_.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 match_.c md5_.c merge_.c merge3_.c moderate_.c name_.c patch_.c path_.c piechart_.c pikchrshow_.c pivot_.c popen_.c pqueue_.c printf_.c publish_.c purge_.c rebuild_.c regexp_.c repolist_.c report_.c robot_.c rss_.c schema_.c search_.c security_audit_.c setup_.c setupuser_.c sha1_.c sha1hard_.c sha3_.c shun_.c sitemap_.c skins_.c smtp_.c sqlcmd_.c stash_.c stat_.c statrep_.c style_.c sync_.c tag_.c tar_.c terminal_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c unversioned_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c xfer_.c xfersetup_.c xsystem_.c zip_.c

OBJ   = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\chat$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\color$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$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)\patch$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$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)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O
OBJ   = $(OBJDIR)\add$O $(OBJDIR)\ajax$O $(OBJDIR)\alerts$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\backlink$O $(OBJDIR)\backoffice$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\builtin$O $(OBJDIR)\bundle$O $(OBJDIR)\cache$O $(OBJDIR)\capabilities$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\chat$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\color$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\cookies$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\deltafunc$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\dispatch$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\etag$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\extcgi$O $(OBJDIR)\file$O $(OBJDIR)\fileedit$O $(OBJDIR)\finfo$O $(OBJDIR)\foci$O $(OBJDIR)\forum$O $(OBJDIR)\fshell$O $(OBJDIR)\fusefs$O $(OBJDIR)\fuzz$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\hname$O $(OBJDIR)\hook$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\interwiki$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)\match$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\patch$O $(OBJDIR)\path$O $(OBJDIR)\piechart$O $(OBJDIR)\pikchrshow$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\publish$O $(OBJDIR)\purge$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\repolist$O $(OBJDIR)\report$O $(OBJDIR)\robot$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\security_audit$O $(OBJDIR)\setup$O $(OBJDIR)\setupuser$O $(OBJDIR)\sha1$O $(OBJDIR)\sha1hard$O $(OBJDIR)\sha3$O $(OBJDIR)\shun$O $(OBJDIR)\sitemap$O $(OBJDIR)\skins$O $(OBJDIR)\smtp$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\statrep$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\terminal$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\unversioned$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)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\xsystem$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 codecheck1$E headers  $(OBJ) $(OBJDIR)\link
	cd $(OBJDIR)
	codecheck1$E $(SRC)
	$(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 ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi chat checkin checkout clearsign clone color comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki 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 patch path piechart pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp xfer xfersetup zip shell sqlite3 th th_lang > $@
	+echo add ajax alerts allrepo attach backlink backoffice bag bisect blob branch browse builtin bundle cache capabilities captcha cgi chat checkin checkout clearsign clone color comformat configure content cookies db delta deltacmd deltafunc descendants diff diffcmd dispatch doc encode etag event export extcgi file fileedit finfo foci forum fshell fusefs fuzz glob graph gzip hname hook http http_socket http_ssl http_transport import info interwiki 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 match md5 merge merge3 moderate name patch path piechart pikchrshow pivot popen pqueue printf publish purge rebuild regexp repolist report robot rss schema search security_audit setup setupuser sha1 sha1hard sha3 shun sitemap skins smtp sqlcmd stash stat statrep style sync tag tar terminal th_main timeline tkt tktsetup undo unicode unversioned update url user utf8 util verify vfile wiki wikiformat winfile winhttp xfer xfersetup xsystem zip shell sqlite3 th th_lang > $@
	+echo fossil >> $@
	+echo fossil >> $@
	+echo $(LIBS) >> $@
	+echo. >> $@
	+echo fossil >> $@

translate$E: $(SRCDIR_tools)\translate.c
633
634
635
636
637
638
639






640
641
642
643
644
645
646
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652







+
+
+
+
+
+







	+translate$E $** > $@

$(OBJDIR)\markdown_html$O : markdown_html_.c markdown_html.h
	$(TCC) -o$@ -c markdown_html_.c

markdown_html_.c : $(SRCDIR)\markdown_html.c
	+translate$E $** > $@

$(OBJDIR)\match$O : match_.c match.h
	$(TCC) -o$@ -c match_.c

match_.c : $(SRCDIR)\match.c
	+translate$E $** > $@

$(OBJDIR)\md5$O : md5_.c md5.h
	$(TCC) -o$@ -c md5_.c

md5_.c : $(SRCDIR)\md5.c
	+translate$E $** > $@

747
748
749
750
751
752
753






754
755
756
757
758
759
760
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772







+
+
+
+
+
+







	+translate$E $** > $@

$(OBJDIR)\report$O : report_.c report.h
	$(TCC) -o$@ -c report_.c

report_.c : $(SRCDIR)\report.c
	+translate$E $** > $@

$(OBJDIR)\robot$O : robot_.c robot.h
	$(TCC) -o$@ -c robot_.c

robot_.c : $(SRCDIR)\robot.c
	+translate$E $** > $@

$(OBJDIR)\rss$O : rss_.c rss.h
	$(TCC) -o$@ -c rss_.c

rss_.c : $(SRCDIR)\rss.c
	+translate$E $** > $@

999
1000
1001
1002
1003
1004
1005






1006
1007
1008
1009
1010
1011
1012
1013
1014

1015
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031

1032
1033







+
+
+
+
+
+








-
+

	+translate$E $** > $@

$(OBJDIR)\xfersetup$O : xfersetup_.c xfersetup.h
	$(TCC) -o$@ -c xfersetup_.c

xfersetup_.c : $(SRCDIR)\xfersetup.c
	+translate$E $** > $@

$(OBJDIR)\xsystem$O : xsystem_.c xsystem.h
	$(TCC) -o$@ -c xsystem_.c

xsystem_.c : $(SRCDIR)\xsystem.c
	+translate$E $** > $@

$(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 builtin_data.h VERSION.h
	 +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h color_.c:color.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.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 interwiki_.c:interwiki.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 patch_.c:patch.h path_.c:path.h piechart_.c:piechart.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.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 unversioned_.c:unversioned.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 xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR_extsrc)\pikchr.c:pikchr.h $(SRCDIR_extsrc)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR_extsrc)\cson_amalgamation.h
	 +makeheaders$E add_.c:add.h ajax_.c:ajax.h alerts_.c:alerts.h allrepo_.c:allrepo.h attach_.c:attach.h backlink_.c:backlink.h backoffice_.c:backoffice.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h builtin_.c:builtin.h bundle_.c:bundle.h cache_.c:cache.h capabilities_.c:capabilities.h captcha_.c:captcha.h cgi_.c:cgi.h chat_.c:chat.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h color_.c:color.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h cookies_.c:cookies.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h deltafunc_.c:deltafunc.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h dispatch_.c:dispatch.h doc_.c:doc.h encode_.c:encode.h etag_.c:etag.h event_.c:event.h export_.c:export.h extcgi_.c:extcgi.h file_.c:file.h fileedit_.c:fileedit.h finfo_.c:finfo.h foci_.c:foci.h forum_.c:forum.h fshell_.c:fshell.h fusefs_.c:fusefs.h fuzz_.c:fuzz.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h hname_.c:hname.h hook_.c:hook.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 interwiki_.c:interwiki.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 match_.c:match.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h patch_.c:patch.h path_.c:path.h piechart_.c:piechart.h pikchrshow_.c:pikchrshow.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h publish_.c:publish.h purge_.c:purge.h rebuild_.c:rebuild.h regexp_.c:regexp.h repolist_.c:repolist.h report_.c:report.h robot_.c:robot.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h security_audit_.c:security_audit.h setup_.c:setup.h setupuser_.c:setupuser.h sha1_.c:sha1.h sha1hard_.c:sha1hard.h sha3_.c:sha3.h shun_.c:shun.h sitemap_.c:sitemap.h skins_.c:skins.h smtp_.c:smtp.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h statrep_.c:statrep.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h terminal_.c:terminal.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 unversioned_.c:unversioned.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 xfer_.c:xfer.h xfersetup_.c:xfersetup.h xsystem_.c:xsystem.h zip_.c:zip.h $(SRCDIR_extsrc)\pikchr.c:pikchr.h $(SRCDIR_extsrc)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR_extsrc)\cson_amalgamation.h
	@copy /Y nul: headers
Changes to win/Makefile.mingw.
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
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







+



















+







  $(SRCDIR)/loadctrl.c \
  $(SRCDIR)/login.c \
  $(SRCDIR)/lookslike.c \
  $(SRCDIR)/main.c \
  $(SRCDIR)/manifest.c \
  $(SRCDIR)/markdown.c \
  $(SRCDIR)/markdown_html.c \
  $(SRCDIR)/match.c \
  $(SRCDIR)/md5.c \
  $(SRCDIR)/merge.c \
  $(SRCDIR)/merge3.c \
  $(SRCDIR)/moderate.c \
  $(SRCDIR)/name.c \
  $(SRCDIR)/patch.c \
  $(SRCDIR)/path.c \
  $(SRCDIR)/piechart.c \
  $(SRCDIR)/pikchrshow.c \
  $(SRCDIR)/pivot.c \
  $(SRCDIR)/popen.c \
  $(SRCDIR)/pqueue.c \
  $(SRCDIR)/printf.c \
  $(SRCDIR)/publish.c \
  $(SRCDIR)/purge.c \
  $(SRCDIR)/rebuild.c \
  $(SRCDIR)/regexp.c \
  $(SRCDIR)/repolist.c \
  $(SRCDIR)/report.c \
  $(SRCDIR)/robot.c \
  $(SRCDIR)/rss.c \
  $(SRCDIR)/schema.c \
  $(SRCDIR)/search.c \
  $(SRCDIR)/security_audit.c \
  $(SRCDIR)/setup.c \
  $(SRCDIR)/setupuser.c \
  $(SRCDIR)/sha1.c \
544
545
546
547
548
549
550

551
552
553
554
555
556
557
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560







+







  $(SRCDIR)/vfile.c \
  $(SRCDIR)/wiki.c \
  $(SRCDIR)/wikiformat.c \
  $(SRCDIR)/winfile.c \
  $(SRCDIR)/winhttp.c \
  $(SRCDIR)/xfer.c \
  $(SRCDIR)/xfersetup.c \
  $(SRCDIR)/xsystem.c \
  $(SRCDIR)/zip.c

EXTRA_FILES = \
  $(SRCDIR)/../extsrc/pikchr-worker.js \
  $(SRCDIR)/../extsrc/pikchr.js \
  $(SRCDIR)/../extsrc/pikchr.wasm \
  $(SRCDIR)/../skins/ardoise/css.txt \
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
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







+













+







  $(SRCDIR)/fossil.numbered-lines.js \
  $(SRCDIR)/fossil.page.brlist.js \
  $(SRCDIR)/fossil.page.chat.js \
  $(SRCDIR)/fossil.page.fileedit.js \
  $(SRCDIR)/fossil.page.forumpost.js \
  $(SRCDIR)/fossil.page.pikchrshow.js \
  $(SRCDIR)/fossil.page.pikchrshowasm.js \
  $(SRCDIR)/fossil.page.ticket.js \
  $(SRCDIR)/fossil.page.whistory.js \
  $(SRCDIR)/fossil.page.wikiedit.js \
  $(SRCDIR)/fossil.pikchr.js \
  $(SRCDIR)/fossil.popupwidget.js \
  $(SRCDIR)/fossil.storage.js \
  $(SRCDIR)/fossil.tabs.js \
  $(SRCDIR)/fossil.wikiedit-wysiwyg.js \
  $(SRCDIR)/graph.js \
  $(SRCDIR)/hbmenu.js \
  $(SRCDIR)/href.js \
  $(SRCDIR)/login.js \
  $(SRCDIR)/markdown.md \
  $(SRCDIR)/menu.js \
  $(SRCDIR)/merge.tcl \
  $(SRCDIR)/scroll.js \
  $(SRCDIR)/skin.js \
  $(SRCDIR)/sorttable.js \
  $(SRCDIR)/sounds/0.wav \
  $(SRCDIR)/sounds/1.wav \
  $(SRCDIR)/sounds/2.wav \
  $(SRCDIR)/sounds/3.wav \
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
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







+



















+







  $(OBJDIR)/loadctrl_.c \
  $(OBJDIR)/login_.c \
  $(OBJDIR)/lookslike_.c \
  $(OBJDIR)/main_.c \
  $(OBJDIR)/manifest_.c \
  $(OBJDIR)/markdown_.c \
  $(OBJDIR)/markdown_html_.c \
  $(OBJDIR)/match_.c \
  $(OBJDIR)/md5_.c \
  $(OBJDIR)/merge_.c \
  $(OBJDIR)/merge3_.c \
  $(OBJDIR)/moderate_.c \
  $(OBJDIR)/name_.c \
  $(OBJDIR)/patch_.c \
  $(OBJDIR)/path_.c \
  $(OBJDIR)/piechart_.c \
  $(OBJDIR)/pikchrshow_.c \
  $(OBJDIR)/pivot_.c \
  $(OBJDIR)/popen_.c \
  $(OBJDIR)/pqueue_.c \
  $(OBJDIR)/printf_.c \
  $(OBJDIR)/publish_.c \
  $(OBJDIR)/purge_.c \
  $(OBJDIR)/rebuild_.c \
  $(OBJDIR)/regexp_.c \
  $(OBJDIR)/repolist_.c \
  $(OBJDIR)/report_.c \
  $(OBJDIR)/robot_.c \
  $(OBJDIR)/rss_.c \
  $(OBJDIR)/schema_.c \
  $(OBJDIR)/search_.c \
  $(OBJDIR)/security_audit_.c \
  $(OBJDIR)/setup_.c \
  $(OBJDIR)/setupuser_.c \
  $(OBJDIR)/sha1_.c \
808
809
810
811
812
813
814

815
816
817
818
819
820
821
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829







+







  $(OBJDIR)/vfile_.c \
  $(OBJDIR)/wiki_.c \
  $(OBJDIR)/wikiformat_.c \
  $(OBJDIR)/winfile_.c \
  $(OBJDIR)/winhttp_.c \
  $(OBJDIR)/xfer_.c \
  $(OBJDIR)/xfersetup_.c \
  $(OBJDIR)/xsystem_.c \
  $(OBJDIR)/zip_.c

OBJ = \
 $(OBJDIR)/add.o \
 $(OBJDIR)/ajax.o \
 $(OBJDIR)/alerts.o \
 $(OBJDIR)/allrepo.o \
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
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







+



















+







 $(OBJDIR)/loadctrl.o \
 $(OBJDIR)/login.o \
 $(OBJDIR)/lookslike.o \
 $(OBJDIR)/main.o \
 $(OBJDIR)/manifest.o \
 $(OBJDIR)/markdown.o \
 $(OBJDIR)/markdown_html.o \
 $(OBJDIR)/match.o \
 $(OBJDIR)/md5.o \
 $(OBJDIR)/merge.o \
 $(OBJDIR)/merge3.o \
 $(OBJDIR)/moderate.o \
 $(OBJDIR)/name.o \
 $(OBJDIR)/patch.o \
 $(OBJDIR)/path.o \
 $(OBJDIR)/piechart.o \
 $(OBJDIR)/pikchrshow.o \
 $(OBJDIR)/pivot.o \
 $(OBJDIR)/popen.o \
 $(OBJDIR)/pqueue.o \
 $(OBJDIR)/printf.o \
 $(OBJDIR)/publish.o \
 $(OBJDIR)/purge.o \
 $(OBJDIR)/rebuild.o \
 $(OBJDIR)/regexp.o \
 $(OBJDIR)/repolist.o \
 $(OBJDIR)/report.o \
 $(OBJDIR)/robot.o \
 $(OBJDIR)/rss.o \
 $(OBJDIR)/schema.o \
 $(OBJDIR)/search.o \
 $(OBJDIR)/security_audit.o \
 $(OBJDIR)/setup.o \
 $(OBJDIR)/setupuser.o \
 $(OBJDIR)/sha1.o \
957
958
959
960
961
962
963

964
965
966
967
968
969
970
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981







+







 $(OBJDIR)/vfile.o \
 $(OBJDIR)/wiki.o \
 $(OBJDIR)/wikiformat.o \
 $(OBJDIR)/winfile.o \
 $(OBJDIR)/winhttp.o \
 $(OBJDIR)/xfer.o \
 $(OBJDIR)/xfersetup.o \
 $(OBJDIR)/xsystem.o \
 $(OBJDIR)/zip.o

APPNAME    = fossil.exe
APPTARGETS =

#### If the USE_WINDOWS variable exists, it is assumed that we are building
#    inside of a Windows-style shell; otherwise, it is assumed that we are
1249
1250
1251
1252
1253
1254
1255

1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274

1275
1276
1277
1278
1279
1280
1281
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294







+



















+







	$(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)/match_.c:$(OBJDIR)/match.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)/patch_.c:$(OBJDIR)/patch.h \
	$(OBJDIR)/path_.c:$(OBJDIR)/path.h \
	$(OBJDIR)/piechart_.c:$(OBJDIR)/piechart.h \
	$(OBJDIR)/pikchrshow_.c:$(OBJDIR)/pikchrshow.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)/publish_.c:$(OBJDIR)/publish.h \
	$(OBJDIR)/purge_.c:$(OBJDIR)/purge.h \
	$(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h \
	$(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h \
	$(OBJDIR)/repolist_.c:$(OBJDIR)/repolist.h \
	$(OBJDIR)/report_.c:$(OBJDIR)/report.h \
	$(OBJDIR)/robot_.c:$(OBJDIR)/robot.h \
	$(OBJDIR)/rss_.c:$(OBJDIR)/rss.h \
	$(OBJDIR)/schema_.c:$(OBJDIR)/schema.h \
	$(OBJDIR)/search_.c:$(OBJDIR)/search.h \
	$(OBJDIR)/security_audit_.c:$(OBJDIR)/security_audit.h \
	$(OBJDIR)/setup_.c:$(OBJDIR)/setup.h \
	$(OBJDIR)/setupuser_.c:$(OBJDIR)/setupuser.h \
	$(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h \
1310
1311
1312
1313
1314
1315
1316

1317
1318
1319
1320
1321
1322
1323
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337







+







	$(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)/xfer_.c:$(OBJDIR)/xfer.h \
	$(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.h \
	$(OBJDIR)/xsystem_.c:$(OBJDIR)/xsystem.h \
	$(OBJDIR)/zip_.c:$(OBJDIR)/zip.h \
	$(SRCDIR_extsrc)/pikchr.c:$(OBJDIR)/pikchr.h \
	$(SRCDIR_extsrc)/sqlite3.h \
	$(SRCDIR)/th.h \
	$(OBJDIR)/VERSION.h
	echo Done >$(OBJDIR)/headers

2000
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
2032
2033
2034
2035







+
+
+
+
+
+
+
+







$(OBJDIR)/markdown_html_.c:	$(SRCDIR)/markdown_html.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/markdown_html.c >$@

$(OBJDIR)/markdown_html.o:	$(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c

$(OBJDIR)/markdown_html.h:	$(OBJDIR)/headers

$(OBJDIR)/match_.c:	$(SRCDIR)/match.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/match.c >$@

$(OBJDIR)/match.o:	$(OBJDIR)/match_.c $(OBJDIR)/match.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/match.o -c $(OBJDIR)/match_.c

$(OBJDIR)/match.h:	$(OBJDIR)/headers

$(OBJDIR)/md5_.c:	$(SRCDIR)/md5.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/md5.c >$@

$(OBJDIR)/md5.o:	$(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/md5.o -c $(OBJDIR)/md5_.c

2152
2153
2154
2155
2156
2157
2158








2159
2160
2161
2162
2163
2164
2165
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195







+
+
+
+
+
+
+
+







$(OBJDIR)/report_.c:	$(SRCDIR)/report.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/report.c >$@

$(OBJDIR)/report.o:	$(OBJDIR)/report_.c $(OBJDIR)/report.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/report.o -c $(OBJDIR)/report_.c

$(OBJDIR)/report.h:	$(OBJDIR)/headers

$(OBJDIR)/robot_.c:	$(SRCDIR)/robot.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/robot.c >$@

$(OBJDIR)/robot.o:	$(OBJDIR)/robot_.c $(OBJDIR)/robot.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/robot.o -c $(OBJDIR)/robot_.c

$(OBJDIR)/robot.h:	$(OBJDIR)/headers

$(OBJDIR)/rss_.c:	$(SRCDIR)/rss.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/rss.c >$@

$(OBJDIR)/rss.o:	$(OBJDIR)/rss_.c $(OBJDIR)/rss.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/rss.o -c $(OBJDIR)/rss_.c

2488
2489
2490
2491
2492
2493
2494








2495
2496
2497
2498
2499
2500
2501
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539







+
+
+
+
+
+
+
+







$(OBJDIR)/xfersetup_.c:	$(SRCDIR)/xfersetup.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/xfersetup.c >$@

$(OBJDIR)/xfersetup.o:	$(OBJDIR)/xfersetup_.c $(OBJDIR)/xfersetup.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/xfersetup.o -c $(OBJDIR)/xfersetup_.c

$(OBJDIR)/xfersetup.h:	$(OBJDIR)/headers

$(OBJDIR)/xsystem_.c:	$(SRCDIR)/xsystem.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/xsystem.c >$@

$(OBJDIR)/xsystem.o:	$(OBJDIR)/xsystem_.c $(OBJDIR)/xsystem.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/xsystem.o -c $(OBJDIR)/xsystem_.c

$(OBJDIR)/xsystem.h:	$(OBJDIR)/headers

$(OBJDIR)/zip_.c:	$(SRCDIR)/zip.c $(TRANSLATE)
	$(TRANSLATE) $(SRCDIR)/zip.c >$@

$(OBJDIR)/zip.o:	$(OBJDIR)/zip_.c $(OBJDIR)/zip.h $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/zip.o -c $(OBJDIR)/zip_.c

2513
2514
2515
2516
2517
2518
2519

2520
2521
2522
2523




2524
2525
2526
2527
2528
2529
2530
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560


2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571







+


-
-
+
+
+
+







                 -DSQLITE_OMIT_DEPRECATED \
                 -DSQLITE_OMIT_PROGRESS_CALLBACK \
                 -DSQLITE_OMIT_SHARED_CACHE \
                 -DSQLITE_OMIT_LOAD_EXTENSION \
                 -DSQLITE_MAX_EXPR_DEPTH=0 \
                 -DSQLITE_ENABLE_LOCKING_STYLE=0 \
                 -DSQLITE_DEFAULT_FILE_FORMAT=4 \
                 -DSQLITE_ENABLE_DBSTAT_VTAB \
                 -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
                 -DSQLITE_ENABLE_FTS4 \
                 -DSQLITE_ENABLE_DBSTAT_VTAB \
                 -DSQLITE_ENABLE_FTS5 \
                 -DSQLITE_ENABLE_FTS5 \
                 -DSQLITE_ENABLE_MATH_FUNCTIONS \
                 -DSQLITE_ENABLE_PERCENTILE \
                 -DSQLITE_ENABLE_SETLK_TIMEOUT \
                 -DSQLITE_ENABLE_STMTVTAB \
                 -DSQLITE_HAVE_ZLIB \
                 -DSQLITE_ENABLE_DBPAGE_VTAB \
                 -DSQLITE_TRUSTED_SCHEMA=0 \
                 -DHAVE_USLEEP \
                 -DSQLITE_WIN32_NO_ANSI \
                 $(MINGW_OPTIONS) \
2541
2542
2543
2544
2545
2546
2547

2548
2549
2550
2551




2552
2553
2554
2555
2556
2557
2558
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591


2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602







+


-
-
+
+
+
+







                 -DSQLITE_OMIT_DEPRECATED \
                 -DSQLITE_OMIT_PROGRESS_CALLBACK \
                 -DSQLITE_OMIT_SHARED_CACHE \
                 -DSQLITE_OMIT_LOAD_EXTENSION \
                 -DSQLITE_MAX_EXPR_DEPTH=0 \
                 -DSQLITE_ENABLE_LOCKING_STYLE=0 \
                 -DSQLITE_DEFAULT_FILE_FORMAT=4 \
                 -DSQLITE_ENABLE_DBSTAT_VTAB \
                 -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
                 -DSQLITE_ENABLE_FTS4 \
                 -DSQLITE_ENABLE_DBSTAT_VTAB \
                 -DSQLITE_ENABLE_FTS5 \
                 -DSQLITE_ENABLE_FTS5 \
                 -DSQLITE_ENABLE_MATH_FUNCTIONS \
                 -DSQLITE_ENABLE_PERCENTILE \
                 -DSQLITE_ENABLE_SETLK_TIMEOUT \
                 -DSQLITE_ENABLE_STMTVTAB \
                 -DSQLITE_HAVE_ZLIB \
                 -DSQLITE_ENABLE_DBPAGE_VTAB \
                 -DSQLITE_TRUSTED_SCHEMA=0 \
                 -DHAVE_USLEEP \
                 -Dmain=sqlite3_shell \
                 -DSQLITE_SHELL_IS_UTF8=1 \
Changes to win/Makefile.msc.
309
310
311
312
313
314
315

316
317
318
319




320
321
322
323
324
325
326
309
310
311
312
313
314
315
316
317
318


319
320
321
322
323
324
325
326
327
328
329







+


-
-
+
+
+
+







                 /DSQLITE_OMIT_DEPRECATED \
                 /DSQLITE_OMIT_PROGRESS_CALLBACK \
                 /DSQLITE_OMIT_SHARED_CACHE \
                 /DSQLITE_OMIT_LOAD_EXTENSION \
                 /DSQLITE_MAX_EXPR_DEPTH=0 \
                 /DSQLITE_ENABLE_LOCKING_STYLE=0 \
                 /DSQLITE_DEFAULT_FILE_FORMAT=4 \
                 /DSQLITE_ENABLE_DBSTAT_VTAB \
                 /DSQLITE_ENABLE_EXPLAIN_COMMENTS \
                 /DSQLITE_ENABLE_FTS4 \
                 /DSQLITE_ENABLE_DBSTAT_VTAB \
                 /DSQLITE_ENABLE_FTS5 \
                 /DSQLITE_ENABLE_FTS5 \
                 /DSQLITE_ENABLE_MATH_FUNCTIONS \
                 /DSQLITE_ENABLE_PERCENTILE \
                 /DSQLITE_ENABLE_SETLK_TIMEOUT \
                 /DSQLITE_ENABLE_STMTVTAB \
                 /DSQLITE_HAVE_ZLIB \
                 /DSQLITE_ENABLE_DBPAGE_VTAB \
                 /DSQLITE_TRUSTED_SCHEMA=0 \
                 /DHAVE_USLEEP \
                 /DSQLITE_WIN32_NO_ANSI

334
335
336
337
338
339
340

341
342
343
344




345
346
347
348
349
350
351
337
338
339
340
341
342
343
344
345
346


347
348
349
350
351
352
353
354
355
356
357







+


-
-
+
+
+
+







                /DSQLITE_OMIT_DEPRECATED \
                /DSQLITE_OMIT_PROGRESS_CALLBACK \
                /DSQLITE_OMIT_SHARED_CACHE \
                /DSQLITE_OMIT_LOAD_EXTENSION \
                /DSQLITE_MAX_EXPR_DEPTH=0 \
                /DSQLITE_ENABLE_LOCKING_STYLE=0 \
                /DSQLITE_DEFAULT_FILE_FORMAT=4 \
                /DSQLITE_ENABLE_DBSTAT_VTAB \
                /DSQLITE_ENABLE_EXPLAIN_COMMENTS \
                /DSQLITE_ENABLE_FTS4 \
                /DSQLITE_ENABLE_DBSTAT_VTAB \
                /DSQLITE_ENABLE_FTS5 \
                /DSQLITE_ENABLE_FTS5 \
                /DSQLITE_ENABLE_MATH_FUNCTIONS \
                /DSQLITE_ENABLE_PERCENTILE \
                /DSQLITE_ENABLE_SETLK_TIMEOUT \
                /DSQLITE_ENABLE_STMTVTAB \
                /DSQLITE_HAVE_ZLIB \
                /DSQLITE_ENABLE_DBPAGE_VTAB \
                /DSQLITE_TRUSTED_SCHEMA=0 \
                /DHAVE_USLEEP \
                /Dmain=sqlite3_shell \
                /DSQLITE_SHELL_IS_UTF8=1 \
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
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







+



















+







        "$(OX)\loadctrl_.c" \
        "$(OX)\login_.c" \
        "$(OX)\lookslike_.c" \
        "$(OX)\main_.c" \
        "$(OX)\manifest_.c" \
        "$(OX)\markdown_.c" \
        "$(OX)\markdown_html_.c" \
        "$(OX)\match_.c" \
        "$(OX)\md5_.c" \
        "$(OX)\merge_.c" \
        "$(OX)\merge3_.c" \
        "$(OX)\moderate_.c" \
        "$(OX)\name_.c" \
        "$(OX)\patch_.c" \
        "$(OX)\path_.c" \
        "$(OX)\piechart_.c" \
        "$(OX)\pikchrshow_.c" \
        "$(OX)\pivot_.c" \
        "$(OX)\popen_.c" \
        "$(OX)\pqueue_.c" \
        "$(OX)\printf_.c" \
        "$(OX)\publish_.c" \
        "$(OX)\purge_.c" \
        "$(OX)\rebuild_.c" \
        "$(OX)\regexp_.c" \
        "$(OX)\repolist_.c" \
        "$(OX)\report_.c" \
        "$(OX)\robot_.c" \
        "$(OX)\rss_.c" \
        "$(OX)\schema_.c" \
        "$(OX)\search_.c" \
        "$(OX)\security_audit_.c" \
        "$(OX)\setup_.c" \
        "$(OX)\setupuser_.c" \
        "$(OX)\sha1_.c" \
502
503
504
505
506
507
508

509
510
511
512
513
514
515
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524







+







        "$(OX)\vfile_.c" \
        "$(OX)\wiki_.c" \
        "$(OX)\wikiformat_.c" \
        "$(OX)\winfile_.c" \
        "$(OX)\winhttp_.c" \
        "$(OX)\xfer_.c" \
        "$(OX)\xfersetup_.c" \
        "$(OX)\xsystem_.c" \
        "$(OX)\zip_.c" \
        "$(SRCDIR_extsrc)\pikchr.c"

EXTRA_FILES   = "$(SRCDIR)\..\extsrc\pikchr-worker.js" \
        "$(SRCDIR)\..\extsrc\pikchr.js" \
        "$(SRCDIR)\..\extsrc\pikchr.wasm" \
        "$(SRCDIR)\..\skins\ardoise\css.txt" \
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
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







+













+







        "$(SRCDIR)\fossil.numbered-lines.js" \
        "$(SRCDIR)\fossil.page.brlist.js" \
        "$(SRCDIR)\fossil.page.chat.js" \
        "$(SRCDIR)\fossil.page.fileedit.js" \
        "$(SRCDIR)\fossil.page.forumpost.js" \
        "$(SRCDIR)\fossil.page.pikchrshow.js" \
        "$(SRCDIR)\fossil.page.pikchrshowasm.js" \
        "$(SRCDIR)\fossil.page.ticket.js" \
        "$(SRCDIR)\fossil.page.whistory.js" \
        "$(SRCDIR)\fossil.page.wikiedit.js" \
        "$(SRCDIR)\fossil.pikchr.js" \
        "$(SRCDIR)\fossil.popupwidget.js" \
        "$(SRCDIR)\fossil.storage.js" \
        "$(SRCDIR)\fossil.tabs.js" \
        "$(SRCDIR)\fossil.wikiedit-wysiwyg.js" \
        "$(SRCDIR)\graph.js" \
        "$(SRCDIR)\hbmenu.js" \
        "$(SRCDIR)\href.js" \
        "$(SRCDIR)\login.js" \
        "$(SRCDIR)\markdown.md" \
        "$(SRCDIR)\menu.js" \
        "$(SRCDIR)\merge.tcl" \
        "$(SRCDIR)\scroll.js" \
        "$(SRCDIR)\skin.js" \
        "$(SRCDIR)\sorttable.js" \
        "$(SRCDIR)\sounds\0.wav" \
        "$(SRCDIR)\sounds\1.wav" \
        "$(SRCDIR)\sounds\2.wav" \
        "$(SRCDIR)\sounds\3.wav" \
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
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







+




















+







        "$(OX)\loadctrl$O" \
        "$(OX)\login$O" \
        "$(OX)\lookslike$O" \
        "$(OX)\main$O" \
        "$(OX)\manifest$O" \
        "$(OX)\markdown$O" \
        "$(OX)\markdown_html$O" \
        "$(OX)\match$O" \
        "$(OX)\md5$O" \
        "$(OX)\merge$O" \
        "$(OX)\merge3$O" \
        "$(OX)\moderate$O" \
        "$(OX)\name$O" \
        "$(OX)\patch$O" \
        "$(OX)\path$O" \
        "$(OX)\piechart$O" \
        "$(OX)\pikchr$O" \
        "$(OX)\pikchrshow$O" \
        "$(OX)\pivot$O" \
        "$(OX)\popen$O" \
        "$(OX)\pqueue$O" \
        "$(OX)\printf$O" \
        "$(OX)\publish$O" \
        "$(OX)\purge$O" \
        "$(OX)\rebuild$O" \
        "$(OX)\regexp$O" \
        "$(OX)\repolist$O" \
        "$(OX)\report$O" \
        "$(OX)\robot$O" \
        "$(OX)\rss$O" \
        "$(OX)\schema$O" \
        "$(OX)\search$O" \
        "$(OX)\security_audit$O" \
        "$(OX)\setup$O" \
        "$(OX)\setupuser$O" \
        "$(OX)\sha1$O" \
772
773
774
775
776
777
778

779
780
781
782
783
784
785
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799







+







        "$(OX)\vfile$O" \
        "$(OX)\wiki$O" \
        "$(OX)\wikiformat$O" \
        "$(OX)\winfile$O" \
        "$(OX)\winhttp$O" \
        "$(OX)\xfer$O" \
        "$(OX)\xfersetup$O" \
        "$(OX)\xsystem$O" \
        "$(OX)\zip$O" \
        "$(OX)\fossil.res"


!ifndef BASEAPPNAME
BASEAPPNAME = fossil
!endif
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
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







+




















+







	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)\match.obj" >> $@
	echo "$(OX)\md5.obj" >> $@
	echo "$(OX)\merge.obj" >> $@
	echo "$(OX)\merge3.obj" >> $@
	echo "$(OX)\moderate.obj" >> $@
	echo "$(OX)\name.obj" >> $@
	echo "$(OX)\patch.obj" >> $@
	echo "$(OX)\path.obj" >> $@
	echo "$(OX)\piechart.obj" >> $@
	echo "$(OX)\pikchr.obj" >> $@
	echo "$(OX)\pikchrshow.obj" >> $@
	echo "$(OX)\pivot.obj" >> $@
	echo "$(OX)\popen.obj" >> $@
	echo "$(OX)\pqueue.obj" >> $@
	echo "$(OX)\printf.obj" >> $@
	echo "$(OX)\publish.obj" >> $@
	echo "$(OX)\purge.obj" >> $@
	echo "$(OX)\rebuild.obj" >> $@
	echo "$(OX)\regexp.obj" >> $@
	echo "$(OX)\repolist.obj" >> $@
	echo "$(OX)\report.obj" >> $@
	echo "$(OX)\robot.obj" >> $@
	echo "$(OX)\rss.obj" >> $@
	echo "$(OX)\schema.obj" >> $@
	echo "$(OX)\search.obj" >> $@
	echo "$(OX)\security_audit.obj" >> $@
	echo "$(OX)\setup.obj" >> $@
	echo "$(OX)\setupuser.obj" >> $@
	echo "$(OX)\sha1.obj" >> $@
1021
1022
1023
1024
1025
1026
1027

1028
1029
1030
1031
1032
1033
1034
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051







+







	echo "$(OX)\vfile.obj" >> $@
	echo "$(OX)\wiki.obj" >> $@
	echo "$(OX)\wikiformat.obj" >> $@
	echo "$(OX)\winfile.obj" >> $@
	echo "$(OX)\winhttp.obj" >> $@
	echo "$(OX)\xfer.obj" >> $@
	echo "$(OX)\xfersetup.obj" >> $@
	echo "$(OX)\xsystem.obj" >> $@
	echo "$(OX)\zip.obj" >> $@
	echo $(LIBS) >> $@

"$(OBJDIR)\translate$E": "$(SRCDIR_tools)\translate.c"
	$(BCC) /Fe$@ /Fo$(@D)\ /Fd$(@D)\ $**

"$(OBJDIR)\makeheaders$E": "$(SRCDIR_tools)\makeheaders.c"
1207
1208
1209
1210
1211
1212
1213

1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226

1227
1228
1229
1230
1231
1232
1233
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







+













+







	echo "$(SRCDIR)\fossil.numbered-lines.js" >> $@
	echo "$(SRCDIR)\fossil.page.brlist.js" >> $@
	echo "$(SRCDIR)\fossil.page.chat.js" >> $@
	echo "$(SRCDIR)\fossil.page.fileedit.js" >> $@
	echo "$(SRCDIR)\fossil.page.forumpost.js" >> $@
	echo "$(SRCDIR)\fossil.page.pikchrshow.js" >> $@
	echo "$(SRCDIR)\fossil.page.pikchrshowasm.js" >> $@
	echo "$(SRCDIR)\fossil.page.ticket.js" >> $@
	echo "$(SRCDIR)\fossil.page.whistory.js" >> $@
	echo "$(SRCDIR)\fossil.page.wikiedit.js" >> $@
	echo "$(SRCDIR)\fossil.pikchr.js" >> $@
	echo "$(SRCDIR)\fossil.popupwidget.js" >> $@
	echo "$(SRCDIR)\fossil.storage.js" >> $@
	echo "$(SRCDIR)\fossil.tabs.js" >> $@
	echo "$(SRCDIR)\fossil.wikiedit-wysiwyg.js" >> $@
	echo "$(SRCDIR)\graph.js" >> $@
	echo "$(SRCDIR)\hbmenu.js" >> $@
	echo "$(SRCDIR)\href.js" >> $@
	echo "$(SRCDIR)\login.js" >> $@
	echo "$(SRCDIR)\markdown.md" >> $@
	echo "$(SRCDIR)\menu.js" >> $@
	echo "$(SRCDIR)\merge.tcl" >> $@
	echo "$(SRCDIR)\scroll.js" >> $@
	echo "$(SRCDIR)\skin.js" >> $@
	echo "$(SRCDIR)\sorttable.js" >> $@
	echo "$(SRCDIR)\sounds/0.wav" >> $@
	echo "$(SRCDIR)\sounds/1.wav" >> $@
	echo "$(SRCDIR)\sounds/2.wav" >> $@
	echo "$(SRCDIR)\sounds/3.wav" >> $@
1758
1759
1760
1761
1762
1763
1764






1765
1766
1767
1768
1769
1770
1771
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796







+
+
+
+
+
+







	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\markdown_html$O" : "$(OX)\markdown_html_.c" "$(OX)\markdown_html.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\markdown_html_.c"

"$(OX)\markdown_html_.c" : "$(SRCDIR)\markdown_html.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\match$O" : "$(OX)\match_.c" "$(OX)\match.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\match_.c"

"$(OX)\match_.c" : "$(SRCDIR)\match.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\md5$O" : "$(OX)\md5_.c" "$(OX)\md5.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\md5_.c"

"$(OX)\md5_.c" : "$(SRCDIR)\md5.c"
	"$(OBJDIR)\translate$E" $** > $@

1872
1873
1874
1875
1876
1877
1878






1879
1880
1881
1882
1883
1884
1885
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916







+
+
+
+
+
+







	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\report$O" : "$(OX)\report_.c" "$(OX)\report.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\report_.c"

"$(OX)\report_.c" : "$(SRCDIR)\report.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\robot$O" : "$(OX)\robot_.c" "$(OX)\robot.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\robot_.c"

"$(OX)\robot_.c" : "$(SRCDIR)\robot.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\rss$O" : "$(OX)\rss_.c" "$(OX)\rss.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\rss_.c"

"$(OX)\rss_.c" : "$(SRCDIR)\rss.c"
	"$(OBJDIR)\translate$E" $** > $@

2124
2125
2126
2127
2128
2129
2130






2131
2132
2133
2134
2135
2136
2137
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174







+
+
+
+
+
+







	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\xfersetup$O" : "$(OX)\xfersetup_.c" "$(OX)\xfersetup.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\xfersetup_.c"

"$(OX)\xfersetup_.c" : "$(SRCDIR)\xfersetup.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\xsystem$O" : "$(OX)\xsystem_.c" "$(OX)\xsystem.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\xsystem_.c"

"$(OX)\xsystem_.c" : "$(SRCDIR)\xsystem.c"
	"$(OBJDIR)\translate$E" $** > $@

"$(OX)\zip$O" : "$(OX)\zip_.c" "$(OX)\zip.h"
	$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\zip_.c"

"$(OX)\zip_.c" : "$(SRCDIR)\zip.c"
	"$(OBJDIR)\translate$E" $** > $@

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
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







+



















+







			"$(OX)\loadctrl_.c":"$(OX)\loadctrl.h" \
			"$(OX)\login_.c":"$(OX)\login.h" \
			"$(OX)\lookslike_.c":"$(OX)\lookslike.h" \
			"$(OX)\main_.c":"$(OX)\main.h" \
			"$(OX)\manifest_.c":"$(OX)\manifest.h" \
			"$(OX)\markdown_.c":"$(OX)\markdown.h" \
			"$(OX)\markdown_html_.c":"$(OX)\markdown_html.h" \
			"$(OX)\match_.c":"$(OX)\match.h" \
			"$(OX)\md5_.c":"$(OX)\md5.h" \
			"$(OX)\merge_.c":"$(OX)\merge.h" \
			"$(OX)\merge3_.c":"$(OX)\merge3.h" \
			"$(OX)\moderate_.c":"$(OX)\moderate.h" \
			"$(OX)\name_.c":"$(OX)\name.h" \
			"$(OX)\patch_.c":"$(OX)\patch.h" \
			"$(OX)\path_.c":"$(OX)\path.h" \
			"$(OX)\piechart_.c":"$(OX)\piechart.h" \
			"$(OX)\pikchrshow_.c":"$(OX)\pikchrshow.h" \
			"$(OX)\pivot_.c":"$(OX)\pivot.h" \
			"$(OX)\popen_.c":"$(OX)\popen.h" \
			"$(OX)\pqueue_.c":"$(OX)\pqueue.h" \
			"$(OX)\printf_.c":"$(OX)\printf.h" \
			"$(OX)\publish_.c":"$(OX)\publish.h" \
			"$(OX)\purge_.c":"$(OX)\purge.h" \
			"$(OX)\rebuild_.c":"$(OX)\rebuild.h" \
			"$(OX)\regexp_.c":"$(OX)\regexp.h" \
			"$(OX)\repolist_.c":"$(OX)\repolist.h" \
			"$(OX)\report_.c":"$(OX)\report.h" \
			"$(OX)\robot_.c":"$(OX)\robot.h" \
			"$(OX)\rss_.c":"$(OX)\rss.h" \
			"$(OX)\schema_.c":"$(OX)\schema.h" \
			"$(OX)\search_.c":"$(OX)\search.h" \
			"$(OX)\security_audit_.c":"$(OX)\security_audit.h" \
			"$(OX)\setup_.c":"$(OX)\setup.h" \
			"$(OX)\setupuser_.c":"$(OX)\setupuser.h" \
			"$(OX)\sha1_.c":"$(OX)\sha1.h" \
2281
2282
2283
2284
2285
2286
2287

2288
2289
2290
2291
2292
2293
2294
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334







+







			"$(OX)\vfile_.c":"$(OX)\vfile.h" \
			"$(OX)\wiki_.c":"$(OX)\wiki.h" \
			"$(OX)\wikiformat_.c":"$(OX)\wikiformat.h" \
			"$(OX)\winfile_.c":"$(OX)\winfile.h" \
			"$(OX)\winhttp_.c":"$(OX)\winhttp.h" \
			"$(OX)\xfer_.c":"$(OX)\xfer.h" \
			"$(OX)\xfersetup_.c":"$(OX)\xfersetup.h" \
			"$(OX)\xsystem_.c":"$(OX)\xsystem.h" \
			"$(OX)\zip_.c":"$(OX)\zip.h" \
			"$(SRCDIR_extsrc)\pikchr.c":"$(OX)\pikchr.h" \
			"$(SRCDIR_extsrc)\sqlite3.h" \
			"$(SRCDIR)\th.h" \
			"$(OX)\VERSION.h" \
			"$(SRCDIR_extsrc)\cson_amalgamation.h"
	@copy /Y nul: $@
Added win/build32.bat.



1
2
3
+
+
+
REM Based on /wiki/Release%20Build%20How-To
nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 FOSSIL_ENABLE_WINXP=1 OPTIMIZATIONS=4 clean fossil.exe
dumpbin /dependents fossil.exe
Added win/build64.bat.



1
2
3
+
+
+
REM Based on /wiki/Release%20Build%20How-To
nmake /f Makefile.msc FOSSIL_ENABLE_SSL=1 OPTIMIZATIONS=4 clean fossil.exe
dumpbin /dependents fossil.exe
Changes to www/aboutcgi.wiki.
65
66
67
68
69
70
71
72

73
74
75
76
77
78
79
65
66
67
68
69
70
71

72
73
74
75
76
77
78
79







-
+







    In this example: "timeline/four".
<tr><td>QUERY_STRING
    <td>The query string that follows the "?" in the URL, if there is one.
</table>

There are other CGI environment variables beyond those listed above.
Many Fossil servers implement the
[https://fossil-scm.org/home/test_env/two/three?abc=xyz|test_env]
[https://fossil-scm.org/home/test-env/two/three?abc=xyz|test-env]
webpage that shows some of the CGI environment
variables that Fossil pays attention to.

In addition to setting various CGI environment variables, if the HTTP
request contains POST content, then the web server relays the POST content
to standard input of the CGI script.

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
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







-
+











-
+







out what web page is being requested, generates that one web page,
then exits.

Usually, the webpage being requested is the first term of the
PATH_INFO environment variable.  (Exceptions to this rule are noted
in the sequel.)  For our example, the first term of PATH_INFO
is "timeline", which means that Fossil will generate
the [/help?cmd=/timeline|/timeline] webpage.
the [/help/www/timeline|/timeline] webpage.

With Fossil, terms of PATH_INFO beyond the webpage name are converted into
the "name" query parameter.  Hence, the following two URLs mean
exactly the same thing to Fossil:
<ol type='A'>
<li> [https://fossil-scm.org/home/info/c14ecc43]
<li> [https://fossil-scm.org/home/info?name=c14ecc43]
</ol>

In both cases, the CGI script is called "/fossil".  For case (A),
the PATH_INFO variable will be "info/c14ecc43" and so the
"[/help?cmd=/info|/info]" webpage will be generated and the suffix of
"[/help/www/info|/info]" webpage will be generated and the suffix of
PATH_INFO will be converted into the "name" query parameter, which
identifies the artifact about which information is requested.
In case (B), the PATH_INFO is just "info", but the same "name"
query parameter is set explicitly by the URL itself.

<h2>Serving Multiple Fossil Repositories From One CGI Script</h2>

280
281
282
283
284
285
286
287

288
289
290
291
292
293
294
280
281
282
283
284
285
286

287
288
289
290
291
292
293
294







-
+







source is seen as a space of key/value pairs which are loaded into an
internal property hash table.  The code that runs to generate the reply
can then reference various properties values.
Fossil does not care where the value of each property comes from (POST
content, cookies, or query parameters) only that the property exists
and has a value.</p></li>
<li><p>
The "[/help?cmd=ui|fossil ui]" and "[/help?cmd=server|fossil server]" commands
The "[/help/ui|fossil ui]" and "[/help/server|fossil server]" commands
are implemented using a simple built-in web server that accepts incoming HTTP
requests, translates each request into a CGI invocation, then creates a
separate child Fossil process to handle each request.  In other words, CGI
is used internally to implement "fossil ui/server".
<br><br>
SCGI is processed using the same built-in web server, just modified
to parse SCGI requests instead of HTTP requests.  Each SCGI request is
Changes to www/aboutdownload.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
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








-
+








-
+


-
+



-
+

-
+







<title>How The Fossil Download Page Works</title>

<h2>1.0 Overview</h2>

The [/uv/download.html|Download] page for the Fossil self-hosting
repository is implemented using [./unvers.wiki|unversioned files].
The "download.html" screen itself, and the various build products
are all stored as unversioned content.  The download.html page
uses XMLHttpRequest() to retrieve the [/help?cmd=/juvlist|/juvlist] webpage
uses XMLHttpRequest() to retrieve the [/help/www/juvlist|/juvlist] webpage
for a list of all unversioned files.  Javascript in the
[/uv/download.js?mimetype=text/plain|download.js] file (which is
sourced by "download.html") then figures out which unversioned files are
build products and paints appropriate icons on the displayed
download page.

Except, the "Source Tarball" download products are not stored as
unversioned files.  They are computed on-demand by the
[/help?cmd=/tarball|/tarball web page].
[/help/www/tarball|/tarball web page].

When a new version is generated, the developers use the
[/help?cmd=uv|fossil uv edit] command to make minor changes
[/help/uv|fossil uv edit] command to make minor changes
to the "[/uv/download.js?mimetype=text/plain|download.js]"
file so that it knows about the
new version number.  Then the developers run
the [/help?cmd=uv|fossil uv add] command for each
the [/help/uv|fossil uv add] command for each
build product.  Finally, the
[/help?cmd=uv|fossil uv sync] command is run to push all
[/help/uv|fossil uv sync] command is run to push all
the content up to servers.  All
[./selfhost.wiki|three self-hosting repositories] for Fossil
are updated automatically.

<h2>2.0 Details</h2>

The current text of the "download.html" and "download.js" files can
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
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







-
+













-
-
+
+







-
+



-
-
+
+







Fossil knows to add its standard header and footer information to the
document, making it look just like any other page.  See
"[./embeddeddoc.wiki|embedded documentation]" for further details on
how this &lt;div class='fossil-doc'&gt; markup works.

With each new release, the "releases" variable in the javascript on
the [/uv/download.js?mimetype=text/plain|download.js] page is
edited (using "[/help?cmd=uv|fossil uv edit download.js]") to add
edited (using "[/help/uv|fossil uv edit download.js]") to add
details of the release.

When the JavaScript in the "download.js" file runs, it requests
a listing of all unversioned content using the /juvlist URL.
([/juvlist|sample /juvlist output]).  The content of the download page is
constructed by matching unversioned files against regular expressions
in the "releases" variable.

Build products need to be constructed on different machines.  The precompiled
binary for Linux is compiled on Linux, the precompiled binary for Windows
is compiled on Windows11, and so forth.  After a new release is tagged,
the release manager goes around to each of the target platforms, checks
out the release and compiles it, then runs
[/help?cmd=uv|fossil uv add] for the build product followed by
[/help?cmd=uv|fossil uv sync] to push the new build product to the
[/help/uv|fossil uv add] for the build product followed by
[/help/uv|fossil uv sync] to push the new build product to the
[./selfhost.wiki|various servers].  This process is repeated for
each build product.

When older builds are retired from the download page, the
[/uv/download.js?mimetype=text/plain|download.js] page is again
edited to remove the corresponding entry from the "release" variable
and the edit is synced using
[/help?cmd=uv|fossil uv sync].  This causes the build products to
[/help/uv|fossil uv sync].  This causes the build products to
disappear from the download page immediately.  But those build products
are still taking up space in the unversioned content table of the
server repository.  To purge the obsolete build products, one or
more [/help?cmd=uv|fossil uv rm] commands are run, followed by
another [/help?cmd=uv|fossil uv sync].  It is important to purge
more [/help/uv|fossil uv rm] commands are run, followed by
another [/help/uv|fossil uv sync].  It is important to purge
obsolete build products since they take up a lot of space.
At [/repo-tabsize] you can see that the unversioned table takes up
a substantial fraction of the repository.

<h2>3.0 Security</h2>

Only users with the [/setup_ulist_notes|"y" permission] are allowed
Changes to www/alerts.md.
1
2
3
4
5
6
7
8

9
10
11

12
13
14
15
16
17
18
1
2
3
4
5
6
7

8
9
10
11
12
13
14
15
16
17
18
19







-
+



+







# Email Alerts

## Overview

Beginning with version 2.7, Fossil can send email messages to
subscribers to alert them to changes in the repository:

  *  New [checkins](/help?cmd=ci)
  *  New [checkins](/help/ci)
  *  [Ticket](./tickets.wiki) changes
  *  [Wiki](./wikitheory.wiki) page changes
  *  New and edited [forum](./forum.wiki) posts
  *  Users receiving [new permissions](./caps/index.md) (admins only)
  *  Announcements

Subscribers can elect to receive emails as soon as these events happen,
or they can receive a daily digest of the events instead.

Email alerts are sent by a [Fossil server](./server/), which must be
[set up](#quick) by the Fossil administrator to send email.
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
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







-
-
-
-
-
















-
-
-
+
+
+
+







system. To follow this guide, you will need a Fossil UI browser window
open to the [Admin → Notification](/setup_notification) Fossil UI screen
on the Fossil server that will be sending these email alerts, logged
in as a user with [**Admin** capability](./caps/ref.html#a). It is not possible to work on a
clone of the server's repository and push the configuration changes up
to that repo as an Admin user, [on purpose](#backup).

**Important:** Do not confuse that screen with Admin → Email-Server,
which sets up a different subsystem within Fossil. That feature is
related to this document's topic, but it is currently incomplete, so we
do not cover it at this time.

<a id="cd"></a>
You will also need a CLI window open with its working directory changed
to a checkout directory of the Fossil repository you are setting up to
send email.  If you don't `cd` to such a checkout directory first,
you'll need to add `-R /path/to/repo.fossil` to each `fossil` command
below to tell Fossil which repository you mean it to apply the command
to.

There are other prerequisites for email service, but since they vary
depending on the configuration you choose, we'll cover these inline
below.


<a id="quick"></a>
## Quick Email Service Setup

If you've already got a working Postfix, Exim, or Sendmail server on the
machine running your Fossil instance(s), and you aren't using Fossil's
`chroot` feature to wall Fossil off from the rest of the machine, it's
If you've already got a working OpenSMTPD, Postfix, Exim, Sendmail,
or similar server on the machine running your Fossil instance(s),
and you aren't using Fossil's [chroot jail feature](./chroot.md)
to wall Fossil off from the rest of the machine, it's
fairly simple to set up email alerts.

(Otherwise, skip [ahead](#advanced) to the sections on advanced email
service setup.)

This is our "quick setup" option even though setting up an SMTP mail
server is not trivial, because there are many other reasons to have such
391
392
393
394
395
396
397
398

399
400

401
402
403
404
405
406
407
388
389
390
391
392
393
394

395


396
397
398
399
400
401
402
403







-
+
-
-
+







it is running inside of a restrictive [chroot jail][cj] which is unable
to hand off messages to the local MTA directly.

When you configure a Fossil server this way, it adds outgoing email
messages to an SQLite database file.  A separate daemon process can then
extract those messages for further disposition.

Fossil includes a copy of [the daemon](/file/tools/email-sender.tcl)
Fossil uses a short TCL script (seen at [](/file/tools/email-sender.tcl))
used on `fossil-scm.org`: it is just a short Tcl script that
continuously monitors this database for new messages and hands any that
that continuously monitors this database for new messages and hands any that
it finds off to a local MTA using the same [pipe to MTA protocol](#pipe)
as above.

In this way, outbound email alerts escape the chroot jail without
requiring that we insert a separate MTA configuration inside that jail.
We only need to arrange that the same SQLite DB file be visible both
inside and outside the chroot jail, which we do by naming the database
517
518
519
520
521
522
523




524
525
526
527
528
529
530
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530







+
+
+
+








*  The Verified checkbox is initially unchecked for subscriber-only
   email addresses until the user clicks the link in the verification
   email. This checkbox lets the Fossil Admin user manually verify the
   user, such as in the case where the verification email message got
   lost.  Unchecking this box does not cause another verification email
   to be sent.

*  Admin users (only) may activate the "user elevation" subscription,
   which sends a notification when a user is created or is explicitly
   assigned permission they did not formerly have.

This screen also allows a Fossil Admin user to perform other activities
on behalf of a subscriber which they could do themselves, such as to
[unsubscribe](#unsub) them.


<a id="backup"></a>
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
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







-
-
-
+
+
+



-
-
-
-
-
+
+
+
+
+



-
-
+
+








This section collects the list of Fossil UI pages and CLI commands that
control the email alert system, some of which have not been mentioned so
far:

Commands:

   *  The [`alerts`](/help?cmd=alerts) command
   *  The [`test-alert`](/help?cmd=test-alert) command
   *  The [`test-add-alerts`](/help?cmd=test-add-alerts) command
   *  The [`alerts`](/help/alerts) command
   *  The [`test-alert`](/help/test-alert) command
   *  The [`test-add-alerts`](/help/test-add-alerts) command

Web pages available to users and subscribers:

   *  The [`/subscribe`](/help?cmd=/subscribe) page
   *  The [`/alerts`](/help?cmd=/alerts) page
   *  The [`/unsubscribe`](/help?cmd=/unsubscribe) page
   *  The [`/renew`](/help?cmd=/renew) page
   *  The [`/contact_admin`](/help?cmd=/contact_admin) page
   *  The [`/subscribe`](/help/www/subscribe) page
   *  The [`/alerts`](/help/www/alerts) page
   *  The [`/unsubscribe`](/help/www/unsubscribe) page
   *  The [`/renew`](/help/www/renew) page
   *  The [`/contact_admin`](/help/www/contact_admin) page

Administrator-only web pages:

   *  The [`/setup_notification`](/help?cmd=/setup_notification) page
   *  The [`/subscribers`](/help?cmd=/subscribers) page
   *  The [`/setup_notification`](/help/www/setup_notification) page
   *  The [`/subscribers`](/help/www/subscribers) page


<a id="design"></a>
## Design of Email Alerts

This section describes the low-level design of the email alert system in
Fossil.  This expands on the high-level administration focused material
Changes to www/antibot.wiki.
1
2
3
4
5



6
7


8
9
10
11
12















13
14
15
16
17
18
19
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


-
-
-
+
+
+


+
+





+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







<title>Defense Against Robots</title>

A typical Fossil website can have millions of pages, and many of
those pages (for example diffs and annotations and tarballs) can
be expensive to compute.
A typical Fossil website can have billions and billions of pages,
and many of those pages (for example diffs and annotations and tarballs) 
can be expensive to compute.
If a robot walks a Fossil-generated website,
it can present a crippling bandwidth and CPU load.
A "robots.txt" file can help, but in practice, most robots these
days ignore the robots.txt file, so it won't help much.

A Fossil website is intended to be used
interactively by humans, not walked by robots.  This article
describes the techniques used by Fossil to try to welcome human
users while keeping out robots.

<h2>Defenses Are Enabled By Default</h2>

In the latest implementations of Fossil, most robot defenses are
enabled by default.  You can probably get by with standing up a
public-facing Fossil instance in the default configuration.  But
you can also customize the defenses to serve your particular needs.

<h2>Customizing Anti-Robot Defenses</h2>

Admin users can configure robot defenses on the
"Robot Defense Settings" page (/setup_robot).
That page is accessible (to Admin users) from the default menu bar
by click on the "Admin" menu choice, then selecting the
"Robot-Defense" link from the list.

<h2>The Hyperlink User Capability</h2>

Every Fossil web session has a "user".  For random passers-by on the internet
(and for robots) that user is "nobody".  The "anonymous" user is also
available for humans who do not wish to identify themselves.  The difference
is that "anonymous" requires a login (using a password supplied via
34
35
36
37
38
39
40
41

42
43
44
45
46
47
48
51
52
53
54
55
56
57

58
59
60
61
62
63
64
65







-
+







A text message appears at the top of each page in this situation to
invite humans to log in as anonymous in order to activate hyperlinks.

But requiring a login, even an anonymous login, can be annoying.
Fossil provides other techniques for blocking robots which
are less cumbersome to humans.

<h2>Automatic Hyperlinks Based on UserAgent</h2>
<h2>Automatic Hyperlinks Based on UserAgent and Javascript</h2>

Fossil has the ability to selectively enable hyperlinks for users
that lack the <b>Hyperlink</b> capability based on their UserAgent string in the
HTTP request header and on the browsers ability to run Javascript.

The UserAgent string is a text identifier that is included in the header
of most HTTP requests that identifies the specific maker and version of
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
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
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
241
242
243
244
245
246
247
248
249
250
251
252

253
254
255
256
257
258
259
260
261
262
263







-
-
-
-
+
+
+
+
+





-
-
+
+













-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+


















+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
+










of the requester and so a malicious robot can forge a UserAgent
string that makes it look like a human.  But most robots want
to "play nicely" on the internet and are quite open
about the fact that they are a robot.  And so the UserAgent string
provides a good first-guess about whether or not a request originates
from a human or a robot.

In Fossil, under the Admin/Robot-Defense menu, there is a setting entitled
"<b>Enable hyperlinks based on User-Agent and/or Javascript</b>".
If this setting is set to "UserAgent only" or "UserAgent and Javascript",
and if the UserAgent string looks like a human and not a robot, then
The [/help/auto-hyperlink|auto-hyperlink] setting, shown as
"<b>Enable hyperlinks based on User-Agent and/or Javascript</b>" on
the Robot Defense Settings page, 
can be set to "UserAgent only" or "UserAgent and Javascript" or "off".
If the UserAgent string looks like a human and not a robot, then
Fossil will enable hyperlinks even if the <b>Hyperlink</b> capability
is omitted from the user permissions.  This setting gives humans easy
access to the hyperlinks while preventing robots
from walking the billions of pages on a typical Fossil site.

If the setting is "UserAgent only", then the hyperlinks are simply
enabled and that is all.  But if the setting is "UserAgent and Javascript",
If the setting is "UserAgent only" (2), then the hyperlinks are simply
enabled and that is all.  But if the setting is "UserAgent and Javascript" (1),
then the hyperlinks are not enabled directly.
Instead, the HTML code that is generated contains anchor tags ("&lt;a&gt;")
with "href=" attributes that point to [/honeypot] rather than the correct
link.  JavaScript code is added to the end of the page that goes back and
fills in the correct "href=" attributes of
the anchor tags with the true hyperlink targets, thus enabling the hyperlinks.
This extra step of using JavaScript to enable the hyperlink targets
is a security measure against robots that forge a human-looking
UserAgent string.  Most robots do not bother to run JavaScript and
so to the robot the empty anchor tag will be useless.  But all modern
web browsers implement JavaScript, so hyperlinks will show up
normally for human users.

<h2>Further Defenses</h2>

If the [/help/auto-hyperlink|"auto-hyperlink"] setting is (2)
Recently (as of this writing, in the spring of 2013) the Fossil server
on the SQLite website ([http://www.sqlite.org/src/]) has been hit repeatedly
by Chinese robots that use forged UserAgent strings to make them look
like normal web browsers and which interpret JavaScript.  We do not
believe these attacks to be nefarious since SQLite is public domain
and the attackers could obtain all information they ever wanted to
know about SQLite simply by cloning the repository.  Instead, we
believe these "attacks" are coming from "script kiddies".  But regardless
of whether or not malice is involved, these attacks do present
an unnecessary load on the server which reduces the responsiveness of
the SQLite website for well-behaved and socially responsible users.
For this reason, additional defenses against
robots have been put in place.

On the Admin/Robot-Defense page of Fossil, just below the
"<b>Enable hyperlinks using User-Agent and/or Javascript</b>"
setting, there are now two additional sub-settings that can be optionally
enabled to control hyperlinks.
"<b>Enable hyperlinks using User-Agent and/or Javascript</b>",
then there are now two additional sub-settings that control when
hyperlinks are enabled.

The first new sub-setting is a delay (in milliseconds) before setting
the "href=" attributes on anchor tags.  The default value for this
delay is 10 milliseconds.  The idea here is that a robots will try to
interpret the links on the page immediately, and will not wait for delayed
scripts to be run, and thus will never enable the true links.

The second sub-setting waits to run the
JavaScript that sets the "href=" attributes on anchor tags until after
at least one "mousedown" or "mousemove" event has been detected on the
&lt;body&gt; element of the page.  The thinking here is that robots will not be
simulating mouse motion and so no mouse events will ever occur and
hence the hyperlinks will never become enabled for robots.

See also [./loadmgmt.md|Managing Server Load] for a description
of how expensive pages can be disabled when the server is under heavy
load.

<h2>Do Not Allow Robot Access To Certain Pages</h2>

The [/help/robot-restrict|robot-restrict setting] is a comma-separated
list of GLOB patterns for pages for which robot access is prohibited.
The default value is:

<blockquote><pre>
timelineX,diff,annotate,fileage,file,finfo,reports,tree,hexdump,download
</pre></blockquote>

Each entry corresponds to the first path element on the URI for a
Fossil-generated page.  If Fossil does not know for certain that the
HTTP request is coming from a human, then any attempt to access one of
these pages brings up a javascript-powered captcha.  The user has to
click the accept button the captcha once, and that sets a cookie allowing
the user to continue surfing without interruption for 15 minutes or so
before being presented with another captcha.

Some path elements have special meanings:

  *  <b>timelineX &rarr;</b>
     This means a subset of /timeline/ pages that are considered
     "expensive".  The exact definition of which timeline pages are
     expensive and which are not is still the subject of active
     experimentation and is likely to change by the time you read this
     text.  The idea is that anybody (including robots) can see a timeline
     of the most recent changes, but timelines of long-ago change or that
     contain lists of file changes or other harder-to-compute values are
     prohibited.

  *  <b>zip &rarr;</b>
     The special "zip" keyword also matches "/tarball/" and "/sqlar/".

  *  <b>zipX &rarr;</b>
     This is like "zip" in that it restricts access to "/zip/", "/tarball"/
     and "/sqlar/" but with exceptions:<ol type="a">
     <li><p> If the [/help/robot-zip-leaf|robot-zip-leaf] setting is
             true, then tarballs of leaf check-ins are allowed.  This permits
             URLs that attempt to download the latest check-in on trunk or
             from a named branch, for example.
     <li><p> If a check-in has a tag that matches the GLOB list in
             [/help/robot-zip-tag|robot-zip-tag], then tarballs of that
             check-in are allowed.  This allow check-ins tagged with
             "release" or "allow-robots" (for example) to be downloaded
             without restriction.
     </ol>
     The "zipX" restriction is not in the default robot-restrict setting.
     This is something you might want to add, depending on your needs.

  *  <b>diff &rarr;</b>
     This matches /vdiff/ and /fdiff/ and /vpatch/ and any other page that
     is primarily about showing the difference between two check-ins or two
     file versioons.

  *  <b>annotate &rarr;</b>
     This also matches /blame/ and /praise/.

Other special keywords may be added in the future.

The default [/help/robot-restrict|robot-restrict]
setting has been shown in practice to do a good job of keeping 
robots from consuming all available CPU and bandwidth while will
still allowing humans access to the full power of the site without
having to be logged in.

One possible enhancement is to add "zipX" to the 
[/help/robot-restrict|robot-restrict] setting,
and enable [help?cmd=robot-zip-leaf|robot-zip-leaf]
and configure [help?cmd=robot-zip-tag|robot-zip-tag].
Do this if you find that robots downloading lots of
obscure tarballs is causing load issues on your site.

<h2>Anti-robot Exception RegExps</h2>

The [/help/robot-exception|robot-exception setting] under the name
of <b>Exceptions to anti-robot restrictions</b> is a list of
[/re_rules|regular expressions], one per line, that match
URIs that will bypass the captcha and allow robots full access.  The
intent of this setting is to allow automated build scripts
to download specific tarballs of project snapshots.

The recommended value for this setting allows robots to use URIs of the
following form:

<blockquote>
<b>https://</b><i>DOMAIN</i><b>/tarball/release/</b><i>HASH</i><b>/</b><i>NAME</i><b>.tar.gz</b>
</blockquote>

The <i>HASH</i> part of this URL can be any valid 
[./checkin_names.wiki|check-in name].  The link works as long as that
check-in is tagged with the "release" symbolic tag.  In this way,
robots are permitted to download tarballs (and ZIP archives) of official
releases, but not every intermediate check-in between releases.  Humans
who are willing to click the captcha can still download whatever they
want, but robots are blocked by the captcha.  This prevents aggressive
robots from downloading tarballs of every historical check-in of your
project, once per day, which many robots these days seem eager to do.

For example, on the Fossil project itself, this URL will work, even for
robots:

<blockquote>
https://fossil-scm.org/home/tarball/release/version-2.27/fossil-scm.tar.gz
</blockquote>

But the next URL will not work for robots because check-in 3bbd18a284c8bd6a
is not tagged as a "release":

<blockquote>
https://fossil-scm.org/home/tarball/release/3bbd18a284c8bd6a/fossil-scm.tar.gz
</blockquote>

The second URL will work for humans, just not robots.

<h2>The Ongoing Struggle</h2>

Fossil currently does a very good job of providing easy access to humans
Fossil currently does a good job of providing easy access to humans
while keeping out troublesome robots.  However, robots
continue to grow more sophisticated, requiring ever more advanced
defenses.  This "arms race" is unlikely to ever end.  The developers of
Fossil will continue to try improve the robot defenses of Fossil so
check back from time to time for the latest releases and updates.

Readers of this page who have suggestions on how to improve the robot
defenses in Fossil are invited to submit your ideas to the Fossil Users
forum:
[https://fossil-scm.org/forum].
Changes to www/backoffice.md.
32
33
34
35
36
37
38
39
40


41
42
43

44
45
46
47
48
49
50
32
33
34
35
36
37
38


39
40
41
42

43
44
45
46
47
48
49
50







-
-
+
+


-
+







request.  After each webpage is generated, Fossil checks to see if any
backoffice work needs to be done. If there is work to do, and no other
process is already assigned to do the work, then a new backoffice process
is started to do the work.

This happens for every webpage, regardless of how that webpage is launched,
and regardless of the purpose of the webpage.  This also happens on the
server for "[fossil sync](/help?cmd=sync)" and
[fossil clone](/help?cmd=clone)" commands which are implemented as
server for "[fossil sync](/help/sync)" and
[fossil clone](/help/clone)" commands which are implemented as
web requests - albeit requests that the human user never sees.
Web requests can arrive at the Fossil server via direct TCP/IP (for example
when Fossil is started using commands like "[fossil server](/help?cmd=server)")
when Fossil is started using commands like "[fossil server](/help/server)")
or via [CGI](./server/any/cgi.md) or
[SCGI](./server/any/scgi.md) or via SSH.
A backoffice process might be started regardless of the origin of the
request.

The backoffice is not a daemon.  Each backoffice process runs for a short
while and then exits.  This helps keep Fossil easy to manage, since there
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
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







-
+










-
+







some system - OpenBSD in particular.  We still do not understand why
this is.  (If you have insights, please share them on the
[Fossil Forum](https://fossil-scm.org/forum) so that we can perhaps
fix the problem.)  For now, the backoffice must be run manually
on OpenBSD systems.

To set up fully-manual backoffice, first disable the automatic backoffice
using the "[backoffice-disable](/help?cmd=backoffice-disable)" setting.
using the "[backoffice-disable](/help/backoffice-disable)" setting.

    fossil setting backoffice-disable on

Then arrange to invoke the backoffice separately using a command
like this:

    fossil backoffice --poll 30 _REPOSITORY-LIST_

Multiple repositories can be named.  This one command will handle
launching the backoffice for all of them.  There are additional useful
command-line options.  See the "[fossil backoffice](/help?cmd=backoffice)"
command-line options.  See the "[fossil backoffice](/help/backoffice)"
documentation for details.

The backoffice processes run manually using the "fossil backoffice"
command do not normally use a lease.  That means that if you run the
"fossil backoffice" command with --poll and you forget to disable
automatic backoffice by setting the "backoffice-disable" flag, then
you might have one backoffice running due to a command and another due
Changes to www/backup.md.
66
67
68
69
70
71
72
73

74
75
76
77
78
79
80
66
67
68
69
70
71
72

73
74
75
76
77
78
79
80







-
+









#### <a id="other-cfg"></a> Others

A repo’s URL aliases, [interwiki configuration](./interwiki.md), and
[ticket customizations](./custom_tcket.wiki) also do not normally sync.

[cfg]: /help?cmd=configuration
[cfg]: /help/configuration



## <a id="private"></a> Private Branches

The very nature of Fossil’s [private branch feature][pbr] ensures that
remote clones don’t get a copy of those branches. Normally this is
287
288
289
290
291
292
293
294

295
296
297
298
299
300
301
302
303
304
287
288
289
290
291
292
293

294
295
296
297
298
299
300
301
302
303
304







-
+










pipe the decrypted SQL dump into `fossil sql`, because on startup, Fossil
normally goes looking for tables created by `fossil init`, and it won’t
find them in a newly-created repo DB. We get around this by passing
the `--no-repository` flag, which suppresses this behavior. Doing it
this way saves you from needing to go and build a matching version of
`sqlite3` just to restore the backup.

[bu]:    /help?cmd=backup
[bu]:    /help/backup
[grcp]:  https://www.grc.com/passwords.htm
[hb]:    https://brew.sh
[hbul]:  https://docs.brew.sh/FAQ#what-does-keg-only-mean
[lz4]:   https://lz4.github.io/lz4/
[pbr]:   ./private.wiki
[rint]:  https://www.random.org/integers/?num=1&min=100000&max=1000000&col=5&base=10&format=html&rnd=new
[Setup]: ./caps/admin-v-setup.md#apsu
[shun]:  ./shunning.wiki
[tkt]:   ./tickets.wiki
[uv]:    ./unvers.wiki
Changes to www/blame.wiki.
1
2
3
4
5
6
7
8
9
10






11
12
13
14
15
16
17
1
2
3
4






5
6
7
8
9
10
11
12
13
14
15
16
17




-
-
-
-
-
-
+
+
+
+
+
+







<title>The Annotate Algorithm</title>

<h2>1.0 Introduction</h2>

The [/help?cmd=annotate|fossil annotate],
[/help?cmd=blame|fossil blame], and
[/help?cmd=praise|fossil praise] commands, and the
[/help?cmd=/annotate|/annotate],
[/help?cmd=/blame|/blame], and
[/help?cmd=/praise|/praise] web pages are all used to show the most
The [/help/annotate|fossil annotate],
[/help/blame|fossil blame], and
[/help/praise|fossil praise] commands, and the
[/help/www/annotate|/annotate],
[/help/www/blame|/blame], and
[/help/www/praise|/praise] web pages are all used to show the most
recent check-in that modified each line of a particular file.
This article overviews the algorithm used to compute the annotation
for a file in Fossil.

<h2>2.0 Algorithm</h2>

<ol type='1'>
43
44
45
46
47
48
49
50

51
52
53
54
55
56
57
58
59
60
61
62
43
44
45
46
47
48
49

50
51
52
53
54
55
56
57
58
59
60
61
62







-
+













<h2>3.0 Discussion and Notes</h2>

The time-consuming part of this algorithm is step 6b - computing the
diff from all historical versions of the file to the version of the file
under analysis.  For a large file that has many historical changes, this
can take several seconds.  For this reason, the default
[/help?cmd=/annotate|/annotate] webpage only shows those lines that were
[/help/www/annotate|/annotate] webpage only shows those lines that were
changed by the 20 most recent modifications to the file.  This allows
the loop on step 6 to terminate after only 19 diffs instead of the hundreds
or thousands of diffs that might be required for a frequently modified file.

As currently implemented (as of 2015-12-12) the annotate algorithm does not
follow files across name changes.  File name change information is
available in the database, and so the algorithm could be enhanced to follow
files across name changes by modifications to step 3.

Step 2 is interesting in that it is
[/artifact/6cb824a0417?ln=196-201 | implemented] using a
[https://www.sqlite.org/lang_with.html#recursivecte|recursive common table expression].
Changes to www/blockchain.md.
206
207
208
209
210
211
212
213

214
215
216
217
218
219
220
206
207
208
209
210
211
212

213
214
215
216
217
218
219
220







-
+








This much is certain: Fossil is definitely not a cryptocurrency. Whether
this makes it “not a blockchain” is a subjective matter.

[arh]:  ./hooks.md
[bse]:  https://www.researchgate.net/publication/311572122_What_is_Blockchain_a_Gentle_Introduction
[caps]: ./caps/
[cs]:   /help?cmd=clearsign
[cs]:   /help/clearsign
[dboc]: https://en.wikipedia.org/wiki/Debasement
[dsig]: https://en.wikipedia.org/wiki/Digital_signature
[fb]:   ./branching.wiki
[GPG]:  https://gnupg.org/
[PGP]:  https://www.openpgp.org/
[PII]:  https://en.wikipedia.org/wiki/Personal_data
[PKI]:  https://en.wikipedia.org/wiki/Public_key_infrastructure
276
277
278
279
280
281
282
283

284
285
286
287
288
289
290
276
277
278
279
280
281
282

283
284
285
286
287
288
289
290







-
+







[ctap]:   ./cap-theorem.md#ap
[ctca]:   ./cap-theorem.md#ca
[ctcp]:   ./cap-theorem.md#cp
[cap]:    https://en.wikipedia.org/wiki/CAP_theorem
[dlt]:    https://en.wikipedia.org/wiki/Distributed_ledger
[DVCS]:   https://en.wikipedia.org/wiki/Distributed_version_control
[fc]:     https://en.wikipedia.org/wiki/Fiat_money
[purge]:  /help?cmd=purge
[purge]:  /help/purge
[shun]:   ./shunning.wiki


<a id="dpc"></a>
## Distributed Partial Consensus

If we can’t get DLT, can we at least get some kind of distributed
333
334
335
336
337
338
339
340

341
342
343
344
345
346
347
333
334
335
336
337
338
339

340
341
342
343
344
345
346
347







-
+







would mean that querying whether a given commit is part of the
“blockchain” would be as simple as going down the list of servers and
sending each an HTTP GET `/info` query for the artifact ID, concluding
that the commit is legitimate once you get enough HTTP 200 status codes back. All of this is
hypothetical, because Fossil doesn’t do this today.

[AGI]:  https://en.wikipedia.org/wiki/Artificial_general_intelligence
[rcks]: /help?cmd=repo-cksum
[rcks]: /help/repo-cksum



<a id="anon"></a>
## Anonymity

Many blockchain based technologies go to extraordinary lengths to
445
446
447
448
449
450
451
452

453
454
455


456
457
458
459
460
461
462
445
446
447
448
449
450
451

452
453


454
455
456
457
458
459
460
461
462







-
+

-
-
+
+







on their scores under these metrics. We may well prefer to use the fork
of a software program that took *less* effort, being smaller, more
self-contained, and with a smaller attack surface.


[alert]: ./alerts.md
[capi]:  ./caps/ref.html#i
[mrep]:  /help?cmd=remote
[mrep]:  /help/remote
[scost]: https://en.wikipedia.org/wiki/Software_development_effort_estimation
[scrub]: /help?cmd=scrub
[sreg]:  /help?cmd=self-register
[scrub]: /help/scrub
[sreg]:  /help/self-register


# Conclusion

This author believes it is technologically indefensible to call Fossil a
“blockchain” in any sense likely to be understood by a majority of those
you’re communicating with. Using a term in a nonstandard way just because you can
Changes to www/branching.wiki.
73
74
75
76
77
78
79
80

81
82
83
84
85
86
87
73
74
75
76
77
78
79

80
81
82
83
84
85
86
87







-
+







works. This is also how Fossil works in autosync mode.

But perhaps Bob is off-network when he does his commit, so he has no way
of knowing that Alice has already committed her changes.  Or, it could
be that Bob has turned off "autosync" mode in Fossil.  Or, maybe Bob
just doesn't want to merge in Alice's changes before he has saved his
own, so he forces the commit to occur using the "--allow-fork" option to
the <b>[/help?cmd=commit | fossil commit]</b> command.  For any of these
the <b>[/help/commit | fossil commit]</b> command.  For any of these
reasons, two commits against check-in 2 have occurred, so the DAG now
has two leaves.

In such a condition, a person working with this repository has a
dilemma: which version of the project is the "latest" in the sense of
having the most features and the most bug fixes?  When there is more
than one leaf in the graph, you don't really know, which is why we
109
110
111
112
113
114
115
116

117
118
119
120
121
122
123
109
110
111
112
113
114
115

116
117
118
119
120
121
122
123







-
+







with "--allow-fork".  (Prime example: trunk.) The inverse case —
intentional forks on short-lived single-developer branches — is far
easier to justify, since presumably the lone developer is never confused
about why there are two or more leaves on that branch. Further
justifications for intentional forking are [#forking | given below].

Let us return to Figure 2. To resolve such situations before they can
become a real problem, Alice can use the <b>[/help?cmd=merge | fossil
become a real problem, Alice can use the <b>[/help/merge | fossil
merge]</b> command to merge Bob's changes into her local copy of
check-in 3. Without arguments, that command merges all leaves on the
current branch. Alice can then verify that the merge is sensible and if
so, commit the results as check-in 5.  This results in a DAG as shown in
Figure 3.

<verbatim type="pikchr center toggle">
347
348
349
350
351
352
353
354

355
356
357
358
359
360
361
347
348
349
350
351
352
353

354
355
356
357
358
359
360
361







-
+







    <li><p id="automation">You've automated Fossil, so you use
    <b>fossil commit --allow-fork</b> commands to prevent Fossil from
    refusing the check-in simply because it would create a fork.
    <br><br>
    If you are writing such a tool — e.g. a shell script to make
    multiple manipulations on a Fossil repo — it's better to make it
    smart enough to detect this condition and cope with it, such as
    by making a call to <b>[/help?cmd=update | fossil update]</b>
    by making a call to <b>[/help/update | fossil update]</b>
    and checking for a merge conflict. That said, if the alternative is
    losing information, you may feel justified in creating forks that an
    interactive user must later manually clean up with <b>fossil merge</b>
    commands.</p></li>
</ol>

That leaves only one case where we can recommend use of "--allow-fork"
598
599
600
601
602
603
604
605

606
607
608
609
610
611
612
598
599
600
601
602
603
604

605
606
607
608
609
610
611
612







-
+







    on a different branch at the time Alan made check-in 3, so Fossil
    sees that as the tip at the time she switches her working directory
    to that branch with a <b>fossil update $BRANCH</b> command. (There is an
    implicit autosync in that command, if the option was enabled at the
    time of the update.)</p></li>

    <li><p>The same thing, only in a fresh checkout directory with a
    <b>[/help?cmd=open | fossil open $REPO $BRANCH]</b> command.</p></li>
    <b>[/help/open | fossil open $REPO $BRANCH]</b> command.</p></li>

    <li><p>Alan makes his check-in 3 while Betty has check-in 1 or 2 as
    the tip in her local clone, but because she's working with an
    autosync'd connection to the same upstream repository as Alan, on
    attempting what will become check-in 4, she gets the "would fork"
    message from <b>fossil commit</b>, so she dutifully updates her clone
    and tries again, moving her work to be a child of the new tip,
749
750
751
752
753
754
755
756

757
758
759
760
761
762
763
764
765
766
767
749
750
751
752
753
754
755

756
757
758
759
760
761
762
763
764
765
766
767







-
+












This fact is helpful because it means you can reuse branch names, which
is especially useful with utility branches.  There are several of these
in the SQLite and Fossil repositories: "broken-build," "declined,"
"mistake," etc. As you might guess from these names, such branch names
are used in renaming the tip of one branch to shunt it off away from the
mainline of that branch due to some human error. (See
<b>[/help?cmd=amend | fossil
<b>[/help/amend | fossil
amend]</b> and the Fossil UI check-in amendment features.) This is a
workaround for Fossil's [./shunning.wiki|normal inability to forget
history]: we usually don't want to actually <i>remove</i> history, but
would like to sometimes set some of it aside under a new label.

Because some VCSes can't cope with duplicate branch names, Fossil
collapses such names down on export using the same time stamp based
arbitration logic, so that only the branch with the newest check-in gets
the branch name in the export.

All of the above is true of tags in general, not just branches.
Changes to www/build.wiki.
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
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







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-



-
+

+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







want to make minor edits to Makefile.classic to configure the build for your
system.</li>

<li><i>MinGW / MinGW-w64</i> → The best-supported path is to build
via the MinGW specific Makefile under a POSIX build of GNU make:
"<b>make -f win/Makefile.mingw</b>".</li>

There is limited support for building under MinGW's native Windows port
of GNU Make instead by defining the <tt>USE_WINDOWS=1</tt> variable, but
it's better to build under MSYS, Cygwin, or WSL on Windows since this
mode doesn't take care of cases such as the "openssl" target, which
depends on <tt>sed</tt>. We've gone as far down this path as is
practical short of breaking cross-compilation under Linux, macOS, and so
forth, as we'd have to do to make everything work under
<tt>cmd.exe</tt>.

Unless you're building under MSYS where commands like "<tt>gcc</tt>"
give MinGW's GCC and not some other version, you will need to make minor
edits to win/Makefile.mingw to configure the cross-compilation
environment. It should suffice to switch to one of the predefined
<tt>PREFIX</tt> values, causing the build to be done using
"<tt>x86_64-w64-mingw32-gcc</tt>" for example, yielding a 64-bit native
Windows binary.

To enable the native [./th1.md#tclEval | Tcl integration feature], use a
command line like the following (all on one line):

<b>make -f win/Makefile.mingw FOSSIL_ENABLE_TCL=1 FOSSIL_ENABLE_TCL_STUBS=1 FOSSIL_ENABLE_TCL_PRIVATE_STUBS=1</b>
<pre>make -f win/Makefile.mingw FOSSIL_ENABLE_TCL=1 FOSSIL_ENABLE_TCL_STUBS=1 FOSSIL_ENABLE_TCL_PRIVATE_STUBS=1</pre>

<li><i>MSYS2 / Cygwin</i> → This is something of a hybrid between
Alternatively, running <b>./configure</b> under MSYS should give a
suitable top-level Makefile. However, options passed to configure that are
not applicable on Windows may cause the configuration or compilation to fail
(e.g. fusefs, internal-sqlite, etc).
options "a" and "c" above: it configures and builds
<code>fossil.exe</code> much as on Linux, but you get a native Windows
executable out at the end. The primary downside is that this type of
executable can become confused when attempting to interoperate with
fully native Windows EXEs by making assumptions that only hold true when
all elements are running under the Cygwin/MSYS environment. The MSVC and
MinGW options do not have this limitation.

Even so, there is value to Linux/Unix natives in having this hybrid
while off in Windows-land. The simpler of the two paths is MSYS2, since
it lets you install the necessary prerequisites in a single command
after installing the base environment:

<pre>pacman -sS gcc make openssl-devel zlib-devel</pre>

The equivalent in Cygwin's <code>setup.exe</code> requires stepping
through the GUI package chooser, and then if you miss one of the
prereqs, going all the way back through it again until you get it right.

<li><i>MSVC</i> → Use the MSVC makefile.</li>

<em>NB:</em> Run the following <code>nmake</code> commands from a "x64 Native
Tools Command Prompt"; <code>buildmsvc.bat</code> is able to automatically load
the build tools (x64 by default, pass "x86" as the first argument to use the
x86 tools), so it can be called from a normal command prompt.
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
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







-
+




-






-
-
+
-
-
-
-

-
-
-
+
-
-
+


-
-
-
-
+





-
+






-
+








This feature is primarily intended for fossil's developers and may
change at any time. It is only known to work on Linux systems and has
been seen to work on x86/64 and ARM.

Fossil has builtin support for processing specific features using
<tt>libfuzzer</tt>. The features which can be tested this way are
found in the help text for the [/help?cmd=test-fuzz|test-fuzz
found in the help text for the [/help/test-fuzz|test-fuzz
command].

Fuzzing requires:

  *  Customizing the build of fossil a small bit.
  *  The clang C compiler.
  *  libfuzzer. On Ubuntu-derived systems, it can be installed with
     <tt>apt install libfuzzer-XYZ</tt>, where XYZ is a version number
     (several versions may be available on any given system)


First, modify the top-level <tt>Makefile.in</tt>:

Compile as follows:
  *  Extend the <tt>TCCFLAGS</tt> variable with: <tt>-fsanitize=fuzzer
   -DFOSSIL_FUZZ</tt> (and see [/finfo/src/fuzz.c | src/fuzz.c] for
   more options).
  *  Rename <tt>APPNAME</tt> from <tt>fossil</tt> to <tt>fossil-fuzz</tt>.

Then rebuild:

<pre></code>$ make clean
<pre><code>make clean
$ ./configure CC=/path/to/clang
$ make
make TCCFLAGS='-DFOSSIL_FUZZ -fsanitize=fuzzer,address,undefined -O0 -g' CC=clang
</code></pre>

If clang is your default compiler, the <tt>CC</tt> configure option is
not required.

The resulting <tt>fossil-fuzz</tt> binary differs from the standard
The resulting <tt>fossil</tt> binary differs from the standard
one primarily in that it runs the <tt>test-fuzz</tt> command by
default. It needs to be told what to fuzz and needs to be given a
directory of input files to seed the fuzzer with:


<pre></code>$ mkdir cases
<pre><code>$ mkdir cases
  # Copy input files into ./cases. e.g. when fuzzing the markdown
  # processor, copy any to-be-tested .md files into that directory.
  # Then start the fuzzer:
$ ./fossil-fuzz --fuzztype markdown cases
</code></pre>

As it works, it writes its mutated test files into the test-input
As it works, it writes its mutated test files into the "cases"
directory, each one named in the form of a hash. When it finds a
problem it will produce a stack trace for the offending code, will
output the name of the file which triggered the crash (named
<tt>cases/SOME_HASH</tt>) and may, depending on the nature of the
problem, produce a file named <tt>crash-SOMETHING</tt>.  In theory the
crash file can be fed directly back into the fuzzer to reproduce the
problem:
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
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







+
+
-
+
















-
+







      to give JS code access to the API exported by the WASM file.

When a new version of <tt>extsrc/pikchr.c</tt> is installed, the
files <tt>pikchr.{js,wasm}</tt> will need to be recompiled to account
for that. Running <tt>make wasm</tt> will, if the build is set up for
the emsdk, recompile those:

<pre><code>$ rm extsrc/pikchr.{js,wasm}
# ^^^^ that rm has proven necessary in order to ensure rebuilds
<pre><code>$ make wasm
$ make wasm
./tools/emcc.sh -o extsrc/pikchr.js ...
$ ls -la extsrc/pikchr.{js,wasm}
-rw-rw-r-- 1 stephan stephan 17263 Jun  8 03:59 extsrc/pikchr.js
-rw-rw-r-- 1 stephan stephan 97578 Jun  8 03:59 extsrc/pikchr.wasm
</code></pre>

<div class="sidebar">If that fails with a message along the lines of
“<code>setting `EXPORTED_RUNTIME_METHODS` expects `<class 'list'>` but
got `<class 'str'>`</code>” then the emcc being invoked is too old: emcc
changed the format of list-type arguments at some point.  The required
minimum version is unknown, but any SDK version from May 2022 or later
"should" (as of this writing) suffice. Any older version may or may not
work.</div>

After that succeeds, we need to run the normal build so that those
generated files can be compiled in to the fossil binary, accessible
via the [/help?cmd=/builtin|/builtin page]:
via the [/help/www/builtin|/builtin page]:

<pre><code>$ make</code></pre>

Before checking in those newly-built files, they need to be tested by
running the [/pikchrshow] page. If that page loads, the compilation
process fundamentally worked (a load failure will be made obvious to
the viewer). If it fails to load then the browser's dev tools console
Changes to www/caps/admin-v-setup.md.
259
260
261
262
263
264
265
266
267
268



269
270
271
272
273
274
275
259
260
261
262
263
264
265



266
267
268
269
270
271
272
273
274
275







-
-
-
+
+
+







In addition, Setup users can use every feature of the Fossil UI. If Fossil can do a
thing, a Setup user on that repo can make Fossil do it.

Setup users can do many things that Admin users cannot. They may not
only use all of the Admin UI features, they may also:

*   See record IDs (RIDs) on screens that show them
*   See the MIME type of attachments on [`/ainfo` pages](/help?cmd=/ainfo)
*   See a remote repo’s HTTP [cache status](/help?cmd=/cachestat)
    and [pull cache entries](/help?cmd=/cacheget)
*   See the MIME type of attachments on [`/ainfo` pages](/help/www/ainfo)
*   See a remote repo’s HTTP [cache status](/help/www/cachestat)
    and [pull cache entries](/help/www/cacheget)
*   Edit a Setup user’s account!

The “Admin” feature of Fossil UI is so-named because Admin users can use
about half of its functions, but only Setup can use these pages:

*   **Access**: This page falls under the [Security](#security)
    category above, but like Configuration, it's generally something set
393
394
395
396
397
398
399
400

401
402
403
404
405
406
407
393
394
395
396
397
398
399

400
401
402
403
404
405
406
407







-
+







Fossil makes these users grant themselves (or others) these capabilities
deliberately, hopefully after careful consideration.


### <a id="y"></a>Write Unversioned

Fossil currently doesn’t distinguish the sub-operations of
[`fossil uv`](/help?cmd=uv); they’re all covered by [**WrUnver**][capy]
[`fossil uv`](/help/uv); they’re all covered by [**WrUnver**][capy]
(“y”) capability. Since some of these operations are unconditionally
destructive due to the nature of unversioned content, and since this
goes against Fossil’s philosophy of immutable history, nobody gets cap
“y” on a Fossil repo by default, not even the Setup or Admin users.  A
Setup or Admin user must grant cap “y” to someone — not necessarily
themselves! — before modifications to remote
unversioned content are possible.
444
445
446
447
448
449
450
451

452
453
454

455
456
457
458
459
460
461
444
445
446
447
448
449
450

451
452
453

454
455
456
457
458
459
460
461







-
+


-
+









[capa]: ./ref.html#a
[caps]: ./ref.html#s
[capx]: ./ref.html#x
[capy]: ./ref.html#y

[fcp]:   https://fossil-scm.org/home/help?cmd=configuration
[fcp]:   https://fossil-scm.org/home/help/configuration
[fdp]:   ../fossil-v-git.wiki#devorg
[forum]: https://fossil-scm.org/forum/
[fui]:   /help?cmd=ui
[fui]:   /help/ui
[lg]:    ./login-groups.md
[rs]:    https://fossil-scm.org/home/doc/trunk/www/settings.wiki
[sia]:   https://fossil-scm.org/home/artifact?ln=1259-1260&name=0fda31b6683c206a
[snoy]:  https://fossil-scm.org/forum/forumpost/00e1c4ecff
[th1]:   ../th1.md
[tt]:    https://en.wikipedia.org/wiki/Tiger_team#Security
[webo]:  ./#webonly
Changes to www/caps/login-groups.md.
115
116
117
118
119
120
121
122

123
124
125
126
127
128
115
116
117
118
119
120
121

122
123
124
125
126
127
128







-
+






That creates login group G joining repo A to B, then joins C to B.
Although we didn’t explicitly tie C to A, a successful login on C gets
you into both A and B, within the restrictions set out above.

Changes are transitive in the same way, provided you check that “apply
to all” box on the user edit screen.

[lg]: /help?cmd=login-group
[lg]: /help/login-group
[sh]: ../server/any/http-over-ssh.md
[wo]: ./index.md#webonly

-----

*[Back to Administering User Capabilities](./)*
Changes to www/caps/ref.html.
313
314
315
316
317
318
319
320
321
322



323
324
325
326
327
328
329
313
314
315
316
317
318
319



320
321
322
323
324
325
326
327
328
329







-
-
-
+
+
+







  </tr> 

  <tr id="z">
    <th>z</th>
    <th>Zip</th>
    <td>
      Pull archives of particular repository versions via <a
      href="/help?cmd=/zip"><tt>/zip</tt></a>, <a
      href="/help?cmd=/tarball"><tt>/tarball</tt></a>, and <a
      href="/help?cmd=/sqlar"><tt>/sqlar</tt></a> URLs. This is an
      href="/help/www/zip"><tt>/zip</tt></a>, <a
      href="/help/www/tarball"><tt>/tarball</tt></a>, and <a
      href="/help/www/sqlar"><tt>/sqlar</tt></a> URLs. This is an
      expensive capability to grant, because creating such archives can
      put a large load on <a href="../server/">a Fossil server</a> which
      you may then need to <a href="../loadmgmt.md">manage</a>.
      Mnemonic: <b>z</b>ip file download.
    </td>
  </tr> 

Changes to www/cgi.wiki.
70
71
72
73
74
75
76
77


78
79









80
81
82
83
84
85
86
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







-
+
+

-
+
+
+
+
+
+
+
+
+







This is a Boolean property.
If it is present, and if the [#directory:|<b>directory:</b>] option is used,
and if the PATH_INFO string is empty, then Fossil will show a list
of available Fossil repositories.

The "skin" of the reply is determined by the first
repository in the list that has a non-zero
[/help?cmd=repolist-skin|repolist-skin] setting.
[/help/repolist-skin|repolist-skin] setting.

If no repository has such a non-zero repolist-skin setting, then
the repository list is generic HTML without any decoration.
the repository list is generic HTML without any decoration, with
the page title taken from the <tt>FOSSIL_REPOLIST_TITLE</tt>
environment variable. The variable can be defined in the CGI
control file using the [#setenv|<tt>setenv:</tt>] statement.

The "Project Description" and "Login-Group" columns on the repolist page
are optional.  They are hidden by default.  Show them by
etting the <tt>FOSSIL_REPOLIST_SHOW</tt> environment variable to
a string that contains substrings "description" and/or "login-group".

The repolist-generated page recurses into subdirectories and will list
all <tt>*.fossil</tt> files found, with the following exceptions:

   *  Filenames starting with a period are treated as "hidden" and skipped.

   *  Subdirectory names which match the base name of a fossil file in
182
183
184
185
186
187
188
189

190
191
192
193
194
195
196
197
198
199
200
201
191
192
193
194
195
196
197

198
199
200
201
202
203
204
205
206
207
208
209
210







-
+












ticket that matches the value of "name", then redirect to URL.  There
can be multiple "redirect:" lines that are processed in order.  If the
repo name is "*", then an unconditional redirect to URL is taken.


<h2 id="jsmode">jsmode: <i>VALUE</i></h2>

Specifies the delivery mode for JavaScript files. See "[/help?cmd=http |
Specifies the delivery mode for JavaScript files. See "[/help/http |
http --jsmode]" for the allowed values and their meanings.


<h2 id="mainmenu">mainmenu: <i>FILE</i></h2>

This parameter causes the contents of the given file to override the
site's <tt>mainmenu</tt> configuration setting, in much the same way
that the <tt>skin</tt> setting overrides the skin. This can be used to
apply a common main menu to a number of sites, and centrally maintain
it, without having to copy its contents into each site. Note, however,
that the contents of this setting are not stored in the repository and
will not be cloned along with the repository.
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
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
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
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


+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+

-
+

-
+





-
+

-
-
-
-
-
+
+
+
+
+




+
+







<title>Change Log</title>

<h2 id='v2_28'>Changes for version 2.28 (pending)</h2><ol>
  <li> Improvements to [./antibot.wiki|anti-robot defenses]:<ol type="a">
      <li> The default configuration now allows robots to download any tarball
           or similar, to better support of automated build systems.
      <li> New special tag "zipX" for the [/help/robot-restrict|robot-restrict]
           setting blocks robot access to tarballs, but with exceptions to support
           automated build systems.
      <li> Tags of the form "ext/PATH" in the robot-restrict setting block access
           by robots to specific [./serverext.wiki|CGI extension] at PATH.
      <li> Enhancements to the default value for the
           [/help/robot-restrict|robot-restrict setting].
      </ol>
  <li> A drop-down menu of recent branches is now possible for the submenu, and
       is used in the [/dir?ci=trunk|code browser].
  <li> Easier access to generated tarballs and ZIPs:<ol type="a">
       <li> When in the [/dir?ci=trunk|code browser] at the top-level,
            a new "Download" submenu option is available to take the
            user to a page where he can download a tarball or ZIP archive.
       <li> New [/help/www/download|/download page] is available. When
            configured using the new
            [/help/suggested-downloads|suggested-downloads setting], a
            link to [/download] named "Tarballs and ZIPs" appears in the
            [/sitemap] and thus on the hamburger menu.
       <li> The filenames for tarballs and ZIPs are now standardized to
            include a timestamp and a hash prefix.
       <li> New "[/help/get|fossil get]" command downloads and unpacks a specific
            check-in without having to clone the repository.
       </ol>
  <li> Timeline enhancements:<ol type="a">
       <li> A new "Simple" view is available.  This is compromise between "Verbose"
            and "Compact" that shows only the check-in hash rather than the full
            detail section.  There is an ellipsis that one can click on to see the
            full detail text.
       <li> The artifact hash in the detail section of each timeline entry is now
            emphasized (controlled by CSS) to make it easier to locate amid all
            the other text and links.
       <li> When clicking on the ellipsis in "Compact" or "Simple" views, the
            ellipsis is replaced by a left arrow ("←") which can be clicked to
            hide the extra detail again.
       <li> New setting [/help/timeline-mark-leaves|timeline-mark-leaves] controls
            whether or not leaf check-ins are marked in the timeline.
       <li> "No-graph" timelines (using the "ng" query parameter) now show
            branch colors and bare check-in circles on the left.  The check-in
            circles appear, but no lines connecting them.
            ([/timeline?ng|example]).
       </ol>
  <li> Labels in Markdown now have IDs generated using the GitHub "slugify"
       algorithm.
  <li> The [/help/timeline|timeline command] is enhanced with the new options
       "<tt>-u|--for-user</tt>" to filter by user, and "<tt>-r</tt>" to display
       entries in chronological order.
  <li> The [/help/open|open command]'s new "<tt>--reopen REPOFILE</tt>" flag
       can be used to fix a checkout after moving its repository file.
  <li> Update internal Unicode character tables, used in regular expression
       handling, from version 13 to 17.
  <li> Add a zoom-message option to [/help/www/chat|/chat] to better support
       pikchr diagrams.
</ol>

<h2 id='v2_27'>Changes for version 2.27 (2025-09-30)</h2><ol>
  <li> Close a potential Denial-of-Service attack against any public-facing Fossil
       server involving exponential behavior in Fossil's regexp implementation.
  <li> Fix a SQL injection on the [/help/www/file|/file page].  Thanks to
       additional defenses built into Fossil, as well as good luck, this injection
       is not exploitable for either data exfiltration or privilege escalation.  The
       only possible result of invoking the injection is a harmless SQL syntax error.
  <li> Strengthen robot defenses to help prevent public-facing servers from being
       overwhelmed by the latest generation of AI spiders.
  <ol type="a">
    <li> New javascript captcha used to restrict access by user "nobody" to pages
         listed in the [/help/robot-restrict|robot-restrict setting].
    <li> The [/help/robot-exception|robot-exception setting] is available to allow
         access to pages that match a regular expression.  Use this, for example, to
         allow curl scripts and similar to download release tarballs.
    <li> Require at least an anonymous login to access the /blame page and similar.
  </ol>
  <li> [/help/www/timeline|Timeline] enhancements:
  <ol type="a">
    <li> The chng= query parameter on the [/help/www/timeline|timeline page]
         so that it works with other query parameters like p=, d=, from=, and to=.
    <li> Always include nodes identify by sel1= and sel2= in the /timeline display.
    <li> Improved title when p= and d= are different.
  </ol>
  <li> Enable the --editor option on the [/help/amend|fossil amend] command.
  <li> When walking the filesystem looking for Fossil repositories, avoid descending
       into directories named "/proc".
  <li> Reduce memory requirements for sending authenticated sync protocol
       messages.
  <li> Show numstat-style change statistics in the /info and /ckout pages.
  <li> Add the [/help/stash | stash rename] subcommand.
  <li> Add the "-h" option to the "[/help/ls|ls]" command to display
       file hashes for a specific check-in when in verbose mode.
   </ol>

<h2 id='v2_26'>Changes for version 2.26 (2025-04-30)</h2><ol>
 <li>Enhancements to [/help/diff|fossil diff] and similar:
     <ol type="a">
     <li> The argument to the --from option can be a directory name, causing
          Fossil to use files under that directory as the baseline for the diff.
     <li> For "gdiff", if no [/help/gdiff-command|gdiff-command setting]
          is defined, Fossil tries to do a --tk diff if "tclsh" and "wish"
          are available, or a --by diff if not.
     <li> The "Reload" button is added to --tk diffs, to bring the displayed
          diff up to date with the latest changes on disk.
     <li> Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
          diffs of multiple files.
     </ol>
 <li>Added the [/help/www/ckout|/ckout web page] to provide information
     about pending changes in a working check-out
 <li>Enhancements to the [/help/ui|fossil ui] command:
     <ol type="a">
     <li> Defaults to using the new [/help/www/ckout|/ckout page] as its
          start page.  Or, if the new "--from PATH" option is present, the
          default start page becomes "/ckout?exbase=PATH".
     <li> The new "--extpage FILENAME" option opens the named file as if it
          where in a [./serverext.wiki|CGI extension].  Example usage: the
          person editing this change log has
          "fossil ui --extpage www/changes.wiki" running and hence can
          press "Reload" on the web browser to view edits.
     <li> Accept both IPv4 and IPv6 connections on all platforms, including
          Windows and OpenBSD.  This also applies to the "fossil server"
          command.
     </ol>
 <li>Enhancements to [/help/merge|fossil merge]:
     <ol type="a">
     <li> Added the [/help/merge-info|fossil merge-info] command and
          especially the --tk option to that command, to provide analysis
          of the most recent merge or update operation.
     <li> When a merge conflict occurs, a new section is added to the conflict
          text that shows Fossil's suggested resolution to the conflict.
     </ol>
 <li>Enhancements to [/help/commit|fossil commit]:
     <ol type="a">
     <li> If Fossil sees potential formatting mistakes (ex: bad hyperlinks)
          in the check-in comment, it will alert the developer and give
          him or her the opportunity to edit the comment before continuing.
          This feature is controllable by the
          [/help/verify-comments|verify-comments setting].
     <li> The new "--if-changes" option causes the commit to become
          a quiet no-op if there are no pending changes.
     <li> Added the ability to sign check-ins with SSH keys. Artifacts signed
          this way are ignored by all previous fossil versions, as if they
          were plain-text file content instead of Fossil artifacts.
     <li> Issue a warning if a user tries to commit on a check-in where the
          branch has been changed.
     <li> The interactive checkin comment prompt shows the formatting rules
          set for that repository.
     <li> Add the "--editor" option.
     </ol>
 <li>Deprecate the --comfmtflags and --comment-format global options and
     no longer list them in the built-in help, but keep them working for
     backwards compatibility.
     Alternative TTY comment formatting can still be specified using the
     [/help/comment-format|comment-format setting], if desired.  The
     default comment format is now called "canonical", not "legacy".
 <li>Enhancements to the [/help/www/timeline|/timeline page]:
     <ol type="a">
     <li> Added the "ml=" ("Merge-in List") query parameter that works
          like "rl=" ("Related List") but adds "mionly" style related
          check-ins instead of the full "rel" style.
     <li> For "tl=", "rl=", and "ml=", the order of the branches in the
          graph now tries to match the order of the branches named in
          the list.
     <li> The "ms=" ("Match Style") query parameter is honored for
          "tl=", "rl=", and "ml=".
     <li> New query parameter "sl=BRANCHLIST" ("Sort List") strives to
          put branches in the specified order in the graph. This
          overrides any "tl=" or similar ordering.
     <li> In the various "from=","to=" query formats, if the one of the
          end points is an ancestor of the other, then the "rel" modifier
          omits check-ins that are not ancestors of the newer endpoint.
     <li> For "tl=" and similar query parameters, if the pattern contains
          GLOB characters, then the matching style ("ms=") is set to GLOB
          automatically and the "ms=" query parameter can be omitted.
     <li> Enhance the "ymd" query parameter so that when used like
          "ymd=YYYYMMDD-YYYYMMDD" it shows all events in the range of
          dates specified.
     <li> Accept the "Z" (Zulu-time) suffix on date arguments for the
          "ymd" and "yw" query parameters.
     <li> The new "min" query parameter, when added to a from=,to= query,
          collapses long runs of check-ins on the same branch into just
          end-points.
     <li> The p= and d= parameters can now reference different check-ins,
          in which case the timeline shows those check-ins that are both
          ancestors of p= and descendants of d=.
     <li> The saturation and intensity of user-specified checkin and branch
          background colors are automatically adjusted to keep the colors
          compatible with the current skin, unless the
          [/help/raw-bgcolor|raw-bgcolor setting] is turned on.
     </ol>
 <li>The [/help/www/docfile|/docfile webpage] was added.  It works like
     /doc but keeps the title of markdown documents with the document rather
     that moving it up to the page title.
 <li>Added the [/help/www/clusterlist|/clusterlist page] for analysis
     and debugging
 <li>Added the "artifact_to_json(NAME)" SQL function that returns a JSON
     decoding of the artifact described by NAME.
 <li>Improvements to the [/help/patch|fossil patch] command:
     <ol type="a">
     <li> Fix a bug in "fossil patch create" that causes
          [/help/revert|fossil revert] operations that happened
          on individualfiles after a [/help/merge|fossil merge]
          to be omitted from the patch.
     <li>  Added the [/help/patch|patch alias] command for managing
           aliases for remote checkout names.
     </ol>
 <li>Enhancements to on-line help and the [/help/help|fossil help] command:
     <ol type="a">
     <li> Add the ability to search the help text, either in the UI
          (on the [/help/www/search|/search page]) or from the command-line
          (using the "[/help/search|fossil search -h PATTERN]" command.)
     <li> Accepts an optional SUBCOMMAND argument following the
          COMMAND argument and only shows results for the specified
          subcommand, not the entire command.
     <li> The -u (--usage) option shows only the command-line syntax
     <li> The -o (--options) option shows only the command-line options
     </ol>
 <li>Enhancements to the [./tickets.wiki|ticket system]:
     <ol type="a">
     <li> Added the ability to attach wiki pages to a ticket for extended
          descriptions.
     <li> Added submenu to the 'View Ticket' page, to use it as
          template for a new ticket.
     <li> Added button 'Submit and New' to create multiple tickets
          in a row.
     <li> Link the version field in ticket view to a matching checkin or tag.
     <li> Show creation time in report and ticket view.
     <li> Show previous comments in edit ticket as reference.
     </ol>
 <li>Added the "hash" query parameter to the
     [/help/www/whatis|/whatis webpage].
 <li>Add a "user permissions changes" [/doc/trunk/www/alerts.md|subscription]
     which alerts subscribers when an admin creates a new user or
     when a user's permissions change.
 <li>If the FOSSIL_REPOLIST_SHOW environment variable exists and contains
     the substring "description", then the project description for each repository
     is shown on the repository list page.  The login-group for each project is
     now only shown if the FOSSIL_REPOLIST_SHOW environment variable exists and
     contains the substring "login-group". ([./cgi.wiki#repolist|More information])
 <li>The [/doc/trunk/www/th1.md|TH1 script language] is enhanced for improved
     security:
     <ol type="a">
     <li> TH1 now makes a distinction between
          [/doc/trunk/www/th1.md#taint|tainted and untainted string values].
          This makes it more difficult to write custom TH1 scripts that
          contain XSS or SQL-injection bugs.  The
          [/help/vuln-report|vuln-report] setting was added to control
          what Fossil does when it encounters a potential TH1
          security problem.
     <li> The "--th" option was removed from the [/help/pikchr|fossil pikchr]
          command.
     <li> The "enable_htmlify" TH1 command was removed.
     </ol>
 <li>Make [/help/www/chat|/chat] better-behaved during server outages, reducing
     the frequency of reconnection attempts over time and providing feedback
     to the user when the connection is down.
 <li>The [/help/www/sqlar|/sqlar] page does not work for users who are not logged
     in, nor are links to that page displayed to users who are not logged in.  Being
     logged in as "anonymous" is sufficient to overcome this restriction, assuming
     that "anonymous" can download tarballs and ZIP archives.
 <li>Many other minor fixes and additions.
</ol>

<h2 id='v2_25'>Changes for version 2.25 (pending)</h2>
<h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>

  *  The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
  *  The "[/help/ui|fossil ui /]" command now works even for repositories
     that have non-ASCII filenames
  *  Add the [/help?cmd=tree|fossil tree] command.
  *  Add the [/help/tree|fossil tree] command.
  *  On case-insensitive filesystems, store files using the filesystem's
     preferred case rather than the case typed in by the user.
  *  Change the name "fossil cherry-pick" command to "fossil cherrypick",
     which is more familiar to Git users.  Retain the legacy name for
     compatibility.
  *  Add new query parameters to the [/help?cmd=/timeline|/timeline page]:
  *  Add new query parameters to the [/help/www/timeline|/timeline page]:
     d2=, p2=, and dp2=.
  *  Add options to the [/help?cmd=tag|fossil tag] command that will list tag values.
  *  Add the -b|--brief option to the [/help?cmd=status|fossil status] command.
  *  Add ability to upload unversioned files via the [/help?cmd=/uvlist|/uvlist page].
  *  Add history search to the [/help?cmd=/chat|/chat page].
  *  Add Unix socket support to the [/help?cmd=server|server command].
  *  Add options to the [/help/tag|fossil tag] command that will list tag values.
  *  Add the -b|--brief option to the [/help/status|fossil status] command.
  *  Add ability to upload unversioned files via the [/help/www/uvlist|/uvlist page].
  *  Add history search to the [/help/www/chat|/chat page].
  *  Add Unix socket support to the [/help/server|server command].
  *  On Windows, use the root certificates managed by the operating system
     (requires OpenSSL 3.2.0 or greater).
  *  Take into account zero-width and double-width unicode characters when
     formatting the command-line timeline.
  *  Update the built-in SQLite to version 3.47.0.  Precompiled binaries are
     linked against OpenSSL 3.4.0.
  *  Numerous minor fixes and additions.


<h2 id='v2_24'>Changes for version 2.24 (2024-04-23)</h2>

  *  Apache change work-around &rarr; As part of a security fix, the Apache webserver
     mod_cgi module has stopped relaying the Content-Length field of the HTTP
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
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
408
409
410
411
412
413
414







-
+













-
+




-
+

















-
+

-
-
+
+


-
+
















-
+


-
+




-
+





-
-
+
+








-
+







           select one of the built-in skins as a default, or to specify a
           custom skin.
     </ul>
  *  If an "ssh:" sync fails in a way that suggests that the fossil executable
     could not be found on the remote host, then retry after adding a PATH=
     prefix to the command.  This helps "ssh:" to "just work" when the server
     is a Mac.
  *  Enhancements to the [/help?cmd=/timeline|/timeline page]:
  *  Enhancements to the [/help/www/timeline|/timeline page]:
     <ul>
     <li> Add the x= query paramater
     <li> Add the shortcut tl= and rl= query parameters
     <li> Add support for from=,ft= and from=,bt= query parameter combinations
     <li> Automatically highlight the endpoints for from=,to= queries.
     <li> Add the to2=Z query parameter to augment from=X,to=Y so that the
          path from X to Z is shown if Y cannot be found.
     </ul>
  *  Moved the /museum/repo.fossil file referenced from the Dockerfile from
     the ENTRYPOINT to the CMD part to allow use of --repolist mode.
  *  The [/uvlist] page now shows the hash algorithm used so that
     viewers don't have to guess.  The hash is shown in a fixed-width
     font for a more visually pleasing display.
  *  If the [/help?cmd=autosync|autosync setting] contains keyword "all",
  *  If the [/help/autosync|autosync setting] contains keyword "all",
     the automatic sync occurs against all defined remote repositories, not
     just the default.
  *  Markdown formatter: improved handling of indented fenced code blocks
     that contain blank lines.
  *  When doing a "[/help?cmd=add|fossil add]" on a system with case-insensitive
  *  When doing a "[/help/add|fossil add]" on a system with case-insensitive
     but case-preserving filenames (Mac and Windows) try to use the filename
     case as it is known to the filesystem, not the case entered by the
     user on the command-line.  See
     [forum:/forumpost/30d9c0d131610f53|forum thread 30d9c0d131610f53].
  *  Fix problems with one-click unsubscribe on email notifications.
  *  Import the latest [/doc/trunk/www/pikchr.md|Pikchr] containing support
     for "diamond" objects.
  *  Add ability to render committed Pikchr files to SVG via
     <samp>/doc/…/foo.pikchr?popup</samp> URLs.
  *  Update Fossil's internal robot detection logic so that it correctly
     identifies the new GoogleOther crawler as a robot.

<h2 id='v2_23'>Changes for version 2.23 (2023-11-01)</h2>

  *  Add ability to "close" forum threads, such that unprivileged users
     may no longer respond to them. Only administrators can close
     threads or respond to them by default, and the
     [/help?cmd=forum-close-policy|forum-close-policy setting] can be
     [/help/forum-close-policy|forum-close-policy setting] can be
     used to add that capability to moderators.
  *  Add the [/help?cmd=all|fossil all whatis] command.
  *  The [/help?cmd=status|fossil status] command and relevant UI pages now
  *  Add the [/help/all|fossil all whatis] command.
  *  The [/help/status|fossil status] command and relevant UI pages now
     correctly report files which were both renamed <b>and</b> edited as such.
  *  Show default value of settings that have a default in
     [/help?cmd=help|fossil help SETTING] output.
     [/help/help|fossil help SETTING] output.
  *  On timeline graphs, show closed check-ins using an X in the middle of the
     node circle or box.
  *  New options for email notification:  Get email only for the first
     post in each new thread, and/or posts that are in reply to my posts.
  *  Fix a regression bug introduced in version 2.22 that caused FTS5 searches
     to fail for terms containing non-ASCII characters.
  *  Improved defense-in-depth against malicious attack:
     <ul>
     <li> When an attempted SQL injection attack is detected, return
          HTTP result code 418, which can signal the web server to sanction
          the attacking IP address.
     <li> Better defense against cross-site request forgery (CSRF)
          attacks.
     <li> Improvements to static analysis of source code (the codecheck1.c
          file in the source tree).
     </ul>
  *  Enhance the [/help?cmd=/dir|treeview file listings]
  *  Enhance the [/help/www/dir|treeview file listings]
     ([/dir?type=tree&ci=trunk|example]) by displaying file sizes
     and adding the option to sort by file size.
  *  The [/help?cmd=fts-config|fossil fts-config] command now shows how much
  *  The [/help/fts-config|fossil fts-config] command now shows how much
     repository space is used by the full-text index.
  *  Changing a setting to an empty string is now the same as deleting the
     setting, in most cases.  There are a few exceptions, indicated by the
     keep-empty flag on the setting definition.
  *  The [/help?cmd=branch|fossil branch list] command can now filter branches
  *  The [/help/branch|fossil branch list] command can now filter branches
     that have/have not been merged into the current branch.
  *  Improvements to interactions with remote repositories over SSH:
     <ul>
     <li> Print the text of the SSH command that is run to do remote interaction,
          for full disclosure to the operator.
     <li> Add a PATH= argument to the [/help?cmd=ui|fossil ui remote:/] and
          [/help?cmd=patch|fossil patch push/pull remote:...] commands so that
     <li> Add a PATH= argument to the [/help/ui|fossil ui remote:/] and
          [/help/patch|fossil patch push/pull remote:...] commands so that
          they work when the "remote" machine is a Mac and the "fossil"
          executable is in the $HOME/bin directory.
     </ul>
  *  Update built-in libraries SQLite, ZLib, Pikchr to their latest versions.
  *  Documentation enhancements and typo fixes.


<h2 id='v2_22'>Changes for version 2.22 (2023-05-31)</h2>
  *  Enhancements to the [/help?cmd=/timeline|/timeline webpage]: <ol type="a">
  *  Enhancements to the [/help/www/timeline|/timeline webpage]: <ol type="a">
     <li> Add the ft=TAG query parameter which in combination with d=Y
          shows all descendants of Y up to TAG
     <li> Enhance the s=PATTERN (search) query parameter so that forum post
          text is also searched when the "vfx" query parameter is used
     <li> Fix the u= (user) query parameter so that it works with a= and b=
     <li> Add the oldestfirst query parameter to show the events in reverse order.
          Useful in combination with y=f and vfs and perhaps also u= to show all
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
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







-
+


-
+


-
+








-
-
-
-
-
+
+
+
+
+



-
+



-
+




-
+







-
+




-
+







     inside the jail for Fossil's use.
  *  Add support for the trigram tokenizer for FTS5 search to enable
     searching in Chinese.
  *  Comment lines (starting with a '#') are now supported inside
     [./settings.wiki#versionable|versioned settings].
  *  Default permissions for anonymous users in new repositories are
     changed to "hz".
  *  The [/help?cmd=status|fossil status] command now detects when a
  *  The [/help/status|fossil status] command now detects when a
     file used to be a symlink and has been replaced by a regular file.
     (It previously checked for the inverse case only.)
  *  The [/help?cmd=empty-dirs|empty-dirs setting] now reuses the same
  *  The [/help/empty-dirs|empty-dirs setting] now reuses the same
     parser as the *-glob settings instead of its prior idiosyncratic
     parser, allowing quoted whitespace in patterns.
  *  Enhancements to the [/help?cmd=/reports|/reports webpage]:
  *  Enhancements to the [/help/www/reports|/reports webpage]:
     <ol type="a">
     <li> The by-week, by-month, and by-year options now show an estimated
          size of the current week, month, or year as a dashed box.
     <li> New sub-categories "Merge Check-ins" and "Non-Merge Check-ins".
     </ol>

<h2 id='v2_21'>Changes for version 2.21 (2023-02-25)</h2>
  *  Users can request a password reset.  This feature is disabled by default.
     Use the new [/help?cmd=self-pw-reset|self-pw-reset property] to enable it.
     New web pages [/help?cmd=/resetpw|/resetpw] and
     [/help?cmd=/reqpwreset|/reqpwreset] added.
  *  Add the [/help?cmd=repack|fossil repack] command (together with
     [/help?cmd=all|fossil all repack]) as a convenient way to optimize the
     Use the new [/help/self-pw-reset|self-pw-reset property] to enable it.
     New web pages [/help/www/resetpw|/resetpw] and
     [/help/www/reqpwreset|/reqpwreset] added.
  *  Add the [/help/repack|fossil repack] command (together with
     [/help/all|fossil all repack]) as a convenient way to optimize the
     size of one or all of the repositories on a system.
  *  Add the ability to put text descriptions on ticket report formats.
  *  Upgrade the test-find-pivot command to the [/help/merge-base|merge-base command].
  *  The [/help?cmd=/chat|/chat page] can now embed fossil-rendered
  *  The [/help/www/chat|/chat page] can now embed fossil-rendered
     views of wiki/markdown/pikchr file attachments with the caveat that such
     embedding happens in an iframe and thus does not inherit styles and such
     from the containing browser window.
  *  The [/help?cmd=all|fossil all remote] subcommand added to "fossil all".
  *  The [/help/all|fossil all remote] subcommand added to "fossil all".
  *  Passwords for remembered remote repositories are now stored as irreversible
     hashes rather than obscured clear-text, for improved security.
  *  Add the "nossl" and "nocompress" options to CGI.
  *  Update search infrastructure from FTS4 to FTS5.
  *  Add the [/help?cmd=/deltachain|/deltachain] page for debugging purposes.
  *  Add the [/help/www/deltachain|/deltachain] page for debugging purposes.
  *  Writes to the database are disabled by default if the HTTP request
     does not come from the same origin.  This enhancement is a defense in depth
     measure only; it does not address any known vulnerabilities.
  *  Improvements to automatic detection and mitigation of attacks from
     malicious robots.

<h2 id='v2_20'>Changes for version 2.20 (2022-11-16)</h2>
  *  Added the [/help?cmd=chat-timeline-user|chat-timeline-user setting].  If
  *  Added the [/help/chat-timeline-user|chat-timeline-user setting].  If
     it is not an empty string, then any changes that would appear on the timeline
     are announced in [./chat.md|the chat room].
  *  The /unsubscribe page now requests confirmation. [./alerts.md|Email notifications]
     now contain only an "Unsubscribe" link, and not a link to subscription management.
  *  Added the "[/help?cmd=branch|fossil branch lsh]" subcommand to list the
  *  Added the "[/help/branch|fossil branch lsh]" subcommand to list the
     most recently modified branches.
  *  More elements of the /info page are now inside of an accordion.
  *  Replace the <tt>--dryrun</tt> flag with <tt>--dry-run</tt> in all
     commands which still used the former name, for consistency.
  *  Rebuilt [/file/Dockerfile | the stock Dockerfile] to create a "from scratch"
     Busybox based container image via an Alpine Linux intermediary
  *  Added [/doc/trunk/www/containers.md | a new document] describing how to
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
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
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
480

481
482
483
484
485


486
487
488
489
490
491
492
493

494
495
496
497
498
499
500
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
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







-
+















-
+

-
+

-
+





-
-
-
-
+
+
+
+

-
-
+
+




-
+

-
+

-
+

-
+

-
+


-
-
+
+



-
-
+
+




-
+





-
+





-
-
+
+


-
-
+
+


-
+

-
+

-
+

-
+

-
+


-
-
+
+




-
+


-
+

-
+












-
-
+
+

-
+










-
+












-
+

-
+


-
+

-
+

-
+


-
+

-
+




















-
+













-
+







-
-
+
+

-
+




-
+





-
+





-
-
+
+











-
+



-
-
+
+







-
+







  *  Performance enhancement for the
     [./checkin_names.wiki#root|"root:BRANCHNAME" style of tag],
     accomplished using a Common Table Expression in the underlying SQL.
  *  Sort tag listings (command line and webpage) by taking numbers into
     consideration so as to cater for tags that follow semantic versioning.
  *  On the wiki listings, omit by default wiki pages that are associated with
     check-ins and branches.
  *  Add the new "[/help?cmd=describe|fossil describe]" command.
  *  Add the new "[/help/describe|fossil describe]" command.
  *  Markdown subsystem extended with [../src/markdown.md#ftnts|footnotes support].
     See corresponding [../test/markdown-test3.md|test cases],
     [/wiki?name=branch/markdown-footnotes#il|known limitations] and
     [forum:/forumthread/ee1f1597e46ec07a|discussion].
  *  Add the new special name "start:BRANCH" to refer to the first check-in of
     the branch.
  *  Support [/wiki?name=branch/generated-tkt-mimetype&p|generated "mimetype"]
     columns in the <var>TICKET</var> and <var>TICKETCHNG</var> tables.
  *  Fix [/timeline?r=fix_remote_url_overwrite_with_proxy|remote-url-overwrite]
     bug where remote-url is overwritten by the proxy setting during sync
     operation. Also require explicit "system" proxy setting to use
     "http_proxy" environment variable.
  *  Reimplemented the [/pikchrshow] app to use a WebAssembly build of
     pikchr so that it can render pikchrs on the client instead of requiring
     a server round-trip.
  *  Add the [/help?cmd=email-listid|email-listid setting]. If set, it is
  *  Add the [/help/email-listid|email-listid setting]. If set, it is
     used as the List-ID header for all outbound notification emails.
  *  Add the "--branch" option to the "[/help?cmd=timeline|timeline]" command
  *  Add the "--branch" option to the "[/help/timeline|timeline]" command
     to restrict the displayed items to a specific branch.
  *  Add the "--versions" option to "[/help?cmd=diff|fossil diff]"
  *  Add the "--versions" option to "[/help/diff|fossil diff]"
     to display details about the compared versions into the patch header.
  *  Numerous other minor enhancements.

<h2 id='v2_18'>Changes for version 2.18 (2022-02-23)</h2>
  *  Added support for [./ssl-server.md|SSL/TLS server mode] for commands
     like "[/help?cmd=server|fossil server]" and "[/help?cmd=http|fossil http]"
  *  The new [/help?cmd=cherry-pick|cherry-pick command] is an alias for
     [/help?cmd=merge|merge --cherrypick].
  *  Add new setting "[/help?cmd=large-file-size|large-file-size]".  If the size
     like "[/help/server|fossil server]" and "[/help/http|fossil http]"
  *  The new [/help/cherry-pick|cherry-pick command] is an alias for
     [/help/merge|merge --cherrypick].
  *  Add new setting "[/help/large-file-size|large-file-size]".  If the size
     of any file in a commit exceeds this size, a warning is issued.
  *  Query parameter "year=YYYY" is now accepted by [/help?cmd=/timeline|/timeline].
  *  The [/help?cmd=tar|tar] and [/help?cmd=zip|zip commands] no longer
  *  Query parameter "year=YYYY" is now accepted by [/help/www/timeline|/timeline].
  *  The [/help/tar|tar] and [/help/zip|zip commands] no longer
     sterilize the manifest file.
  *  Further improvement to diff alignment in cases that involve both
     edits and indentation changes.
  *  [/doc/trunk/www/chat.md|Chat] improvements:<ul>
     <li>  [/help?cmd=/chat|The /chat page] input options have been reworked
     <li>  [/help/www/chat|The /chat page] input options have been reworked
           again for better cross-browser portability.
     <li>  When sending a [/help?cmd=/chat|/chat] message fails, it is no longer
     <li>  When sending a [/help/www/chat|/chat] message fails, it is no longer
           immediately lost and sending may optionally be retried.
     <li>  [/help?cmd=/chat|/chat] can now optionally embed attachments of certain
     <li>  [/help/www/chat|/chat] can now optionally embed attachments of certain
           types directly into message bodies via an iframe.
     <li>  Add the "--as FILENAME" option to the "[/help?cmd=chat|fossil chat send]"
     <li>  Add the "--as FILENAME" option to the "[/help/chat|fossil chat send]"
           command.
     <li>  Added the "[/help?cmd=chat|fossil chat pull]" command, available to
     <li>  Added the "[/help/chat|fossil chat pull]" command, available to
           administrators only, for backing up the chat conversation.
     </ul>
  *  Promote the test-detach command into the [/help?cmd=detach|detach command].
  *  For "[/help?cmd=pull|fossil pull]" with the --from-parent-project option,
  *  Promote the test-detach command into the [/help/detach|detach command].
  *  For "[/help/pull|fossil pull]" with the --from-parent-project option,
     if no URL is specified then use the last URL from the most recent prior
     "fossil pull --from-parent-project".
  *  Add options --project-name and --project-desc to the
     "[/help?cmd=init|fossil init]" command.
  *  The [/help?cmd=/ext|/ext page] generates the SERVER_SOFTWARE environment
     "[/help/init|fossil init]" command.
  *  The [/help/www/ext|/ext page] generates the SERVER_SOFTWARE environment
     variable for clients.
  *  Fix the REQUEST_URI [/doc/trunk/www/aboutcgi.wiki#cgivar|CGI variable] such
     that it includes the query string.  This is how most other systems understand
     REQUEST_URI.
  *  Added the --transport-command option to [/help?cmd=sync|fossil sync]
  *  Added the --transport-command option to [/help/sync|fossil sync]
     and similar.

<h2 id='v2_17'>Changes for version 2.17 (2021-10-09)</h2>

  *  Major improvements to the "diff" subsystem, including: <ul>
     <li> Added new [/help?cmd=diff|formatting options]: --by, -b, --webpage, --json, --tcl.
     <li> Added new [/help/diff|formatting options]: --by, -b, --webpage, --json, --tcl.
     <li> Partial-line matching for unified diffs
     <li> Better partial-line matching for side-by-side diffs
     <li> Buttons on web-based diffs to show more context
     <li> Performance improvements
     </ul>
  *  The --branchcolor option on [/help?cmd=commit|fossil commit] and
     [/help?cmd=amend|fossil amend] can now take the value "auto" to
  *  The --branchcolor option on [/help/commit|fossil commit] and
     [/help/amend|fossil amend] can now take the value "auto" to
     force Fossil to use its built-in automatic color choosing algorithm.
  *  Fossil now [./concepts.wiki#workflow|autosyncs] prior to running
     [/help?cmd=open|fossil open].
  *  Add the [/help?cmd=ticket-default-report|ticket-default-report setting],
     [/help/open|fossil open].
  *  Add the [/help/ticket-default-report|ticket-default-report setting],
     which if set to the title of a ticket report causes that ticket report
     to be displayed below the search box in the /ticket page.
  *  The "nc" query parameter to the [/help?cmd=/timeline|/timeline] page
  *  The "nc" query parameter to the [/help/www/timeline|/timeline] page
     causes all graph coloring to be omitted.
  *  Improvements and bug fixes to the new "[/help?cmd=ui|fossil ui REMOTE]"
  *  Improvements and bug fixes to the new "[/help/ui|fossil ui REMOTE]"
     feature so that it works better on a wider variety of platforms.
  *  In [/help?cmd=/wikiedit|/wikiedit], show the list of attachments for
  *  In [/help/www/wikiedit|/wikiedit], show the list of attachments for
     the current page and list URLs suitable for pasting them into the page.
  *  Add the --no-http-compression option to [/help?cmd=sync|fossil sync]
  *  Add the --no-http-compression option to [/help/sync|fossil sync]
     and similar.
  *  Print total payload bytes on a [/help?cmd=sync|fossil sync] when using
  *  Print total payload bytes on a [/help/sync|fossil sync] when using
     the --verbose option.
  *  Add the <tt>close</tt>, <tt>reopen</tt>, <tt>hide</tt>, and
     </tt>unhide</tt> subcommands to [/help?cmd=branch|the branch command].
  *  The "-p" option to [/help?cmd=branch|fossil branch list] shows only
     </tt>unhide</tt> subcommands to [/help/branch|the branch command].
  *  The "-p" option to [/help/branch|fossil branch list] shows only
     private branches.
  *  The [/md_rules|Markdown formatter] now interprets the content of
     block HTML markup (such as &lt;table&gt;) in most cases.  Only content
     of &lt;pre&gt; and &lt;script&gt; is passed through verbatim.
  *  The [/help?cmd=wiki|wiki list command] no longer lists "deleted"
  *  The [/help/wiki|wiki list command] no longer lists "deleted"
     pages by default. Use the new <tt>--all</tt> option to include deleted
     pages in the output.
  *  The [/help?cmd=all|fossil all git status] command only shows reports for
  *  The [/help/all|fossil all git status] command only shows reports for
     the subset of repositories that have a configured Git export.
  *  The [/help?cmd=/chat|/chat] configuration was reimplemented and
  *  The [/help/www/chat|/chat] configuration was reimplemented and
     provides new options, including the ability for a repository
     administrator to
     [./chat.md#notifications|extend the selection of notification sounds]
     using unversioned files.
  *  Chat now uses fossil's full complement of markdown features,
     instead of the prior small subset of markup it previously supported.
     This retroactively applies to all chat messages, as they are
     markdown-processed when they are sent instead of when they
     are saved.
  *  Added a chat message preview mode so messages can be previewed
     before being sent. Similarly, added a per-message ability to view
     the raw un-parsed message text.
  *  The hotkey to activate preview mode in [/help?cmd=/wikiedit|/wikiedit],
     [/help?cmd=/fileedit|/fileedit], and [/help?cmd=/pikchrshow|/pikchrshow]
  *  The hotkey to activate preview mode in [/help/www/wikiedit|/wikiedit],
     [/help/www/fileedit|/fileedit], and [/help/www/pikchrshow|/pikchrshow]
     was changed from ctrl-enter to shift-enter in order to align with
     [/help?cmd=/chat|/chat]'s new preview feature and related future
     [/help/www/chat|/chat]'s new preview feature and related future
     changes.

<h2 id='v2_16'>Changes for Version 2.16 (2021-07-02)</h2>
  *  <b>Security:</b> Fix the client-side TLS so that it verifies that the
     server hostname matches its certificate.
  *  The default "ssh" command on Windows is changed to "ssh" instead of the
     legacy "plink", as ssh is now generally available on Windows systems.
     Installations that still need to use the legacy "plink" can make that
     happen by running: '<tt>fossil set ssh-command "plink -ssh" --global</tt>'.
  *  Added the [./patchcmd.md|fossil patch] command.
  *  The [/help?cmd=ui|fossil ui] command is enhanced in multiple ways:<ol>
  *  The [/help/ui|fossil ui] command is enhanced in multiple ways:<ol>
     <li> The REPOSITORY argument can be the name of a check-out directory.
     <li> If the REPOSITORY argument is prefixed by "HOST:" or "USER@HOST:"
          then the ui is run on the remote machine and tunnelled back to the local
          machine using ssh.  (The latest version of fossil must be installed on
          both the local and the remote for this to work correctly.)
     <li> The new --nobrowser and --fossilcmd options is provided.
     </ol>
  *  The [/brlist|/brlist web page] allows the user to
     select multiple branches to be displayed together in a single
     timeline.
  *  The [./forum.wiki|Forum] provides a hyperlink on the author of each
     post that goes to a timeline of recent posts by that same author.
  *  Added the "[/help?cmd=bisect|fossil bisect run]" command for improved
  *  Added the "[/help/bisect|fossil bisect run]" command for improved
     automation of bisects.
  *  The [/help?cmd=merge|fossil merge] command now does a better job merging
  *  The [/help/merge|fossil merge] command now does a better job merging
     branches where files have been renamed between the current branch and the
     branch being merged.
  *  The [/help?cmd=open|fossil open] command allows the repository file
  *  The [/help/open|fossil open] command allows the repository file
     to be inside the working directory without requiring the --force flag.
  *  The [/help?cmd=/wikiedit|/wikiedit] and [/help?cmd=/wikinew|/wikinew]
  *  The [/help/www/wikiedit|/wikiedit] and [/help/www/wikinew|/wikinew]
     pages now default to markdown format.
  *  The [/help?cmd=/login|/login] page now links to a user's forum post
  *  The [/help/www/login|/login] page now links to a user's forum post
     timeline if the repository has forum posts.
  *  Tags may now be propagated for forum posts, wiki pages, and technotes.
     The [/help?cmd=tag|tag command] can now manipulate and list such tags.
     The [/help/tag|tag command] can now manipulate and list such tags.
  *  [./caps/login-groups.md|Login-Groups] are now shown on the repository
     list of the "[/help?cmd=all|fossil all ui]" command.
     list of the "[/help/all|fossil all ui]" command.
  *  Administrators can configure [./alerts.md|email alerts] to expire
     a specific number of days (ex: 365) after the last user contact with
     the Fossil server. This prevents alert emails being sent to
     abandoned email accounts forever.
  *  SQL that defines [/tktsetup_tab|database objects for tickets] now
     [/timeline?c=c717f1ef9a1a4c91|can DROP] a VIEW or an INDEX provided
     that its name starts with '<code>ticket</code>' or '<code>fx_</code>'.
  *  Update the built-in SQLite to version 3.36.0.
  *  Numerous other minor enhancements.

<h2 id='v2_15'>Changes for Version 2.15 (2021-03-26) and Patch 2.15.1 on (2021-04-07)
    and 2.15.2 on (2021-06-15)</h2>
  *  <b>Patch 2.15.2:</b> Fix the client-side TLS so that it verifies that the
     server hostname matches its certificate. <b>Upgrading to
     the patch is recommended.</b>
  *  <b>Patch 2.15.1:</b> Fix a data exfiltration bug in the server.  <b>Upgrading to
     the patch is recommended.</b>
  *  The [./defcsp.md|default CSP] has been relaxed slightly to allow
     images to be loaded from any URL.  All other resources are still
     locked down by default.
  *  The built-in skins all use the "[/help?cmd=mainmenu|mainmenu]"
  *  The built-in skins all use the "[/help/mainmenu|mainmenu]"
     setting to determine the content of the main menu.
     The ability to edit the
     "mainmenu" setting is added on the /Admin/Configuration page.
  *  The hamburger menu is now available on most of the built-in skins.
  *  Any built-in skin named "X" can be used instead of the standard
     repository skin by adding the URL parameter <tt>skin=X</tt> to the
     request.  The selection is persisted using the display
     preferences cookie unless the "once" query parameter is also
     included.  The [/skins] page may be used to select a skin.
  *  The [/cookies] page now gives the user an opportunity to delete
     individual cookies.  And the /cookies page is linked from the
     /sitemap, so that it appears in hamburger menus.
  *  The [/sitemap] extensions are now specified by a single new
     "[/help?cmd=sitemap-extra|sitemap-extra setting]",
     "[/help/sitemap-extra|sitemap-extra setting]",
     rather than a cluster of various
     "sitemap-*" settings.  The older settings are no longer used.
     <b>This change might require minor server configuration
     adjustments on servers that use /sitemap extensions.</b>
     The /Admin/Configuration page provides the ability to edit
     the new "sitemap-extra" setting.
  *  Added the "--ckout-alias NAME" option to
     [/help?cmd=ui|fossil ui], [/help?cmd=server|fossil server], and
     [/help?cmd=http|fossil http].  This option causes Fossil to
     [/help/ui|fossil ui], [/help/server|fossil server], and
     [/help/http|fossil http].  This option causes Fossil to
     understand URIs of the form "/doc/NAME/..." as if they were
     "[/help?cmd=/doc|/doc/ckout/...]", to facilitate testing of
     "[/help/www/doc|/doc/ckout/...]", to facilitate testing of
     [./embeddeddoc.wiki|embedded documentation] changes prior to
     check-in.
  *  For diff web pages, if the diff type (unified versus side-by-side)
     is not specified by a query parameter, and if the
     "[/help?cmd=preferred-diff-type|preferred-diff-type]"
     "[/help/preferred-diff-type|preferred-diff-type]"
     setting is omitted or less than 1, then select the diff type based
     on a guess of whether or not the request is coming from a mobile
     device.  Mobile gets unified and desktop gets side-by-side.
  *  The various pages which show diffs now have toggles to show/hide
     individual diffs.
  *  Add the "[/help?cmd=preferred-diff-type|preferred-diff-type]"
  *  Add the "[/help/preferred-diff-type|preferred-diff-type]"
     setting to allow an admin to force a default diff type.
  *  The "pikchr-background" setting is now available in
     "detail.txt" skin files, for better control of Pikchr
     colors in inverted color schemes.
  *  Add the <tt>--list</tt> option to the
     [/help?cmd=tarball|tarball],
     [/help?cmd=zip|zip], and [/help?cmd=sqlar|sqlar]
     [/help/tarball|tarball],
     [/help/zip|zip], and [/help/sqlar|sqlar]
     commands.
  *  The javascript used to implement the hamburger menu on the
     default built-in skin has been made generic so that it is usable
     by a variety of skins, and promoted to an ordinary built-in
     javascript file.
  *  New TH1 commands:
     "[/doc/trunk/www/th1.md#bireqjs|builtin_request_js]",
     "[/doc/trunk/www/th1.md#capexpr|capexpr]",
     "foreach", "lappend", and "string match"
  *  The [/help/leaves|leaves command] now shows the branch point
     of each leaf.
  *  The [/help?cmd=add|fossil add] command refuses to add files whose
  *  The [/help/add|fossil add] command refuses to add files whose
     names are reserved by Windows (ex: "aux") unless the --allow-reserved
     option is included.  This helps prevent Unix users from accidentally
     creating check-ins that are unreadable by Windows users.
  *  Add the "re=" query parameter to the [/help?cmd=/dir|/dir] webpage,
     for symmetry with the [/help?cmd=/tree|/tree] page.
  *  Add the "re=" query parameter to the [/help/www/dir|/dir] webpage,
     for symmetry with the [/help/www/tree|/tree] page.
  *  Update the built-in SQLite to version 3.35.0.
  *  The ./configure script now has the --print-minimum-sqlite-version option
     that prints the minimum SQLite version required by the current version
     of Fossil.  This might be used by integrators who insist on building
     Fossil to link against the system SQLite library rather than the
     built-in copy of SQLite, to verify that their system SQLite library
     is recent enough.
  *  Webpage that shows [/help?cmd=/whistory|history of a wiki page]
  *  Webpage that shows [/help/www/whistory|history of a wiki page]
     gained client-side UI to help with comparison between two arbitrary
     versions of a wiki (by the means of anchoring a "baseline" version)
     and the ability to squeeze several sequential edits made by the same
     user into a single "recycled" row (the latest edit in that sequence).

<h2 id='v2_14'>Changes for Version 2.14 (2021-01-20) and Patch 2.14.1 on (2021-04-07)
    and 2.14.2 on (2021-06-15)</h2>
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
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







-
+






-
+




-
+




-
+


-
+

-
+


-
-
-
+
+
+

-
+







     add content to a repository using Fossil 2.14 or later.  No
     action is needed on your part. However, if you upgrade to
     version 2.14 and then later downgrade or otherwise use an earlier
     version of Fossil, the email notification mechanism may fail
     to send out notifications for some events, due to the missing
     trigger.  If you want to
     permanently downgrade an installation, then you should run
     "[/help?cmd=rebuild|fossil rebuild]" after the downgrade
     "[/help/rebuild|fossil rebuild]" after the downgrade
     to get email notifications working again.  If you are not using
     email notification, then the schema change will not affect you in
     any way.
  *  <b>Schema Update Notice #2:</b>
     This release changes how the descriptions of wiki edits are stored
     in the EVENT table, for improved display on timelines.  You must
     run "[/help?cmd=rebuild|fossil rebuild]" to take advantage of
     run "[/help/rebuild|fossil rebuild]" to take advantage of
     this enhancement.  Everything will still work without
     "fossil rebuild", except you will get goofy descriptions of
     wiki updates in the timeline.
  *  Add support for [./chat.md|Fossil chat].
  *  The "[/help?cmd=clone|fossil clone]" command is enhanced so that
  *  The "[/help/clone|fossil clone]" command is enhanced so that
     if the repository filename is omitted, an appropriate name is derived
     from the remote URL and the newly cloned repo is opened.  This makes
     the clone command work more like Git, thus making it easier for
     people transitioning from Git.
  *  Added the --mainbranch option to the [/help?cmd=git|fossil git export]
  *  Added the --mainbranch option to the [/help/git|fossil git export]
     command.
  *  Added the --format option to the
     "[/help?cmd=timeline|fossil timeline]" command.
     "[/help/timeline|fossil timeline]" command.
  *  Enhance the --numstat option on the
     "[/help?cmd=diff|fossil diff]" command so that it shows a total
     "[/help/diff|fossil diff]" command so that it shows a total
     number of lines added and deleted and total number of files
     modified.
  *  Add the "contact" sub-command to [/help?cmd=user|fossil user].
  *  Added commands "[/help?cmd=all|fossil all git export]" and
     "[/help?cmd=all|fossil all git status]".
  *  Add the "contact" sub-command to [/help/user|fossil user].
  *  Added commands "[/help/all|fossil all git export]" and
     "[/help/all|fossil all git status]".
  *  Added the "df=CHECKIN" query parameter to the
     [/help?cmd=/timeline|/timeline page].
     [/help/www/timeline|/timeline page].
  *  Improvements to the "[/sitemap]" page.  Add subpages
     [/sitemap-timeline] and [/sitemap-test].
  *  Better text position in cylinder objects of Pikchr diagrams.
  *  New "details.txt" settings available to custom skins to better control
     the rendering of Pikchr diagrams:
     <ul>
     <li> pikchr-foreground
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



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
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
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
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
1006
1007

1008
1009
1010
1011

1012
1013
1014
1015
1016
1017
1018
1019







-
+



-
+

-
-
+
+





-
+












-
+






-
+












-
+




-
+





-
+

-
+

-
-
-
+
+
+

-
-
-
+
+
+

-
+


-
+



-
-
+
+






-
+


-
+

-
+



-
+




-
+

-
+















-
+








-
+







-
+

-
-
-
+
+
+






-
+






-
+











-
+


-
-
-
+
+
+


-
+



-
+







<h2 id='v2_13'>Changes for Version 2.13 (2020-11-01)</h2>

  *  Added support for [./interwiki.md|interwiki links].
  *  Enable &lt;del&gt; and &lt;ins&gt; markup in  wiki.
  *  Improvements to the Forum threading display.
  *  Added support for embedding [./pikchr.md|pikchr]
     markup in markdown and fossil-wiki content.
  *  The new "[/help?cmd=pikchr|pikchr]" command can render
  *  The new "[/help/pikchr|pikchr]" command can render
     pikchr scripts, optionally pre-processed with
     [/doc/trunk/www/th1.md|TH1] blocks and variables exactly like
     site skins are.
  *  The new [/help?cmd=/pikchrshow|pikchrshow] page provides an
  *  The new [/help/www/pikchrshow|pikchrshow] page provides an
     editor and previewer for pikchr markup.
  *  In [/help?cmd=/wikiedit|/wikiedit] and
     [/help?cmd=/fileedit|/fileedit], Ctrl-Enter can now be used
  *  In [/help/www/wikiedit|/wikiedit] and
     [/help/www/fileedit|/fileedit], Ctrl-Enter can now be used
     initiate a preview and to toggle between the editor and preview
     tabs.
  *  The <tt>/artifact</tt> and <tt>/file</tt> views, when in
     line-number mode, now support interactive selection of a range
     of lines to hyperlink to.
  *  Enhance the [/help?cmd=/finfo|/finfo] webpage so that when query
  *  Enhance the [/help/www/finfo|/finfo] webpage so that when query
     parameters identify both a filename and a checkin, the resulting
     graph tracks the identified file across renames.
  *  The built-in SQLite is updated to an alpha of version 3.34.0, and
     the minimum SQLite version is increased to 3.34.0 because the
     /finfo change in the previous bullet depends on enhancements to
     recursive common table expressions that are only available in
     SQLite 3.34.0 and later.
  *  Countless other minor refinements and documentation improvements.

<h2 id='v2_12'>Changes for Version 2.12.1 (2020-08-20)</h2>

  *  (2.12.1): Fix client-side vulnerabilities discovered by Max Justicz.
  *  Security fix in the "[/help?cmd=git|fossil git export]" command.
  *  Security fix in the "[/help/git|fossil git export]" command.
     The same fix is also backported to version 2.10.1 and 2.11.1.
     New "safety-net" features were added to prevent similar problems
     in the future.
  *  Enhancements to the graph display for cases when there are
     many cherry-pick merges into a single check-in.
     [/timeline?f=2d75e87b760c0a9|Example]
  *  Enhance the [/help?cmd=open|fossil open] command with the new
  *  Enhance the [/help/open|fossil open] command with the new
     --workdir option and the ability to accept a URL as the repository
     name, causing the remote repository to be cloned automatically.
     Do not allow "fossil open" to open in a non-empty working directory
     unless the --keep option or the new --force option is used.
  *  Enhance the markdown formatter to more closely follow the
     [https://spec.commonmark.org/0.29/#emphasis-and-strong-emphasis|CommonMark specification]
     with regard to text highlighting.
     Underscores in the middle of identifiers (ex: fossil_printf())
     no longer need to be escaped.
  *  The markdown-to-html translator can prevent unsafe HTML
     (for example: &lt;script&gt;) on user-contributed pages like forum and
     tickets and wiki.  The admin can adjust this behavior using
     the [/help?cmd=safe-html|safe-html setting] on the Admin/Wiki page.
     the [/help/safe-html|safe-html setting] on the Admin/Wiki page.
     The default is to disallow unsafe HTML everywhere.
     [https://fossil-scm.org/forum/forumpost/3714e6568f|Example].
  *  Added the "collapse" and "expand" capability for long forum posts.
     [https://fossil-scm.org/forum/forumpost/9297029862|Example]
  *  The "[/help?cmd=remote-url|fossil remote]" command now has options for
  *  The "[/help/remote-url|fossil remote]" command now has options for
     specifying multiple persistent remotes with symbolic names.  Currently
     only one remote can be used at a time, but that might change in the
     future.
  *  Add the "Remember me?" checkbox on the login page.  Use a session
     cookie for the login if it is not checked.
  *  Added the experimental "[/help?cmd=hook|fossil hook]" command for
  *  Added the experimental "[/help/hook|fossil hook]" command for
     managing "hook scripts" that run before checkin or after a push.
  *  Enhance the [/help?cmd=revert|fossil revert] command so that it
  *  Enhance the [/help/revert|fossil revert] command so that it
     is able to revert all files beneath a directory.
  *  Add the [/help?cmd=bisect|fossil bisect skip] command.
  *  Add the [/help?cmd=backup|fossil backup] command.
  *  Enhance [/help?cmd=bisect|fossil bisect ui] so that it shows all unchecked
  *  Add the [/help/bisect|fossil bisect skip] command.
  *  Add the [/help/backup|fossil backup] command.
  *  Enhance [/help/bisect|fossil bisect ui] so that it shows all unchecked
     check-ins in between the innermost "good" and "bad" check-ins.
  *  Added the <tt>--reset</tt> flag to the "[/help?cmd=add|fossil add]",
     "[/help?cmd=rm|fossil rm]", and
     "[/help?cmd=addremove|fossil addremove]" commands.
  *  Added the <tt>--reset</tt> flag to the "[/help/add|fossil add]",
     "[/help/rm|fossil rm]", and
     "[/help/addremove|fossil addremove]" commands.
  *  Added the "<tt>--min</tt> <i>N</i>" and "<tt>--logfile</tt> <i>FILENAME</i>"
     flags to the [/help?cmd=backoffice|backoffice] command, as well as other
     flags to the [/help/backoffice|backoffice] command, as well as other
     enhancements to make the backoffice command a viable replacement for
     automatic backoffice.  Other incremental backoffice improvements.
  *  Added the [/help?cmd=/fileedit|/fileedit page], which allows
  *  Added the [/help/www/fileedit|/fileedit page], which allows
     editing of text files online. Requires explicit activation by
     a setup user.
  *  Translate built-in help text into HTML for display on web pages.
     [/help?cmd=help|Example].
  *  On the [/help?cmd=/timeline|/timeline] webpage, the combination
     [/help/help|Example].
  *  On the [/help/www/timeline|/timeline] webpage, the combination
     of query parameters "p=CHECKIN" and "bt=ANCESTOR" draws all
     ancestors of CHECKIN going back to ANCESTOR.  For example,
     [/timeline?p=202006271506&bt=version-2.11] shows all ancestors
     of the checkin that occurred on 2020-06-27 15:06 going back to
     the 2.11 release.
  *  Update the built-in SQLite so that the
     "[/help?cmd=sql|fossil sql]" command supports new output
     "[/help/sql|fossil sql]" command supports new output
     modes ".mode box" and ".mode json".
  *  Add the "<tt>obscure()</tt>" SQL function to the
     "[/help?cmd=sql|fossil sql]" command.
     "[/help/sql|fossil sql]" command.
  *  Added virtual tables "<tt>helptext</tt>" and "<tt>builtin</tt>" to
     the "[/help?cmd=sql|fossil sql]" command, providing access to the
     the "[/help/sql|fossil sql]" command, providing access to the
     dispatch table including all help text, and the builtin data files,
     respectively.
  *  [./delta_format.wiki|Delta compression] is now applied to forum edits.
  *  The [/help?cmd=/wikiedit|wiki editor] has been modernized and is
  *  The [/help/www/wikiedit|wiki editor] has been modernized and is
     now Ajax-based. The WYSIWYG editing option for Fossil-format wiki
     pages was removed. (Please let us know, via the site's Forum menu,
     if that removal unduly impacts you.) This also changes the semantics
     of the wiki "Sandbox": that pseudo-page may be freely edited but
     no longer saved via the UI (the [/help?cmd=wiki|wiki CLI command]
     no longer saved via the UI (the [/help/wiki|wiki CLI command]
     can, though).
  *  The [/help?cmd=allow-symlinks|allow-symlinks setting] no longer
  *  The [/help/allow-symlinks|allow-symlinks setting] no longer
     syncs. It must be activated individually on any clones which require
     symlinks.
  *  Countless documentation enhancements.

<h2 id='v2_11'>Changes for Version 2.11 (2020-05-25)</h2>

  *  (2.11.2): Backport security fixes from 2.12.1
  *  (2.11.1): Backport security fix for the "fossil git export" command.
  *  Support [/md_rules|Markdown] in the default ticket configuration.
  *  Timestamp strings in [./checkin_names.wiki|object names]
     can now omit punctation.  So, for example, "202004181942" and
     "2020-04-18 19:42" mean the same thing.
  *  Enhance backlink processing so that it works with Markdown-formatted
     tickets and so that it works for wiki pages.
     Ticket [a3572c6a5b47cd5a].
     <ul><li> "[/help?cmd=rebuild|fossil rebuild]" is needed to
     <ul><li> "[/help/rebuild|fossil rebuild]" is needed to
     take full advantage of this fix.  Fossil will continue
     to work without the rebuild, but the new backlinks will be missing.</ul>
  *  The algorithm for finding the
     [./tech_overview.wiki#configloc|location of the configuration database]
     is enhanced to be XDG-compliant.
  *  Add a hide/show feature to
     [./wikitheory.wiki#assocwiki|associated wiki] display on
     check-in and branch information pages.
  *  Enhance the "[/help?cmd=info|fossil info]" command so that it
  *  Enhance the "[/help/info|fossil info]" command so that it
     works with no arguments even if not within an open check-out.
  *  Many improvements to the forum and especially email notification
     of forum posts, in response to community feedback after switching
     SQLite support from a mailing list over to the forum.
  *  Minimum length of a self-registered user ID increased from 3 to 6
     characters.
  *  When the "vfx" query parameter is used on the
     "[/help?cmd=/timeline|/timeline]" page, it causes the complete
     "[/help/www/timeline|/timeline]" page, it causes the complete
     text of forum posts to be displayed.
  *  Rework the "[/help?cmd=grep|fossil grep]" command to be more useful.
  *  Expose the [/help?cmd=redirect-to-https|redirect-to-https]
     setting to the [/help?cmd=settings|settings] command.
  *  Rework the "[/help/grep|fossil grep]" command to be more useful.
  *  Expose the [/help/redirect-to-https|redirect-to-https]
     setting to the [/help/settings|settings] command.
  *  Improve support for CGI on IIS web servers.
  *  The [./serverext.wiki|/ext page] can now render index files,
     in the same way as the embedded docs.
  *  Most commands now support the Unix-conventional "<tt>--</tt>"
     flag to treat all following arguments as filenames
     instead of flags.
  *  Added the [/help?cmd=mimetypes|mimetypes config setting]
  *  Added the [/help/mimetypes|mimetypes config setting]
     (versionable) to enable mimetype overrides and custom definitions.
  *  Add an option on the /Admin/Timeline setup page to set a default
     timeline style other than "Modern".
  *  In [./embeddeddoc.wiki|embedded documentation], hyperlink URLs
     of the form "/doc/$CURRENT/..." the "$CURRENT" text is translated
     into the check-in hash for the document currently being viewed.
  *  Added the [/help?cmd=/phantoms|/phantoms] webpage that shows all
  *  Added the [/help/www/phantoms|/phantoms] webpage that shows all
     phantom artifacts.
  *  Enhancements to phantom processing to try to reduce
     bandwidth-using chatter about phantoms on the sync protocol.
  *  Security: Fossil now assumes that the schema of every
     database it opens has been tampered with by an adversary and takes
     extra precautions to ensure that such tampering is harmless.
  *  Security: Fossil now puts the Content-Security-Policy in the
     HTTP reply header, in addition to also leaving it in the
     HTML &lt;head&gt; section, so that it is always available, even
     if a custom skin overrides the HTML &lt;head&gt; and omits
     the CSP in the process.
  *  Output of the [/help?cmd=diff|fossil diff -y] command automatically
  *  Output of the [/help/diff|fossil diff -y] command automatically
     adjusts according to the terminal width.
  *  The Content-Security-Policy is now set using the
     [/help?cmd=default-csp|default-csp setting].
  *  Merge conflicts caused via the [/help?cmd=merge|merge] and
     [/help?cmd=update|update] commands no longer leave temporary
     [/help/default-csp|default-csp setting].
  *  Merge conflicts caused via the [/help/merge|merge] and
     [/help/update|update] commands no longer leave temporary
     files behind unless the new <tt>--keep-merge-files</tt> flag
     is used.
  *  The [/help?cmd=/artifact_stats|/artifact_stats page] is now accessible
  *  The [/help/www/artifact_stats|/artifact_stats page] is now accessible
     to all users if the new "artifact_stats_enable" setting is turned
     on.  There is a new checkbox under the /Admin/Access menu to turn
     that capability on and off.
  *  Add the [/help?cmd=tls-config|fossil tls-config] command for viewing
  *  Add the [/help/tls-config|fossil tls-config] command for viewing
     the TLS configuration and the list of SSL Cert exceptions.
  *  Captchas all include a button to read the captcha using an audio
     file, so that they can be completed by the visually impaired.
  *  Stop using the IP address as part of the login cookie.
  *  Bug fix: fix the SSL cert validation logic so that if an exception
     is allowed for particular site, the exception expires as soon as the
     cert changes values.
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

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
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
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







-
+









-
+





-
+
















-
+


-
+












-
-
-
+
+
+

-
-
+
+



-
+



-
+



-
+
















-
-
+
+






-
+







  *  Many minor enhancements to existing features.

<h2 id='v2_10'>Changes for Version 2.10 (2019-10-04)</h2>

  *  (2.10.2): backport security fixes from 2.12.1
  *  (2.10.1): backport security fix for the "fossil git export" command.
  *  Added support for [./serverext.wiki|CGI-based Server Extensions].
  *  Added the [/help?cmd=repolist-skin|repolist-skin] setting used to
  *  Added the [/help/repolist-skin|repolist-skin] setting used to
     add style to repository list pages.
  *  Enhance the hierarchical display of Forum threads to do less
     indentation and to provide links back to the previous message
     in the thread.  Provide sequential numbers for all messages in
     a forum thread.
  *  Add support for fenced code blocks and improved hyperlink
     processing to the [/md_rules|markdown formatter].
  *  Add support for hyperlinks to wiki pages in the
     [/md_rules|markdown formatter].
  *  Enhance the [/help?cmd=/stat|/stat] page so that it gives the
  *  Enhance the [/help/www/stat|/stat] page so that it gives the
     option to show a breakdown of forum posts.
  *  The special check-in name "merge-in:BRANCH" means the source of
     the most recent merge-in from the parent branch of BRANCH.
  *  Add hyperlinks to branch-diffs on the /info page and from
     timelines of a branch.
  *  Add graphical context on the [/help?cmd=/vdiff|/vdiff] page.
  *  Add graphical context on the [/help/www/vdiff|/vdiff] page.
  *  Uppercase query parameters, POST parameters, and cookie names are
     converted to all lowercase and entered into the parameter set,
     instead of being discarded.
  *  Change the default [./hashpolicy.wiki|hash policy] to SHA3.
  *  Timeout [./server/any/cgi.md|CGI requests] after 300 seconds, or
     some other value set by the
     [./cgi.wiki#timeout|"timeout:" property] in the CGI script.
  *  The check-in lock interval is reduced from 24 hours to 60 seconds,
     though the interval is now configurable using a setting.
     An additional check for conflicts is added after interactive
     check-in comment entry, to compensate for the reduced lock interval.
  *  Performance optimizations.
  *  Many documentation improvements.

<h2 id='v2_9'>Changes for Version 2.9 (2019-07-13)</h2>

  *  Added the [/help?cmd=git|fossil git export] command and instructions
  *  Added the [/help/git|fossil git export] command and instructions
     for [./mirrortogithub.md|creating a GitHub mirror of a Fossil project].
  *  Improved handling of relative hyperlinks on the
     [/help?cmd=/artifact|/artifact] pages for wiki. For example,
     [/help/www/artifact|/artifact] pages for wiki. For example,
     hyperlinks and the lizard &lt;img&gt; now work correctly
     for both [/artifact/2ff24ab0887cf522] and
     [/doc/0d7ac90d575004c2415/www/index.wiki].
  *  Enhancements to the timeline graph layout, to show more information
     with less clutter.
  *  Added tool-tips to the /timeline graph.  On by default but can be
     disabled by setting the "Tooltip dwell time" to 0 in the timeline
     configuration.
  *  Copy buttons added to various check-in hash and branch name links.
  *  Double-clicking on a /timeline graph node now jumps to the /info page
     for the check-in.  So, repurpose the timestamp hyperlink to show all
     activity around that check-in in time.
  *  Added the [/help?cmd=touch|fossil touch] command, and the --setmtime
     option on the [/help?cmd=open|fossil open] and
     [/help?cmd=update|fossil update] commands.
  *  Added the [/help/touch|fossil touch] command, and the --setmtime
     option on the [/help/open|fossil open] and
     [/help/update|fossil update] commands.
  *  Many documentation enhancements.
  *  For the "[/help?cmd=update|fossil update]" and
     "[/help?cmd=checkout|fossil checkout]" commands, if a
  *  For the "[/help/update|fossil update]" and
     "[/help/checkout|fossil checkout]" commands, if a
     managed file is removed because it is no longer part of the target
     check-in and the directory containing the file is empty after the
     file is removed and the directory is not the current working
     directory and is not on the [/help?cmd=empty-dirs|empty-dirs]
     directory and is not on the [/help/empty-dirs|empty-dirs]
     list, then also remove the directory.
  *  Update internal Unicode character tables, used in regular expression
     handling, from version 11.0 to 12.1.
  *  In "[/help?cmd=regexp|fossil regexp]", "[/help?cmd=grep|fossil grep]"
  *  In "[/help/regexp|fossil regexp]", "[/help/grep|fossil grep]"
     and the TH1 "regexp" command, the -nocase option now removes multiple
     diacritics from the same character (derived from SQLite's
     remove_diacritics=2)
  *  Added the [/help?cmd=/secureraw|/secureraw] page that requires the
  *  Added the [/help/www/secureraw|/secureraw] page that requires the
     complete SHA1 or SHA3 hash, not just a prefix, before it will deliver
     content.
  *  Accept purely numeric ISO8601 date/time strings as long as they
     do not conflict with a hash.  Example: "20190510134217" instead of
     "2019-05-10 13:42:17".  This helps keep URLs shorter and less
     complicated
  *  Support both "1)" and "1." for numbered lists in markdown, as
     commonmark does.
  *  The sync and clone HTTP requests omit the extra /xfer path element
     from the end of the request URI. All servers since 2010 know that
     the HTTP request is for a sync or clone from the mimetype so the
     extra path element is not needed.
  *  If an automatic sync gets a permanent redirect request, then update
     the saved remote URL to the new address.
  *  Temporary filenames (for example used for external "diff" commands)
     try to preserve the suffix of the original file.
  *  Added the [/help?cmd=/thisdayinhistory|/thisdayinhistory] web page.
  *  Enhanced parsing of [/help?cmd=/timeline|/timeline] query parameters
  *  Added the [/help/www/thisdayinhistory|/thisdayinhistory] web page.
  *  Enhanced parsing of [/help/www/timeline|/timeline] query parameters
     "ymd=", "ym=", and "yw=".  All arguments are option (in which case they
     default to the current time) and all accept ISO8601 date/times without
     punctuation.
  *  Automatically disapprove pending moderation requests for a user when
     that user is deleted.  This helps in dealing with spam-bots.
  *  Improvements to the "Capability Summary" section in the
     [/help?cmd=/secaudit0|Security Audit] web-page.
     [/help/www/secaudit0|Security Audit] web-page.
  *  Use new "ci-lock" and "ci-lock-failed" pragmas in the
     [./sync.wiki|sync protocol] to try to prevent accident forks
     caused by concurrent commits when operating in auto-sync mode.
  *  Fix a bug ([https://fossil-scm.org/forum/forumpost/c51b9a1169|details])
     that can cause repository databases to be overwritten with debugging
     output, thus corrupting the repository. This is only a factor when
     CGI debugging is enabled, and even then is a rare occurrence, but it is
961
962
963
964
965
966
967
968

969
970
971
972
973
974
975
1226
1227
1228
1229
1230
1231
1232

1233
1234
1235
1236
1237
1238
1239
1240







-
+







  *  There is an optional "js" file for each skin that can be used to
     hold javascript.  This file can be loaded by reference or can be
     included in the header or footer.
  *  Add the [./backoffice.md|backoffice].
  *  Update internal Unicode character tables, used in regular expression
     handling, from version 10.0 to 11.0.
  *  Improvements to the "Security Audit" administration page
  *  Add the [/help?cmd=branch|fossil branch current] command.
  *  Add the [/help/branch|fossil branch current] command.
  *  Add the [./grep.md|grep] command.
  *  Update the built-in SQLite to version 3.25.1.
  *  Some code and interfaces are in place to support sending and
     receiving email directly via SMTP, but this feature is not yet
     complete or ready for production use.
  *  The `mv-rm-files` setting is now compiled into Fossil in the
     default Fossil configuration; no longer must you say
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
1249
1250
1251
1252
1253
1254
1255



1256
1257
1258
1259
1260

1261
1262
1263
1264

1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284

1285

1286
1287
1288
1289
1290
1291
1292







-
-
-
+
+
+


-
+



-
+



















-
+
-







     repository.  This fix is the main reason for the current release.
  *  Added the new "Classic" timeline viewing mode.  "Classic" is the
     same as "Verbose" in the previous release.  The "Verbose" mode is
     now like "Compact" except the extra check-in details are shown by
     default.
  *  Add support for ETags:, Last-Modified:, and If-Modified-Since:
     cache control mechanisms.
  *  Enhance the [/help?cmd=/tarball|/tarball],
     [/help?cmd=/zip|/zip], and
     [/help?cmd=/sqlar|/sqlar] pages so that the checkin
  *  Enhance the [/help/www/tarball|/tarball],
     [/help/www/zip|/zip], and
     [/help/www/sqlar|/sqlar] pages so that the checkin
     name to be downloaded can be expressed as part of the URI,
     and without the need for query parameters.
  *  On the [/help?cmd=/timeline|/timeline] webpage, add the days=N
  *  On the [/help/www/timeline|/timeline] webpage, add the days=N
     query parameter and enhance the ymd=DATE and yw=DATE query parameters
     to accept 'now' as an argument to show the latest day or week.
  *  In the web page that comes up in response to the
     [/help?cmd=all|fossil all ui] command, show the last modification
     [/help/all|fossil all ui] command, show the last modification
     time for each repository, and allow click-to-sort on the modification
     time column.
  *  In the tarball cache replacement algorithm, give extra weight to
     tarballs that have been accessed more than once.
  *  Additional defenses against web-based attacks.  There have not been
     any known vulnerabilities.  We are just being paranoid.
  *  Update the built-in SQLite to an alpha version of 3.24.0.

<h2 id='v2_5'>Changes for Version 2.5 (2018-02-07)</h2>

  *  Numerous enhancements to the look and feel of the web interface.
     Especially:  Added separate "Modern", "Compact", "Verbose", and
     "Columnar" view options on timelines.
  *  Common display settings (such as the "view" option and the number
     of rows in a timeline) are held in a cookie and thus persist
     across multiple pages.
  *  Rework the skin editing process so that changes are implemented
     on one of nine /draft pages, evaluated, then merged back to the
     default.
  *  Added the [https://fossil-scm.org/skins/ardoise/timeline|Ardoise]
  *  Added the [/timeline?skin=ardoise&once|Ardoise] skin.
     skin.
  *  Fix the "fossil server" command on Unix to be much more responsive
     to multiple simultaneous web requests.
  *  Use the IPv6 stack for the "fossil ui" and "fossil server"
     commands on Windows.
  *  Support for [https://sqlite.org/sqlar|SQL Archives] as a download
     option.
  *  Fossil now automatically generates the
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
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
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181


1182
1183
1184
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
1210
1211
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
1249
1250
1251
1252

1253
1254
1255
1256

1257
1258

1259
1260

1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271

1272
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



1320
1321

1322
1323
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

1359
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
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
1423


1424
1425

1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440


1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458

1459
1460
1461
1462
1463
1464
1465
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






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
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
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
1423
1424
1425
1426
1427
1428
1429

1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443


1444
1445
1446
1447
1448
1449

1450
1451


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
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
1517
1518
1519

1520
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
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
1632
1633
1634
1635
1636
1637
1638
1639
1640


1641
1642
1643
1644
1645

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
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







-
-
+
+

-
-
+
+








-
+





-
-
-
-
-
-
+
+
+
+
+
+











-
+




-
+






-
+









-
-
+
+












-
-
+
+











-
+








-
-
+
+

-
+
















-
+













-
-
+
+




-
+

-
-
+
+







-
-
+
+






-
+






-
+




-
-
+
+


-
-
-
-
+
+
+
+


-
+











-
-
-
-
+
+
+
+









-
+



-
+

-
+

-
+










-
+

-
-
-
-
+
+
+
+

-
+


-
+




-
-
+
+


-
+

-
+

-
+

-
-
+
+



-
+









-
+





-
-
-
+
+
+

-
+


-
-
+
+

-
+


-
+




-
+







-
+



-
-
+
+




-
+




-
+




-
+

-
-
+
+










-
-
+
+



-
+






-
-
+
+











-
-
-
-
+
+
+
+













-
+


-
-
+
+

-
+













-
-
+
+

















-
+








  *  New feature: URL Aliases.  URL Aliases allow an administrator
     to define their own URLs on the web interface that are rewritten to
     built-in URLs with specific parameters.  Create and configure URL Aliases
     using the /Setup/URL_Aliases menu option in the web interface.
  *  Add tech-note search capability.
  *  Add the -r|--revision and -o|--origin options to the
     [/help?cmd=annotate|annotate] command.
  *  Add the origin= query parameter to the [/help?cmd=/annotate|/annotate]
     [/help/annotate|annotate] command.
  *  Add the origin= query parameter to the [/help/www/annotate|/annotate]
     webpage.
  *  The [/help?cmd=annotate|fossil annotate] command and the
     [/help?cmd=/annotate|/annotate] web page go backwards in time as far
  *  The [/help/annotate|fossil annotate] command and the
     [/help/www/annotate|/annotate] web page go backwards in time as far
     as can be computed in 30 milliseconds by default, rather than stopping
     after 20 steps.  The new limit= query parameter or the --limit command-line
     option can be used to alter this timeout.
  *  Provide separate [/help#settings|on-line help screens for each setting].
  *  Back out support for the --no-dir-symlinks option
  *  Remove support from the legacy configuration sync protocol.  The only
     way now to do a configuration push or pull is to use the new protocol that
     was added in 2011.
  *  Add the from= and to= query parameters to [/help?cmd=/fdiff|/fdiff]
  *  Add the from= and to= query parameters to [/help/www/fdiff|/fdiff]
     in order to get a diff of two files in the same check-in.
  *  Fix the "ssh://" protocol to prevent an attack whereby the attacker convinces
     a victim to run a "clone" with a dodgy URL and thereby gains access to their
     system.
  *  Provide a checkbox that will temporarily disable all ad-units.
  *  Improvements to the [/help?cmd=/stat|/stat] page
  *  Various new hyperlinks to the [/help?cmd=/bloblist|/bloblist]
     and [/help?cmd=/bigbloblist|/bigbloblist] pages.
  *  Correct the [/help?cmd=/doc|/doc] page to support read-only repositories.
  *  Correct [/help?cmd=/zip|/zip], [/help?cmd=/tarball|/tarball],
     [/help?cmd=zip|zip], and [/help?cmd=tarball|tarball] pages and commands to
  *  Improvements to the [/help/www/stat|/stat] page
  *  Various new hyperlinks to the [/help/www/bloblist|/bloblist]
     and [/help/www/bigbloblist|/bigbloblist] pages.
  *  Correct the [/help/www/doc|/doc] page to support read-only repositories.
  *  Correct [/help/www/zip|/zip], [/help/www/tarball|/tarball],
     [/help/zip|zip], and [/help/tarball|tarball] pages and commands to
     honor the versioned manifest setting when outside of an open checkout
     directory.
  *  The admin-log and access-log settings are now on by default for
     new repositories.
  *  Update the built-in SQLite to version 3.21.0.

<h2 id='v2_3'>Changes for Version 2.3 (2017-07-21)</h2>

  *  Update the built-in SQLite to version 3.20.0 (beta).
  *  Update internal Unicode character tables, used in regular expression
     handling, from version 9.0 to 10.0.
  *  Show the last-sync-URL on the [/help?cmd=/urllist|/urllist] page.
  *  Show the last-sync-URL on the [/help/www/urllist|/urllist] page.
  *  Added the "Event Summary" activity report.
     [/reports?type=ci&view=lastchng|example]
  *  Added the "Security Audit" page, available to administrators only
  *  Added the Last Login time to the user list page, for administrators only
  *  Added the --numstat option to the [/help?cmd=diff|fossil diff] command
  *  Added the --numstat option to the [/help/diff|fossil diff] command
  *  Limit the size of the heap and stack on unix systems, as a proactive
     defense against the
     [https://www.qualys.com/2017/06/19/stack-clash/stack-clash.txt|Stack Clash]
     attack.
  *  Fix "database locked" warnings caused by "PRAGMA optimize".
  *  Fix a potential XSS vulnerability on the
     [/help?cmd=/help|/help] webpage.
     [/help/www/help|/help] webpage.
  *  Documentation updates

<h2 id='v2_2'>Changes for Version 2.2 (2017-04-11)</h2>

  *  GIT comment tags are now handled by Fossil during import/export.
  *  Show the content of README files on directory listings.
     ([/file/skins|example])
  *  Support for Basic Authentication if enabled (default off).
  *  Show the hash algorithms used on the
     [/help?cmd=/rcvfromlist|/rcvfromlist] page.
  *  The [/help?cmd=/tarball|/tarball] and [/help?cmd=/zip|/zip] pages
     [/help/www/rcvfromlist|/rcvfromlist] page.
  *  The [/help/www/tarball|/tarball] and [/help/www/zip|/zip] pages
     now use the the r= query parameter
     to select which check-in to deliver.  The uuid= query parameter
     is still accepted for backwards compatibility.
  *  Update the built-in SQLite to version 3.18.0.
  *  Run "[https://www.sqlite.org/pragma.html#pragma_optimize|PRAGMA optimize]"
     on the database connection as it is closing.

<h2 id='v2_1'>Changes for Version 2.1 (2017-03-10)</h2>

  *  Add support for [./hashpolicy.wiki|hash policies] that control which
     of the Hardened-SHA1 or SHA3-256 algorithms is used to name new
     artifacts.
  *  Add the "gshow" and "gcat" subcommands to [/help?cmd=stash|fossil stash].
  *  Add the [/help?cmd=/juvlist|/juvlist] web page and use it to construct
  *  Add the "gshow" and "gcat" subcommands to [/help/stash|fossil stash].
  *  Add the [/help/www/juvlist|/juvlist] web page and use it to construct
     the [/uv/download.html|Download Page] of the Fossil self-hosting website
     using Ajax.

<h2 id='v2_0'>Changes for Version 2.0 (2017-03-03)</h2>

  *  Use the
     [https://github.com/cr-marcstevens/sha1collisiondetection|hardened SHA1]
     implementation by Marc Stevens and Dan Shumow.
  *  Add the ability to read and understand
     [./fileformat.wiki#names|artifact names] that are based on SHA3-256
     rather than SHA1, but do not actually generate any such names.
  *  Added the [/help?cmd=sha3sum|sha3sum] command.
  *  Added the [/help/sha3sum|sha3sum] command.
  *  Update the built-in SQLite to version 3.17.0.

<h2 id='v1_37'>Changes for Version 1.37 (2017-01-16)</h2>

  *  Add checkbox widgets to various web pages.  See [/technote/8d18bf27e9|
     this technote] for more information.  To get the checkboxes to look as
     intended, you must update the CSS in your repository and all clones.
  *  Add the [/help/all|fossil all ui] command
  *  Add the [/help?cmd=/file|/file] webpage
  *  Enhance the [/help?cmd=/brlist|/brlist] webpage to make use of branch colors.
  *  Add the [/help/www/file|/file] webpage
  *  Enhance the [/help/www/brlist|/brlist] webpage to make use of branch colors.
  *  Add support for the ms=EXACT|LIKE|GLOB|REGEXP query parameter on the
     [/help?cmd=/timeline|/timeline] webpage, with associated form widgets.
     [/help/www/timeline|/timeline] webpage, with associated form widgets.
  *  Enhance the [/help/changes|changes] and [/help/status|status] commands
     with many new filter options so that specific kinds of changes can be
     found without having to pipe through grep or sed.
  *  Enhanced the [/help/sqlite3|fossil sql] command so that it opens the
     [./tech_overview.wiki#localdb|checkout database] and the
     [./tech_overview.wiki#configdb|configuration database] in addition to the
     repository database.
  *  TH1 enhancements:
     <ul><li>Add <nowiki>[unversioned content]</nowiki> command.</li>
     <li>Add <nowiki>[unversioned list]</nowiki> command.</li>
     <li>Add project_description variable.</li>
     </ul>
  *  Rename crnl-glob [/help/settings|setting] to crlf-glob, but keep
     crnl-glob as a compatibility alias.
  *  Added the --command option to the [/help/diff|diff] command.
  *  Fix a C99-ism that prevents the 1.36 release from building with MSVC.
  *  Fix [/help?cmd=ticket|ticket set] when using the "+" prefix with fields
  *  Fix [/help/ticket|ticket set] when using the "+" prefix with fields
     from the "ticketchng" table.
  *  Remove the "fusefs" command from builds that do not have the underlying
     support enabled.
  *  Fixes for incremental git import/export.
  *  Minor security enhancements to
     [./encryptedrepos.wiki|encrypted repositories].
  *  Update the built-in SQLite to version 3.16.2.
  *  Update the built-in Zlib to version 1.2.11.


<h2 id='v1_36'>Changes for Version 1.36 (2016-10-24)</h2>

  *  Add support for [./unvers.wiki|unversioned content],
     the [/help?cmd=unversioned|fossil unversioned] command and the
     [/help?cmd=/uv|/uv] and [/uvlist] web pages.
     the [/help/unversioned|fossil unversioned] command and the
     [/help/www/uv|/uv] and [/uvlist] web pages.
  *  The [/uv/download.html|download page] is moved into
     [./unvers.wiki|unversioned content] so that the self-hosting Fossil
     websites no longer uses any external content.
  *  Added the "Search" button to the graphical diff generated by
     the --tk option on the [/help?cmd=diff|diff] command.
     the --tk option on the [/help/diff|diff] command.
  *  Added the "--checkin VERSION" option to the
     [/help?cmd=diff|diff] command.
  *  Various performance enhancements to the [/help?cmd=diff|diff] command.
     [/help/diff|diff] command.
  *  Various performance enhancements to the [/help/diff|diff] command.
  *  Update internal Unicode character tables, used in regular expression
     handling, from version 8.0 to 9.0.
  *  Update the built-in SQLite to version 3.15. Fossil now requires
     the SQLITE_DBCONFIG_MAINDBNAME interface of SQLite which is only available
     in SQLite version 3.15 and later and so Fossil will not work with
     earlier SQLite versions.
  *  Fix [https://www.mail-archive.com/fossil-users@lists.fossil-scm.org/msg23618.html|multi-line timeline bug]
  *  Enhance the [/help?cmd=purge|fossil purge] command.
  *  New command [/help?cmd=shell|fossil shell].
  *  Enhance the [/help/purge|fossil purge] command.
  *  New command [/help/shell|fossil shell].
  *  SQL parameters whose names are all lower-case in Ticket Report SQL
     queries are filled in using HTTP query parameter values.
  *  Added support for [./childprojects.wiki|child projects] that are
     able to pull from their parent but not push.
  *  Added the -nocomplain option to the TH1 "query" command.
  *  Added support for the chng=GLOBLIST query parameter on the
     [/help?cmd=/timeline|/timeline] webpage.
     [/help/www/timeline|/timeline] webpage.

<h2 id='v1_35'>Changes for Version 1.35 (2016-06-14)</h2>

  *  Enable symlinks by default on all non-Windows platforms.
  *  Enhance the [/md_rules|Markdown formatting] so that hyperlinks that begin
     with "/" are relative to the root of the Fossil repository.
  *  Rework the [/help?cmd=/setup_ulist|/setup_list page] (the User List page)
  *  Rework the [/help/www/setup_ulist|/setup_list page] (the User List page)
     to display all users in a click-to-sort table.
  *  Fix backslash-octal escape on filenames while importing from git
  *  When markdown documents begin with &lt;h1&gt; HTML elements, use that
     header at the document title.
  *  Added the [/help?cmd=/bigbloblist|/bigbloblist page].
  *  Enhance the [/help?cmd=/finfo|/finfo page] so that when it is showing
  *  Added the [/help/www/bigbloblist|/bigbloblist page].
  *  Enhance the [/help/www/finfo|/finfo page] so that when it is showing
     the ancestors of a particular file version, it only shows direct
     ancestors and omits changes on branches, thus making it show the same set
     of ancestors that are used for [/help?cmd=/blame|/blame].
  *  Added the --page option to the [/help?cmd=ui|fossil ui] command
  *  Added the [/help?cmd=bisect|fossil bisect ui] command
  *  Enhanced the [/help?cmd=diff|fossil diff] command so that it accepts
     of ancestors that are used for [/help/www/blame|/blame].
  *  Added the --page option to the [/help/ui|fossil ui] command
  *  Added the [/help/bisect|fossil bisect ui] command
  *  Enhanced the [/help/diff|fossil diff] command so that it accepts
     directory names as arguments and computes diffs on all files contained
     within those directories.
  *  Fix the [/help?cmd=add|fossil add] command so that it shows "SKIP" for
  *  Fix the [/help/add|fossil add] command so that it shows "SKIP" for
     files added that were already under management.
  *  TH1 enhancements:
     <ul><li>Add <nowiki>[array exists]</nowiki> command.</li>
     <li>Add minimal <nowiki>[array names]</nowiki> command.</li>
     <li>Add tcl_platform(engine) and tcl_platform(platform) array
     elements.</li>
     </ul>
  *  Get autosetup working with MinGW.
  *  Fix autosetup detection of zlib in the source tree.
  *  Added autosetup detection of OpenSSL when it may be present under the
     "compat" subdirectory of the source tree.
  *  Added the [/help?cmd=reparent|fossil reparent] command
  *  Added --include and --exclude options to [/help?cmd=tarball|fossil tarball]
     and [/help?cmd=zip|fossil zip] and the in= and ex= query parameters to the
     [/help?cmd=/tarball|/tarball] and [/help?cmd=/zip|/zip] web pages.
  *  Added the [/help/reparent|fossil reparent] command
  *  Added --include and --exclude options to [/help/tarball|fossil tarball]
     and [/help/zip|fossil zip] and the in= and ex= query parameters to the
     [/help/www/tarball|/tarball] and [/help/www/zip|/zip] web pages.
  *  Add support for [./encryptedrepos.wiki|encrypted Fossil repositories].
  *  If the FOSSIL_PWREADER environment variable is set, then use the program it
     names in place of getpass() to read passwords and passphrases
  *  Option --baseurl now works on Windows.
  *  Numerous documentation improvements.
  *  Update the built-in SQLite to version 3.13.0.

<h2 id='v1_34'>Changes for Version 1.34 (2015-11-02)</h2>

  *  Make the [/help?cmd=clean|fossil clean] command undoable for files less
  *  Make the [/help/clean|fossil clean] command undoable for files less
     than 10MiB.
  *  Update internal Unicode character tables, used in regular expression
     handling, from version 7.0 to 8.0.
  *  Add the new [/help?cmd=amend|amend] command which is used to modify
  *  Add the new [/help/amend|amend] command which is used to modify
     tags of a "check-in".
  *  Fix bug in [/help?cmd=import|import] command, handling version 3 of
  *  Fix bug in [/help/import|import] command, handling version 3 of
     the svndump format for subversion.
  *  Add the [/help?cmd=all|all cache] command.
  *  Add the [/help/all|all cache] command.
  *  TH1 enhancements:
     <ul><li>Add minimal <nowiki>[lsearch]</nowiki> command. Only exact
     case-sensitive matching is supported.</li>
     <li>Add the <nowiki>[glob_match]</nowiki>, <nowiki>[markdown]</nowiki>,
     <nowiki>[dir]</nowiki>, and <nowiki>[encode64]</nowiki> commands.</li>
     <li>Add the <nowiki>[tclIsSafe] and [tclMakeSafe]</nowiki> commands to
     the Tcl integration subsystem.</li>
     <li>Add 'double', 'integer', and 'list' classes to the
     <nowiki>[string is]</nowiki> command.</li>
     </ul>
  *  Add the --undo option to the [/help?cmd=diff|diff] command.
  *  Add the --undo option to the [/help/diff|diff] command.
  *  Build-in Antirez's "linenoise" command-line editing library for use with
     the [/help?cmd=sqlite3|fossil sql] command on Unix platforms.
  *  Add [/help?cmd=stash|stash cat] as an alias for the
     [/help?cmd=stash|stash show] command.
  *  Automatically pull before [/help?cmd=merge|fossil merge] when auto-sync
     the [/help/sqlite3|fossil sql] command on Unix platforms.
  *  Add [/help/stash|stash cat] as an alias for the
     [/help/stash|stash show] command.
  *  Automatically pull before [/help/merge|fossil merge] when auto-sync
     is enabled.
  *  Fix --hard option to [/help?cmd=mv|fossil mv] and [/help?cmd=rm|fossil rm]
  *  Fix --hard option to [/help/mv|fossil mv] and [/help/rm|fossil rm]
     to enable them to work properly with certain relative paths.
  *  Change the mimetype for ".n" and ".man" files to text/plain.
  *  Display improvements in the [/help?cmd=bisect|fossil bisect chart] command.
  *  Display improvements in the [/help/bisect|fossil bisect chart] command.
  *  Updated the built-in SQLite to version 3.9.1 and activated JSON1 and FTS5
     support (both currently unused within Fossil).

<h2 id='v1_33'>Changes for Version 1.33 (2015-05-23)</h2>
  *  Improved fork detection on [/help?cmd=update|fossil update],
     [/help?cmd=status|fossil status] and related commands.
  *  Improved fork detection on [/help/update|fossil update],
     [/help/status|fossil status] and related commands.
  *  Change the default skin to what used to be called "San Francisco Modern".
  *  Add the [/repo-tabsize] web page
  *  Add [/help?cmd=import|fossil import --svn], for importing a subversion
  *  Add [/help/import|fossil import --svn], for importing a subversion
     repository into fossil which was exported using "svnadmin dump".
  *  Add the "--compress-only" option to [/help?cmd=rebuild|fossil rebuild].
  *  Add the "--compress-only" option to [/help/rebuild|fossil rebuild].
  *  Use a pie chart on the [/reports?view=byuser] page.
  *  Enhanced [/help?cmd=clean|fossil clean --verily] so that it ignores
  *  Enhanced [/help/clean|fossil clean --verily] so that it ignores
     keep-glob and ignore-glob settings.  Added the -x alias for --verily.
  *  Add the --soft and --hard options to [/help?cmd=rm|fossil rm] and
     [/help?cmd=mv|fossil mv].  The default is still --soft, but that is
  *  Add the --soft and --hard options to [/help/rm|fossil rm] and
     [/help/mv|fossil mv].  The default is still --soft, but that is
     now configurable at compile-time or by the mv-rm-files setting.
  *  Improved ability to [./customgraph.md|customize the timeline graph].
  *  Improvements to the [/sitemap] page.
  *  Automatically adjust the [/help?cmd=timeline|CLI timeline] to the terminal
  *  Automatically adjust the [/help/timeline|CLI timeline] to the terminal
     width on Linux.
  *  Added <nowiki>[info commands] and [info vars]</nowiki> commands to TH1.
     These commands perform the same function as their Tcl counterparts,
     except they do not accept a pattern argument.
  *  Fix some obscure issues with TH1 expression processing.
  *  Fix titles in search results for documents that are not wiki, markdown,
     or HTML.
  *  Formally translate TH1 to Tcl return codes and vice-versa, where
     necessary, in the Tcl integration subsystem.
  *  Add [/help?cmd=leaves|fossil leaves -multiple], for finding multiple
  *  Add [/help/leaves|fossil leaves -multiple], for finding multiple
     leaves on the same branch.
  *  Added the "Blitz" skin option.
  *  Removed the ".fossil-settings/keep-glob" file.  It should not have been
     checked into the repository.
  *  Update the built-in SQLite to version 3.8.10.2.
  *  Make [/help?cmd=open|fossil open] honor ".fossil-settings/allow-symlinks".
  *  Allow [/help?cmd=add|fossil add] to be used on symlinks to nonexistent or
     unreadable files in the same way as [/help?cmd=addremove|fossil addremove].
  *  Make [/help/open|fossil open] honor ".fossil-settings/allow-symlinks".
  *  Allow [/help/add|fossil add] to be used on symlinks to nonexistent or
     unreadable files in the same way as [/help/addremove|fossil addremove].
  *  Added fork warning to be issued if sync produced a fork
  *  Update the [/help?cmd=/info|info] page to report when a file becomes a
  *  Update the [/help/www/info|info] page to report when a file becomes a
     symlink.  Additionally show the UUID for files whose types have changed
     without changing contents or symlink target.
  *  Have [/help?cmd=changes|fossil changes] and
     [/help?cmd=status|fossil status] report when executable or symlink status
  *  Have [/help/changes|fossil changes] and
     [/help/status|fossil status] report when executable or symlink status
     changes on otherwise unmodified files.
  *  Permit filtering weekday and file [/help?cmd=/reports|reports] by user.
  *  Permit filtering weekday and file [/help/www/reports|reports] by user.
     Also ensure the user parameter is preserved when changing types.  Add a
     field for direct entry of the user name to each applicable report.
  *  Create parent directories of [/help?cmd=settings|empty-dirs] if they don't
  *  Create parent directories of [/help/settings|empty-dirs] if they don't
     already exist.
  *  Inhibit timeline links to wiki pages that have been deleted.

<h2 id='v1_33'>Changes for Version 1.32 (2015-03-14)</h2>
  *  When creating a new repository using [/help?cmd=init|fossil init], ensure
  *  When creating a new repository using [/help/init|fossil init], ensure
     that the new repository is fully compatible with historical versions of
     Fossil by having a valid manifest as RID 1.
  *  Anti-aliased rendering of arrowheads on timeline graphs.
  *  Added vi/less-style key bindings to the --tk diff GUI.
  *  Documentation updates to fix spellings and changes all "checkins" to
     "check-ins".
  *  Add the --repolist option to server commands such as
     [/help?cmd=server|fossil server] or [/help?cmd=http|fossil http].
     [/help/server|fossil server] or [/help/http|fossil http].
  *  Added the "Xekri" skin.
  *  Enhance the "ln=" query parameter on artifact displays to accept multiple
     ranges, separate by spaces (or "+" when URL-encoded).
  *  Added [/help?cmd=forget|fossil forget] as an alias for
     [/help?cmd=rm|fossil rm].
  *  Added [/help/forget|fossil forget] as an alias for
     [/help/rm|fossil rm].

<h2 id='v1_31'>Changes For Version 1.31 (2015-02-23)</h2>
  *  Change the auxiliary schema by adding columns MLINK.ISAUX and MLINK.PMID
     columns to the schema, to support better drawing of file change graphs.
     A [/help?cmd=rebuild|fossil rebuild] is recommended but is not required.
     A [/help/rebuild|fossil rebuild] is recommended but is not required.
     so that the new graph drawing logic can work effectively.
  *  Added [/search|search] over Check-in comments, Documents, Tickets and
     Wiki.  Disabled by default.  The search can be either a full-scan or it
     can use an index that is kept up-to-date automatically.  The new
     /srchsetup web-page and the [/help?cmd=fts-config|fts-config] command
     /srchsetup web-page and the [/help/fts-config|fts-config] command
     were added to help configure the search capability.  Expect further
     enhancements to the search capabilities in subsequent releases.
  *  Added form elements to some submenus (in particular the /timeline)
     for easier operation.
  *  Added the --ifneeded option to [/help?cmd=rebuild|fossil rebuild].
  *  Added the --ifneeded option to [/help/rebuild|fossil rebuild].
  *  Added "override skins" using the "skin:" line of the CGI script or
     using the --skin LABEL option on the [/help?cmd=server|server],
     [/help?cmd=ui|ui], or [/help?cmd=http|http] commands.
     using the --skin LABEL option on the [/help/server|server],
     [/help/ui|ui], or [/help/http|http] commands.
  *  Embedded html documents that begin with
     &lt;doc class="fossil-doc"&gt; are displayed with standard
     headers and footers added.
  *  Allow &lt;div style='...'&gt; markup in [/wiki_rules|wiki].
  *  Renamed "Events" to "Technical Notes", while updating the technote
     display and control pages.  Add support for technotes as plain text
     or as Markdown.
  *  Added the [/md_rules] pages containing summary instructions on the
     Markdown format.
  *  Added the --repolist and --nojail options to the various server commands
     (ex: [/help?cmd=server|fossil server]).
  *  Added the [/help?cmd=all|fossil all add] subcommand to "fossil all".
     (ex: [/help/server|fossil server]).
  *  Added the [/help/all|fossil all add] subcommand to "fossil all".
  *  Improvements to the /login page.  Some hyperlinks to pages that require
     "anonymous" privileges are displayed even if the current user is "nobody"
     but automatically redirect to /login.
  *  The [/help?cmd=/doc|/doc] web-page will now try to deliver the file
  *  The [/help/www/doc|/doc] web-page will now try to deliver the file
     "404.md" from the top-level directory (if such a file exists) in
     place of its built-in 404 text.
  *  Download of Tarballs and ZIP Archives by user "nobody" is now enabled
     by default in new repositories.
  *  Enhancements to the table sorting controls.  More display tables
     are now sortable.
  *  Add IPv6 support to [/help?cmd=sync|fossil sync] and
     [/help?cmd=clone|fossil clone]
  *  Add IPv6 support to [/help/sync|fossil sync] and
     [/help/clone|fossil clone]
  *  Add more skins such as "San Francisco Modern" and "Eagle".
  *  During shutdown, check to see if the check-out database (".fslckout")
     contains a lot of free space, and if it does, VACUUM it.
  *  Added the [/mimetype_list] page.
  *  Added the [/hash-collisions] page.
  *  Allow the user of Common Table Expressions in the SQL that defaults
     ticket reports.
  *  Break out the components (css, footer, and header) for the
     various built-in skins into separate files in the source tree.

<h2 id='v1_30'>Changes For Version 1.30 (2015-01-19)</h2>
  *  Added the [/help?cmd=bundle|fossil bundle] command.
  *  Added the [/help?cmd=purge|fossil purge] command.
  *  Added the [/help?cmd=publish|fossil publish] command.
  *  Added the [/help?cmd=unpublished|fossil unpublished] command.
  *  Added the [/help/bundle|fossil bundle] command.
  *  Added the [/help/purge|fossil purge] command.
  *  Added the [/help/publish|fossil publish] command.
  *  Added the [/help/unpublished|fossil unpublished] command.
  *  Enhance the [/tree] webpage to show the age of each file with the option
     to sort by age.
  *  Enhance the [/brlist] webpage to show additional information about each branch
     and to be sortable by clicking on column headers.
  *  Add support for Docker. Just install docker and type
     "sudo docker run -d -p 8080:8080 nijtmans/fossil" to get it running.
  *  Add the [/help/fusefs|fossil fusefs DIRECTORY] command that mounts a
     Fuse Filesystem at the given DIRECTORY and populates it with read-only
     copies of all historical check-ins. This only works on systems that
     support FuseFS.
  *  Add the administrative log that records all configuration.
  *  Added the [/sitemap] webpage.
  *  Added the [/bloblist] web page.
  *  Let [/help?cmd=new|fossil new] no longer create an initial empty commit
  *  Let [/help/new|fossil new] no longer create an initial empty commit
     by default. The first commit after checking out an empty repository will
     become the initial commit.
  *  Added the [/help?cmd=all|fossil all dbstat] and
     [/help?cmd=all|fossil all info] commands.
  *  Added the [/help/all|fossil all dbstat] and
     [/help/all|fossil all info] commands.
  *  Update SQLite to version 3.8.8.
  *  Added the --verily option to the [/help?cmd=clean|fossil clean] command.
  *  Added the --verily option to the [/help/clean|fossil clean] command.
  *  Add the "autosync-tries" setting to control the number of autosync attempts
     before returning an error.
  *  Added a compile-time option (--with-miniz) to build using miniz instead
     of zlib.  Disabled by default.
  *  Support customization of commands and webpages, including the ability to
     add new ones, via the "TH1 hooks" feature.  Disabled by default. Enabled
     via a compile-time option.
  *  Add the <nowiki>[checkout], [render], [styleHeader], [styleFooter],
     [trace], [getParameter], [setParameter], [artifact], and
     [globalState]</nowiki> commands to TH1, primarily for use by TH1 hooks.
  *  Automatically adjust the width of command-line timeline output according to the
     detected width of the terminal.
  *  Prompt the user to optionally fix invalid UTF-8 at check-in.
  *  Added a line-number toggle option to the [/help?cmd=/info|/info]
     and [/help?cmd=/artifact|/artifact] pages.
  *  Added a line-number toggle option to the [/help/www/info|/info]
     and [/help/www/artifact|/artifact] pages.
  *  Most commands now issue errors rather than silently ignoring unrecognized
     command-line options.
  *  Use full 40-character SHA1 hashes (instead of abbreviations) in most
     internal URLs.
  *  The "ssh:" sync method on Windows now uses "plink.exe" instead of "ssh" as
     the secure-shell client program.
  *  Prevent a partial clone when the connection is lost.
  *  Make the distinction between 301 and 302 redirects.
  *  Allow commits against a closed check-in as long as the commit goes onto
     a different branch.
  *  Improved cache control in the web interface reduces unnecessary requests
     for common resources like the page logo and CSS.
  *  Fix a rare and long-standing sync protocol bug
     that would silently prevent the sync from running to completion.  Before
     this bug-fix it was sometimes necessary to do "fossil sync --verily" to
     get two repositories in sync.
  *  Add the [/finfo?name=src/foci.c|files_of_checkin] virtual table - useful
     for ad hoc queries in the [/help?cmd=sqlite3|fossil sql] interface,
     for ad hoc queries in the [/help/sqlite3|fossil sql] interface,
     and also used internally.
  *  Added the "$secureurl" TH1 variable for use in headers and footers.
  *  (Internal:) Add the ability to include resources as separate files in the
     source tree that are converted into constant byte arrays in the compiled
     binary.  Use this feature to store the Tk script that implements the --tk
     diff option in a separate file for easier editing.
  *  (Internal:) Implement a system of compile-time checks to help ensure
1475
1476
1477
1478
1479
1480
1481
1482
1483


1484
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
1516

1517
1518
1519


1520
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
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
1632
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
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
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
1862

1863
1864

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
1892
1893
1894
1895
1896







-
-
+
+

-
-
-
-
+
+
+
+



-
+



-
-
-
+
+
+

-
-
-
+
+
+



-
+

-
+


-
-
-
+
+
+


-
+

-
-
+
+

-
+




-
+





-
+







-
+







-
+


-
-
-
+
+
+





-
+

-
+






-
+

-
-
+
+






-
+






-
-
-
+
+
+

-
+









-
+

-
+




-
+






-
-
+
+










-
+







     to the graphical diff display that results
     from using the --tk option with the [/help/diff | fossil diff] command.
  *  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 option --empty to the "[/help/open | fossil open]" command.
  *  Enhanced [/help/www/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].
     [/help/annotate|fossil annotate], [/help/blame|fossil blame],
     [/help/diff|fossil (g)diff], [/help/stash|fossil stash diff].
  *  Add --strip-trailing-cr option to [/help/diff|fossil (g)diff] and
     [/help/stash|fossil stash diff].
  *  Add button "Ignore Whitespace" to /annotate, /blame, /ci, /fdiff
     and /vdiff UI pages.
  *  Enhance [/reports?view=byweekday|/reports] with a "byweekday" view.
  *  Enhance the [/help?cmd=cat|fossil cat] command so that it works outside
  *  Enhance the [/help/cat|fossil cat] command so that it works outside
     of a checkout when using the -R command-line option.
  *  Use full-length SHA1 hashes, not abbreviations, in most hyperlinks.
  *  Correctly render the &lt;title&gt; markup on wiki pages in the
     [/help?cmd=/artifact|/artifact] webpage.
  *  Enhance the [/help?cmd=whatis|fossil whatis] command to report on attachments
     and cluster artifacts.  Added the [/help?cmd=test-whatis-all] command for
     [/help/www/artifact|/artifact] webpage.
  *  Enhance the [/help/whatis|fossil whatis] command to report on attachments
     and cluster artifacts.  Added the [/help/test-whatis-all] command for
     testing purposes.
  *  Add support for HTTP Basic Authentication on [/help?cmd=clone|clone] and
     [/help?cmd=sync|sync].
  *  Fix the [/help?cmd=stash|stash] so that it remembers added files and re-adds
  *  Add support for HTTP Basic Authentication on [/help/clone|clone] and
     [/help/sync|sync].
  *  Fix the [/help/stash|stash] so that it remembers added files and re-adds
     them when the stash is applied.
  *  Fix the server so that it avoids writing to the database (and thus avoids
     database locking issues) on a
     [/help?cmd=pull|pull] or [/help?cmd=clone|clone].
     [/help/pull|pull] or [/help/clone|clone].
  *  Add support for [./server.wiki#loadmgmt|server load management] using both
     a cache of expensive pages (the [/help?cmd=cache|fossil cache] command)
     a cache of expensive pages (the [/help/cache|fossil cache] command)
     and by rejecting expensive page requests when the server load average is too
     high.
  *  Add the [/help?cmd=praise|fossil praise] command as an alias for
     [/help?cmd=blame|fossil blame] for subversion compatibility.
  *  Enhance the [/help?cmd=test-diff|fossil test-diff] command with -y or --tk
  *  Add the [/help/praise|fossil praise] command as an alias for
     [/help/blame|fossil blame] for subversion compatibility.
  *  Enhance the [/help/test-diff|fossil test-diff] command with -y or --tk
     options so that it shows both filenames above their respective columns in
     the side-by-side diff output.
  *  Issue a warning if a [/help?cmd=add|fossil add] command tries to add a file
  *  Issue a warning if a [/help/add|fossil add] command tries to add a file
     that matches the ignore-glob.
  *  Add option -W|--width to "[/help?cmd=stash|fossil stash ls]"
     and "[/help?cmd=leaves|fossil leaves]" commands.
  *  Add option -W|--width to "[/help/stash|fossil stash ls]"
     and "[/help/leaves|fossil leaves]" commands.
  *  Enhance support for running as the root user. Now works on Haiku.
  *  Added the <tt>-empty</tt> option to [/help?cmd=new|fossil new], which
  *  Added the <tt>-empty</tt> option to [/help/new|fossil new], which
     causes it to not create an initial empty commit. The first commit after
     checking out a repo created this way will become the initial commit.
  *  Enhance sync operations by committing each round-trip to minimize number
     of retransmits when autosync fails. Include option for
     [/help?cmd=update| fossil update] and [/help?cmd=merge| fossil merge] to
     [/help/update| fossil update] and [/help/merge| fossil merge] to
     continue even if missing content.
  *  Minor portability fixes for platforms where the char type is unsigned
     by default.

<h2>Changes For Version 1.28 (2014-01-27)</h2>
  *  Enhance [/help?cmd=/reports | /reports] to support event type filtering.
  *  Enhance [/help/www/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
  *  Add the "[/help/blame | fossil blame]" command that works just like
     "fossil annotate" but uses a different output format that includes the
     user who made each change 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.
     "[/help/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
  *  Add option -W|--width and --offset to "[/help/timeline | fossil timeline]"
     and  "[/help/finfo | fossil finfo]" commands.
  *  Option -n|--limit of "[/help/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
  *  New <tt>--once</tt> option to [/help/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
  *  Modify [/help/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.
     to the "[/help/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
     "[/help/all | fossil all]" command
  *  Add the --whatif option to "[/help/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
  *  Enhancements to the "[/help/www/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],
     [/help?cmd=clean | fossil clean], [/help?cmd=extras | fossil extras],
     [/help?cmd=ls | fossil ls] and [/help?cmd=status | fossil status] commands
  *  Enhance the [/help/changes | fossil changes],
     [/help/clean | fossil clean], [/help/extras | fossil extras],
     [/help/ls | fossil ls] and [/help/status | fossil status] commands
     to restrict operation to files and directories named on the command-line.
  *  New --integrate option to [/help?cmd=merge | fossil merge], which
  *  New --integrate option to [/help/merge | fossil merge], which
     automatically closes the merged branch when committing.
  *  Renamed <tt>/stats_report</tt> page to [/reports]. Graph width is now
     relative, not absolute.
  *  Added <tt>yw=YYYY-WW</tt> (year-week) filter to timeline to limit the results
     to a specific year and calendar week number, e.g. [/timeline?yw=2013-01].
  *  Updates to SQLite to prevent opening a repository file using file descriptors
     1 or 2 on Unix.  This fixes a bug under which an assertion failure could
     overwrite part of a repository database file, corrupting it.
  *  Added support for unlimited line lengths in side-by-side diffs.
  *  New --close option to [/help?cmd=commit | fossil commit], which
  *  New --close option to [/help/commit | fossil commit], which
     immediately closes the branch being committed.
  *  Added <tt>chart</tt> option to [/help?cmd=bisect | fossil bisect].
  *  Added <tt>chart</tt> option to [/help/bisect | fossil bisect].
  *  Improvements to the "human or bot?" determination.
  *  Reports errors about missing CGI-standard environment variables for HTTP
     servers which do not support them.
  *  Minor improvements to sync support on Windows.
  *  Added <tt>--scgi</tt> option to [/help?cmd=server | fossil server].
  *  Added <tt>--scgi</tt> option to [/help/server | fossil server].
  *  Internal improvements to the sync process.
  *  The internals of the JSON API are now MIT-licensed, so downstream
     users/packagers are no longer affected by the "do no evil" license
     clause.

<h2>Changes For Version 1.26 (2013-06-18)</h2>
  *  The argument to the --port option for the [/help?cmd=ui | fossil ui] and
     [/help?cmd=server | fossil server] commands can take an IP address in addition
  *  The argument to the --port option for the [/help/ui | fossil ui] and
     [/help/server | fossil server] commands can take an IP address in addition
     to the port number, causing Fossil to bind to just that one IP address.
  *  After prompting for a password, also ask if that password should be
     remembered.
  *  Performance improvements to the diff engine.
  *  Fix the side-by-side diff engine to work better with multi-byte Unicode text.
  *  Color-coding in the web-based annotation (blame) display.  Fix the annotation
     engine so that it is no longer confused by time-warps.
  *  The markdown formatter is now available by default and can be used for
     tickets, wiki, and embedded documentation.
  *  Add subcommands "fossil bisect log" and "fossil bisect status" to the
     [/help?cmd=bisect | fossil bisect] command, as well as other bisect enhancements.
     [/help/bisect | fossil bisect] command, as well as other bisect enhancements.
  *  Enhanced defenses that prevent spiders from using excessive CPU and bandwidth.
  *  Consistent use of the -n or --dry-run command line options.
  *  Win32: Fossil now understands Cygwin paths containing one or more of
     the characters <nowiki>"*:<>?|</nowiki>. Those are normally forbidden in
     win32. This means that the win32 fossil.exe is better usable in a Cygwin
     environment. See
     [http://cygwin.com/cygwin-ug-net/using-specialnames.html#pathnames-specialchars].
Changes to www/chat.md.
53
54
55
56
57
58
59
60

61
62
63
64
65
66
67
53
54
55
56
57
58
59

60
61
62
63
64
65
66
67







-
+







cases those settings can be ignored.  The settings control things like
the amount of time that chat messages are retained before being purged
from the repository database.

## <a id="usage"></a>Usage

For users with appropriate permissions, simply browse to the
[/chat](/help?cmd=/chat) to start up a chat session.  The default
[/chat](/help/www/chat) to start up a chat session.  The default
skin includes a "Chat" entry on the menu bar on wide screens for
people with chat privilege.  There is also a "Chat" option on
the [Sitemap page](/sitemap), which means that chat will appear
as an option under the hamburger menu for many [skins](./customskin.md).

Chat messages are subject to [Fossil's
full range of Markdown processing](/md_rules). Because chat messages are
120
121
122
123
124
125
126
127

128
129
130
131

132
133
134
135

136
137
138
139
140
141
142
120
121
122
123
124
125
126

127
128
129
130

131
132
133
134

135
136
137
138
139
140
141
142







-
+



-
+



-
+







show up in that list, nor does the chat infrastructure have a way to
track and present those. That list can be used to filter messages on a
specific user by tapping on that user's name, tapping a second time to
remove the filter.

### <a id="cli"></a> The `fossil chat` Command

Type [fossil chat](/help?cmd=chat) from within any open check-out
Type [fossil chat](/help/chat) from within any open check-out
to bring up a chatroom for the project that is in that checkout.
The new chat window will attempt to connect to the default sync
target for that check-out (the server whose URL is shown by the
[fossil remote](/help?cmd=remote) command).
[fossil remote](/help/remote) command).

### <a id="robots"></a> Chat Messages From Robots

The [fossil chat send](/help?cmd=chat) can be used by project-specific
The [fossil chat send](/help/chat) can be used by project-specific
robots to send notifications to the chatroom.  For example, on the
[SQLite project](https://sqlite.org/) (for which the Fossil chatroom
feature, and indeed all of Fossil, was invented) there are long-running
fuzz servers that sometimes run across obscure problems.  Whenever this
happens, a message is sent to the SQLite developers chatroom alerting
them to the problem.

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
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







-
+








+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







-
+







~~~~

Substitute the appropriate project URL, robot account
name and password, message text and file attachment, of course.

### <a id="chat-robot"></a> Chat Messages For Timeline Events

If the [chat-timeline-user setting](/help?cmd=chat-timeline-user) is not an
If the [chat-timeline-user setting](/help/chat-timeline-user) is not an
empty string, then any change to the repository that would normally result
in a new timeline entry is announced in the chatroom.  The announcement
appears to come from a user whose name is given by the chat-timeline-user
setting.

This mechanism is similar to [email notification](./alerts.md) except that
the notification is sent via chat instead of via email.

## Quirks

  - There is no message-editing capability. This is by design and
    desire of `/chat`'s developers.

  - When `/chat` has problems connecting to the message poller (see
    the next section) it will provide a subtle visual indicator of the
    connection problem - a dotted line along the top of the input
    field.  If the connection recovers within a short time, that
    indicator will go away, otherwise it will pop up a loud message
    signifying that the connection to the poller is down and how long
    it will wait to retry (progressively longer, up to some
    maximum).  `/chat` will recover automatically when the server is
    reachable.  Trying to send messages while the poller connection is
    down is permitted, and the poller will attempt to recover
    immediately if sending of a message succeeds. That applies to any
    operations which send traffic, e.g. if the per-message "toggle
    text mode" button is activated or a message is globally deleted.

## Implementation Details

*You do not need to understand how Fossil chat works in order to use it.
But many developers prefer to know how their tools work.
This section is provided for the benefit of those curious developers.*

The [/chat](/help?cmd=/chat) webpage downloads a small amount of HTML
The [/chat](/help/www/chat) webpage downloads a small amount of HTML
and a small amount of javascript to run the chat session.  The
javascript uses XMLHttpRequest (XHR) to download chat content, post
new content, or delete historical messages.  The following web
interfaces are used by the XHR:

  *  [/chat-poll](/help?name=/chat-poll) &rarr;
     Downloads chat content as JSON.
226
227
228
229
230
231
232
233

234
235
236
237
238

239
240
241
242
243
244
244
245
246
247
248
249
250

251
252
253
254
255

256
257
258
259
260
261
262







-
+




-
+






  file  BLOB        -- Text of the uploaded file, or NULL
);
~~~

The CHAT table is not cross-linked with any other tables in the repository
schema.  An administrator can "DROP TABLE chat;" at any time, without
harm (apart from deleting all chat history, of course).  The CHAT table
is dropped when running [fossil scrub --verily](/help?cmd=scrub).
is dropped when running [fossil scrub --verily](/help/scrub).

On the server-side, message text is stored exactly as entered by the
users.  The /chat-poll page queries the CHAT table and constructs a
JSON reply described in the [/chat-poll
documentation](/help?cmd=/chat-poll).  The message text is translated
documentation](/help/www/chat-poll).  The message text is translated
into HTML before being converted to JSON so that the text can be
safely added to the display using assignment to `innerHTML`. Though
`innerHTML` assignment is generally considered unsafe, it is only so
with untrusted content from untrusted sources. The chat content goes
through sanitization steps which eliminate any potential security
vulnerabilities of assigning that content to `innerHTML`.
Changes to www/childprojects.wiki.
45
46
47
48
49
50
51
52

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

52
53
54
55
56
57







-
+





The repository is now a separate project, independent from its parent.
Clone the new project to the developers as needed.

The child project and the parent project will not normally be able to sync
with one another, since they are now separate projects with distinct
project codes.  However, if the
"--from-parent-project" command-line option is provided to the
"[/help?cmd=pull|fossil pull]" command in the child, and the URL of
"[/help/pull|fossil pull]" command in the child, and the URL of
parent repository is also provided on the command-line, then updates to
the parent project that occurred after the child was created will be added
to the child repository.  Thus, by periodically doing a
pull --from-parent-project, the child project is able to stay up to date
with all the latest changes in the parent.
Changes to www/chroot.md.
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
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





-
-
-
+
+
+
-


-
+

-
-
-
+
+
+
+















-
-
-
+
+
+
+
+
+








# The Server Chroot Jail

If you run Fossil as root in any mode that [serves data on the
network][srv], and you're running it on Unix or a compatible OS, Fossil
will drop itself into a [`chroot(2)` jail][cj] shortly after starting
up, once it's done everything that requires root access. Most commonly,
you run Fossil as root to allow it to bind to TCP port 80 for HTTP
service, since normal users are restricted to ports 1024 and up on OSes
up. The usual reason for launching Fossil 
as root to allow it to bind to TCP port 80 for HTTP
service, since normal users are restricted to ports 1024 and higher.
where this behavior occurs.

Fossil uses the owner of the Fossil repository file as its new user
ID when dropping root privileges.
ID when it drops root privileges.

When this happens, Fossil needs to have all of its dependencies inside
the chroot jail in order to continue work.  There are several things you
typically need in order to make things work properly:
When Fossil enters a chroot jail, it needs to have all of its dependencies
inside the chroot jail in order to continue work.  There are several
resources that need to be inside the chroot jail with Fossil in order for
Fossil to work correctly:

*   the repository file(s)

*   `/dev/null` — create it with `mknod(8)` inside the jail directory
    ([Linux example][mnl], [OpenBSD example][obsd])

*   `/dev/urandom` — ditto

*   `/proc` — you might need to mount this virtual filesystem inside the
    jail on Linux systems that make use of [Fossil’s server load
    shedding feature][fls]

*   any shared libraries your `fossil` binary is linked to, unless you
    [configured Fossil with `--static`][bld] to avoid it

Fossil does all of this in order to protect the host OS. You can make it
bypass the jail part of this by passing <tt>--nojail</tt> to <tt>fossil server</tt>,
but you cannot make it skip the dropping of root privileges, on purpose.
Fossil does all of this as one of many layers of defense against
hacks and exploits. You can prevent Fossil from entering the chroot
jail using the <tt>--nojail</tt> option to the 
[fossil server command](/help/server)
but you cannot make Fossil hold onto root privileges.  Fossil always drops
root privilege before accepting inputs, for security.


[bld]: https://fossil-scm.org/home/doc/trunk/www/build.wiki
[cj]:  https://en.wikipedia.org/wiki/Chroot
[fls]: ./loadmgmt.md
[mnl]: https://fossil-scm.org/forum/forumpost/90caff30cb
[srv]: ./server/
[obsd]: ./server/openbsd/fastcgi.md#chroot
Changes to www/ckout-workflows.md.
133
134
135
136
137
138
139
140

141
142
133
134
135
136
137
138
139

140
141
142







-
+



    fossil clone https://dev.example.com/repo/my-project

The `/repo` addition is the key: whatever comes after is used as the
repository name. [See the docs][clone] for more details.

[caod]:  https://fossil-scm.org/forum/forumpost/3f143cec74
[clone]: /help?cmd=clone
[clone]: /help/clone

<div style="height:50em" id="this-space-intentionally-left-blank"></div>
Changes to www/co-vs-up.md.
22
23
24
25
26
27
28
29

30
31

22
23
24
25
26
27
28

29
30

31







-
+

-
+
    provide a good reason to develop a habit of using `fossil checkout`
    instead.

In summary, these are two separate commands; neither is an alias for the
other. They overlap enough that they can be used interchangeably for
some use cases, but `update` is more powerful and more broadly useful.

[co]:    /help?cmd=checkout
[co]:    /help/checkout
[cvsmu]: http://web.mit.edu/gnu/doc/html/cvs_7.html#SEC37
[up]:    /help?cmd=update
[up]:    /help/update
Changes to www/concepts.wiki.
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
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







-
-
+
+






-
+




-
+








With other configuration management software, setting up a server is
a lot of work and normally takes time, patience, and a lot of system
knowledge.  Fossil is designed to avoid this frustration.  Setting up
a server with Fossil is ridiculously easy.  You have four options:

  #  <b>Stand-alone server.</b>
     Simply run the [/help?cmd=server|fossil server] or
     [/help?cmd=ui|fossil ui] command from the command-line.
     Simply run the [/help/server|fossil server] or
     [/help/ui|fossil ui] command from the command-line.
     <br><br>
  #  <b>CGI.</b>
     Install a 2-line CGI script on a CGI-enabled web-server like Apache.
     <br><br>
  #  <b>SCGI.</b>
     Start an SCGI server using the
     [/help?cmd=server| fossil server --scgi] command for handling
     [/help/server| fossil server --scgi] command for handling
     SCGI requests from web-servers like Nginx.
     <br><br>
  #  <b>Inetd or Stunnel.</b>
     Configure programs like inetd, xinetd, or stunnel to hand off HTTP requests
     directly to the [/help?cmd=http|fossil http] command.
     directly to the [/help/http|fossil http] command.

See the [./server/ | How To Configure A Fossil Server] document
for details.

<h2>6.0 Review Of Key Concepts</h2>

<ul>
Changes to www/containers.md.
539
540
541
542
543
544
545
546

547
548
549
550
551
552
553
554
539
540
541
542
543
544
545

546

547
548
549
550
551
552
553







-
+
-







    [Install]
    WantedBy=default.target

I was then able to enable email alert forwarding for select repositories
after configuring them per [the docs](./alerts.md) by saying:

    $ systemctl --user daemon-reload
    $ systemctl --user enable alert-sender@myproject
    $ systemctl --user enable --now alert-sender@myproject
    $ systemctl --user start  alert-sender@myproject

Because this is a parameterized script and we’ve set our repository
paths predictably, you can do this for as many repositories as you need
to by passing their names after the “`@`” sign in the commands above.


## 6. <a id="light"></a>Lightweight Alternatives to Docker
668
669
670
671
672
673
674
675

676
677
678
679
680
681
682
667
668
669
670
671
672
673

674
675
676
677
678
679
680
681







-
+







      localhost/fossil
    $ podman start fossil

[pmmac]:  https://podman.io/getting-started/installation.html#macos
[pmwin]:  https://github.com/containers/podman/blob/main/docs/tutorials/podman-for-windows.md
[Podman]: https://podman.io/
[rl]:     https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md
[whatis]: https://podman.io/whatis.html
[whatis]: https://docs.podman.io/en/latest/index.html


### 6.3 <a id="nspawn"></a>`systemd-container`

If even the Podman stack is too big for you, the next-best option I’m
aware of is the `systemd-container` infrastructure on modern Linuxes,
available since version 239 or so.  Its runtime tooling requires only
Changes to www/contribute.wiki.
38
39
40
41
42
43
44
45

46
47
48
49
50
51
52
38
39
40
41
42
43
44

45
46
47
48
49
50
51
52







-
+







[https://fossil-scm.org/forum | the forum] or email them to
<a href="mailto:drh@sqlite.org">drh@sqlite.org</a>.  Be sure to
describe in detail what the patch does and which version of Fossil
it is written against. It's best to make patches against tip-of-trunk
rather than against past releases.

If your change is more complicated than a patch can properly encode, you
may submit [/help?cmd=bundle | a Fossil bundle] instead. Unlike patches,
may submit [/help/bundle | a Fossil bundle] instead. Unlike patches,
bundles can contain multiple commits, check-in comments, file renames,
file deletions, branching decisions, and more which <tt>patch(1)</tt>
files cannot. It's best to make a bundle of a new branch so the change
can be integrated, tested, enhanced, and merged down to trunk in a
controlled fashion.

A contributor agreement is not strictly necessary to submit a patch or bundle,
Changes to www/customskin.md.
21
22
23
24
25
26
27
28

29
30
31
32
33
34
35

36
37
38
39
40
41
42
21
22
23
24
25
26
27

28
29
30
31
32
33
34

35
36
37
38
39
40
41
42







-
+






-
+







   * css.txt
   * details.txt
   * footer.txt
   * header.txt
   * js.txt

Try out the built-in skins by using the --skin option on the
[fossil ui](/help?cmd=ui) or [fossil server](/help?cmd=server) commands.
[fossil ui](/help/ui) or [fossil server](/help/server) commands.

## <a id="sharing"></a>Sharing Skins

The skin of a repository is not part of the versioned state and does not
"push" or "pull" like checked-in files.  The skin is local to the
repository.  However, skins can be shared between repositories using
the [fossil config](/help?cmd=configuration) command.
the [fossil config](/help/configuration) command.
The "fossil config push skin" command will send the local skin to a remote
repository and the "fossil config pull skin" command will import a skin
from a remote repository.  The "fossil config export skin FILENAME"
will export the skin for a repository into a file FILENAME.  This file
can then be imported into a different repository using the
"fossil config import FILENAME" command.  Unlike "push" and "pull",
the "export" and "import" commands are able to move skins between
302
303
304
305
306
307
308
309

310
311
312
313
314

315
316
317
318
319
320
321
302
303
304
305
306
307
308

309
310
311
312
313
314
315
316
317
318
319
320
321
322







-
+





+







working as desired, the draft skin is "published" and becomes the
new live skin that most users see.

### Skin Development Using A Local Text Editor

An alternative approach is to copy the five control files for your
baseline skin into a temporary working directory (here called
"./newskin") and then launch the [fossil ui](/help?cmd=ui) command
"./newskin") and then launch the [fossil ui](/help/ui) command
with the "--skin ./newskin" option.  If the argument to the --skin
option contains a "/" character, then the five control files are
read out of the directory named.  You can then edit the control
files in the ./newskin folder using you favorite text editor, and
press "Reload" on your browser to see the effects.


### Disabling The Web Browser Cache During Development

Fossil is aggressive about asking the web browser to cache 
resources.  While developing a new skin, it is often helpful to
put your web browser into developer mode and disable the cache.
If you fail to do this, then you might make some change to your skin
333
334
335
336
337
338
339
340


341
342
343
344
345
346
347
334
335
336
337
338
339
340

341
342
343
344
345
346
347
348
349







-
+
+








  *  All text within &lt;th1&gt;...&lt;/th1&gt; is omitted from the
     output and is instead run as a TH1 script.  That TH1
     script has the opportunity to insert new text in place of itself,
     or to inhibit or enable the output of subsequent text.

  *  Text of the form "$NAME" or "$&lt;NAME&gt;" is replaced with
     the value of the TH1 variable NAME.
     the value of the TH1 variable NAME.  See the [TH1 Variables](#vars)
     section for more information on the two possible variable formats.

For example, first few lines of a typical Skin Header will look
like this:

    <div class="header">
      <div class="title"><h1>$<project_name></h1>$<title>/div>

421
422
423
424
425
426
427









428
429
430
431


432
433
434
435





436
437
438
439
440
441
442
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







+
+
+
+
+
+
+
+
+



-
+
+



-
+
+
+
+
+









## <a id="vars"></a>TH1 Variables

Before expanding the TH1 within the header and footer, Fossil first
initializes a number of TH1 variables to values that depend on
repository settings and the specific page being generated.

Variables holding text that is loaded from "external, potentially untrusted"
sources (including the repository settings) are treated as [tainted strings]
(./th1.md#taint) and must be noted in the `$<NAME>` form, instead of `$NAME`,
or they may trigger an error (see the linked document for details).  The
`$<NAME>` form corresponds to the TH1 statement `puts [ htmlize "$NAME" ]`,
where the [htmlize](./th1.md#htmlize) function escapes the tainted string,
making it safe for output in HTML code.


   *   **`project_name`** - The project_name variable is filled with the
       name of the project as configured under the Admin/Configuration
       menu.
       menu.  This is a [tainted string](./th1.md#taint) variable and must
       be used as `$<project_name>`.

   *   **`project_description`** - The project_description variable is
       filled with the description of the project as configured under
       the Admin/Configuration menu.
       the Admin/Configuration menu.  This is a [tainted string]
       (./th1.md#taint) variable and must be used as `$<project_description>`.

   *   **`mainmenu`** - The mainmenu variable contains a TCL list with the main
       menu entries.  See the [mainmenu](/help/mainmenu) setting for details.

   *   **`title`** - The title variable holds the title of the page being
       generated.

       The title variable is special in that it is deleted after
       the header script runs and before the footer script.  This is
       necessary to avoid a conflict with a variable by the same name used
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
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







-
+











-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





       be copied directly out of one of the subdirectories under skins.  If
       sources are not easily at hand, then a copy/paste out of the
       CSS, footer, and header editing screens under the Admin menu will
       work just as well.  The important point is that the three files
       be named exactly "css.txt", "footer.txt", and "header.txt" and that
       they all be in the same directory.

   2.  Run the [fossil ui](/help?cmd=ui) command with an extra
   2.  Run the [fossil ui](/help/ui) command with an extra
       option "--skin SKINDIR" where SKINDIR is the name of the directory
       in which the three txt files were stored in step 1.   This will bring
       up the Fossil website using the tree files in SKINDIR.

   3.  Edit the *.txt files in SKINDIR.  After making each small change,
       press Reload on the web browser to see the effect of that change.
       Iterate until the desired look is achieved.

   4.  Copy/paste the resulting css.txt, details.txt,
       header.txt, and footer.txt files
       into the CSS, details, header, and footer configuration screens
       under the Admin/Skins menu.
       under the Admin/Skins menu. Alternately, import them using the
       process described below.

An alternative to step 4 is to convert the skin files into a form
which can be imported into a repository using `fossil config import`.
It requires compiling [a small tool from the fossil source
tree](/file/tools/skintxt2config.c):

>
```
$ cc -o s2c /path/to/fossil/checkout/tools/skintxt2config.c
```

With that in place, the custom skin files can be converted with:

>
```
$ ./s2c yourskin/*.txt > skin.config
```

It can be imported into an arbitrary fossil repository with:

>
```
$ fossil config import skin.config
```

And it can be pushed to a remote repository with:

>
```
$ fossil config push skin
```

That approach has proven to be an effective way to locally develop
skin changes then push them to a "live" site.


## See Also

*   [Customizing the Timeline Graph](customgraph.md)
Changes to www/defcsp.md.
29
30
31
32
33
34
35
36

37
38
39
40
41
42
43
29
30
31
32
33
34
35

36
37
38
39
40
41
42
43







-
+







script-src 'self' 'nonce-$nonce';
style-src 'self' 'unsafe-inline';
img-src * data:;
</pre>

The default is recommended for most installations.  However,
the site administrators can overwrite this default CSP using the
[default-csp setting](/help?cmd=default-csp).  For example,
[default-csp setting](/help/default-csp).  For example,
CSP restrictions can be completely disabled by setting the default-csp to:

    default-src *;

The following sections detail the maining of the default CSP setting.

### <a id="base"></a> default-src 'self' data:
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
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







-
+






-
+

-
+




-
+

















-
+







    external resources in the backup copies, so that when the main repo
    site disappears, so do those files.

Unversioned content is in the middle of the first list above — between
fully-external content and fully in-repo content — because it isn’t
included in a clone unless you give the `--unversioned` flag. If you
then want updates to the unversioned content to be included in syncs,
you have to give the same flag to [a `sync` command](/help?cmd=sync).
you have to give the same flag to [a `sync` command](/help/sync).
There is no equivalent with other commands such as `up` and `pull`, so
you must then remember to give `fossil uv` commands when necessary to
pull new unversioned content down.

Thus our recommendation that you refer to in-repo resources exclusively.

[du]:   /help?cmd=/doc
[du]:   /help/www/doc
[fp]:   ./forum.wiki
[ru]:   /help?cmd=/raw
[ru]:   /help/www/raw
[spof]: https://en.wikipedia.org/wiki/Single_point_of_failure
[tkt]:  ./tickets.wiki
[tn]:   ./event.wiki
[tls]:  ./server/debian/nginx.md 
[uu]:   /help?cmd=/uv
[uu]:   /help/www/uv
[uv]:   ./unvers.wiki
[wiki]: ./wikitheory.wiki


## <a id="override"></a>Overriding the Default CSP

If you wish to relax the default CSP’s restrictions or to tighten them
further, there are multiple ways to accomplish that.

The following methods are listed in top-down order to give the simplest
and most straightforward method first.  Further methods dig down deeper
into the stack, which is helpful to understand even if you end up using
a higher-level method.


### <a id="cspsetting"></a>The `default-csp` Setting

If the [`default-csp` setting](/help?cmd=default-csp) is defined and is
If the [`default-csp` setting](/help/default-csp) is defined and is
not an empty string, its value is injected into the page using
[TH1](./th1.md) via one or more of the methods below, depending on the
skin you’re using and local configuration.

Changing this setting is the easiest way to set a nonstandard CSP on
your site.

Changes to www/delta_format.wiki.
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
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







-
+


-
+


-
+


-
+



-
+







contained in the [../src/deltacmd.c|deltacmd.c] source file.  SQL functions
to create, apply, and analyze deltas are implemented by code in the
[../src/deltafunc.c|deltafunc.c] source file.

The following command-line tools are available to create and apply
deltas and to test the delta logic:

   *   [/help?cmd=test-delta|fossil test-delta] &rarr; Run self-tests of
   *   [/help/test-delta|fossil test-delta] &rarr; Run self-tests of
       the delta logic

   *   [/help?cmd=test-delta-create|fossil test-delta-create X Y] &rarr; compute
   *   [/help/test-delta-create|fossil test-delta-create X Y] &rarr; compute
       a delta that converts file X into file Y.  Output that delta.

   *   [/help?cmd=test-delta-apply|fossil test-delta-apply X D] &rarr; apply
   *   [/help/test-delta-apply|fossil test-delta-apply X D] &rarr; apply
       delta D to input file X and output the result.

   *   [/help?cmd=test-delta-analyze|fossil test-delta-analyze X Y] &rarr; compute
   *   [/help/test-delta-analyze|fossil test-delta-analyze X Y] &rarr; compute
       and delta that converts file X into file Y but instead of writing the
       delta to output, write performance information about the delta.

When running the [/help?cmd=sqlite3|fossil sql] command to get an
When running the [/help/sqlite3|fossil sql] command to get an
interactive SQL session connected to the repository, the following
additional SQL functions are provided:

   *   <b>delta_create(</b><i>X</i><b>,</b><i>Y</i><b>)</b> &rarr;
       Compute a data that carries blob X into blob Y and return that delta
       as a blob.

Changes to www/embeddeddoc.wiki.
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
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







-
+
















-
+







<pre>
<i>&lt;baseurl&gt;</i><big><b>/doc/</b></big><i>&lt;version&gt;</i><big><b>/</b></big><i>&lt;filename&gt;</i>
</pre>

The <i>&lt;baseurl&gt;</i> is the main URL used to access the fossil web server.
For example, the <i>&lt;baseurl&gt;</i> for the fossil project itself is
[https://fossil-scm.org/home].
If you launch the web server using the "[/help?cmd=ui|fossil ui]" command line,
If you launch the web server using the "[/help/ui|fossil ui]" command line,
then the <i>&lt;baseurl&gt;</i> is usually
<b>http://localhost:8080/</b>.

The <i>&lt;version&gt;</i> is the
[./checkin_names.wiki|name of a check-in]
that contains the embedded document.  This might be a hash prefix for
the check-in, or it might be the name of a branch or tag, or it might
be a timestamp.  See the prior link
for more possibilities and examples.

The <i id="ckout">&lt;version&gt;</i> can
also be the special identifier "<b>ckout</b>".
The "<b>ckout</b>" keywords means to
pull the documentation file from the local source tree on disk, not
from the any check-in.  The "<b>ckout</b>" keyword
only works when you start your server using the 
"[/help?cmd=server|fossil server]" or "[/help?cmd=ui|fossil ui]"
"[/help/server|fossil server]" or "[/help?cmd=ui|fossil ui]"
commands.  The "/doc/ckout" URL is intended to show a preview of
the documentation you are currently editing but have not yet checked in.

The original designed purpose of the "ckout" feature is to allow the
user to preview local changes to documentation before committing the
change.  This is an important facility, since unlike other document
languages like HTML, there is still a lot of variation among rendering
Changes to www/env-opts.md.
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
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







-
+




















+
+
+
+
+








`--comfmtflags NUMBER`: Specify flags that control how check-in comments
and certain other text outputs are formatted for display. The flags are
individual bits in `NUMBER`, which must be specified in base 10:

  * _0_ &mdash; Uses the revised algorithm with no special handling.

  * _1_ &mdash; Uses the legacy algorithm, other flags are ignored.
  * _1_ &mdash; Uses the canonical algorithm, other flags are ignored.

  * _2_ &mdash; Trims leading and trailing carriage-returns and line-feeds
        where they do not materially impact pre-existing formatting
        (i.e. at the start of the comment string _and_ right before
        line indentation).

  * _4_ &mdash; Trims leading and trailing spaces where they do not materially
        impact the pre-existing formatting (i.e. at the start of the
        comment string _and_ right before line indentation).

  * _8_ &mdash; Attempts to break lines on word boundaries while honoring the
        logical line length.

  * _16_ &mdash; Looks for the original comment text within the text being
         printed.  Upon matching, a new line will be emitted, thus
         preserving more of the pre-existing formatting.


`--comment-format NUMBER`: Alias for `--comfmtflags NUMBER`.


> NOTE: As of Fossil version 2.26, use of the `--comfmtflags` and
> `--comment-format` options is no longer recommended and they are
> no longer documented, but retained for backwards compatibility.


`--errorlog ERRLOG`: Name a file to which fossil will log panics,
errors, and warnings.


`--help`: If `--help` is found anywhere on the command line, translate
the command to `fossil help cmdname` where `cmdname` is the first
141
142
143
144
145
146
147











148
149
150
151
152
153
154
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







+
+
+
+
+
+
+
+
+
+
+







local (or remote) testing of the moderation subsystem and its impact
on the contents and status of wiki pages.


`FOSSIL_HOME`: Location of [configuration database][configdb].
See the [configuration database location][configloc] description
for additional information.

`FOSSIL_REPOLIST_TITLE`: The page title of the "Repository List" page
loaded by the `fossil all ui` or `fossil ui /` commands. Only used if
none of the listed repositories has the `repolist_skin` property set.
Can be set from the [CGI control file][cgictlfile].

`FOSSIL_REPOLIST_SHOW`: If this variable exists and has a text value
that contains the substring "description", then the "Project Description"
column appears on the repolist page.  If it contains the substring
"login-group", then the Login-Group column appears on the repolist page.
Can be set from the [CGI control file][cgictlfile].

`FOSSIL_USE_SEE_TEXTKEY`: If set, treat the encryption key string for
SEE as text to be hashed into the actual encryption key.  This has no
effect if Fossil was not compiled with SEE support enabled.


`FOSSIL_USER`: Name of the default user account if the checkout, local
208
209
210
211
212
213
214




215
216
217
218
219
220
221
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241







+
+
+
+







`FOSSIL_HOME`, `LOCALAPPDATA` (Windows), `APPDATA` (Windows),
`HOMEDRIVE` and `HOMEPATH` (Windows, used together), and `HOME` is
used as the location of the `~/.fossil` file.

`LOGNAME`: Name of the logged in user on many Unix-like platforms.
Used as the fossil user name if `FOSSIL_USER` is not specified. See
the discussion of Fossil Username below for a lot more detail.

`NO_COLOR`: If defined and not set to a `false` value (i.e. "off", "no",
"false", "0"), the `fossil search` command skips colorization of console
output using ANSI escape codes (VT100).

`PATH`: Used by most platforms to locate programs invoked without a
fully qualified name. Explicitly used by `fossil ui` on certain platforms
to choose the browser to launch.

`PATH_INFO`: If defined, included in error log messages.

231
232
233
234
235
236
237
238

239
240
241
242
243
244
245
251
252
253
254
255
256
257

258
259
260
261
262
263
264
265







-
+







`REQUEST_URI`: If defined, included in error log messages.

`SCRIPT_NAME`: If defined, included in error log messages.

`SSH_CONNECTION`: Informs CGI processing if the remote client is SSH.

`SSL_CERT_FILE`, `SSL_CERT_DIR`: Override the [`ssl-ca-location`]
(/help?cmd=ssl-ca-location) setting.
(/help/ssl-ca-location) setting.

`SQLITE_FORCE_PROXY_LOCKING`: From `sqlite3.c`, 1 means force always
use proxy, 0 means never use proxy, and undefined means use proxy for
non-local files only.

`SQLITE_TMPDIR`: Names the temporary file location for SQLite.  When
set, this will be used instead of `TMPDIR`.
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

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







-
-
+
+


















+
will happen on all platforms.


### Web browser

Occasionally, fossil wants to launch a web browser for the user, most
obviously as part of the `fossil ui` command. In that specific case,
the browser is launched pointing at the web server started by `fossil
ui` listening on a private TCP port.
the browser is launched pointing at the web server started by
`fossil ui` listening on a private TCP port.

On all platforms, if the local or global settings `web-browser` is
set, that is the command used to open a URL.

Otherwise, the specific actions vary by platform.

On Unix-like platforms other than Apple's, it looks for the first
program from the list `xdg-open`, `gnome-open`, `firefox`, and
`google-chrome` that it can find on the `PATH`.

On Apple platforms, it assumes that `open` is the command to open a
URL in the user's configured default browser.

On Windows platforms, it assumes that `start` is the command to open
a URL in the user's configured default browser.

[configdb]: ./tech_overview.wiki#configdb
[configloc]: ./tech_overview.wiki#configloc
[cgictlfile]: ./cgi.wiki
Changes to www/event.wiki.
105
106
107
108
109
110
111
112

113
114
115
116
117
118
119
105
106
107
108
109
110
111

112
113
114
115
116
117
118
119







-
+







Release Notes 2021-07-02

This note describes changes in the Fossil snapshot for ...
</verbatim>

The <b>-t|--technote</b> option to the <b>export</b> subcommand takes one of
three identifiers: <b>DATETIME</b>; <b>TECHNOTE-ID</b>; and <b>TAG</b>.
See the [/help?cmd=wiki | wiki help] for specifics.
See the [/help/wiki | wiki help] for specifics.

Users must have check-in privileges (permission "i") in order to
create or edit technotes.  In addition, users must have create-wiki
privilege (permission "f") to create new technotes and edit-wiki
privilege (permission "k") in order to edit existing technotes.

Technote content may be formatted as [/wiki_rules | Fossil wiki],
Changes to www/fileedit-page.md.
11
12
13
14
15
16
17
18

19
20
21
22
23
24
25
11
12
13
14
15
16
17

18
19
20
21
22
23
24
25







-
+







browser halfway around the world comes with several obligatory caveats
and disclaimers...

## <a id="cap"></a> `/fileedit` Does *Nothing* by Default.

In order to "activate" it, a user with [the "setup"
permission](./caps/index.md) must set the
[fileedit-glob](/help?cmd=fileedit-glob) repository setting to a
[fileedit-glob](/help/fileedit-glob) repository setting to a
comma- or newline-delimited list of globs representing a whitelist of
files which may be edited online. Any user with commit access may then
edit files matching one of those globs. Certain pages within the UI
get an "edit" link added to them when the current user's permissions
and the whitelist both permit editing of that file.

## <a id="csrf"></a> CSRF & HTTP Referrer Headers
Changes to www/fileformat.wiki.
59
60
61
62
63
64
65
66

67
68
69
70
71
72
73
59
60
61
62
63
64
65

66
67
68
69
70
71
72
73







-
+







repository.
Fossil recognizes the following kinds of structural
artifacts:

<ul>
<li> [#manifest | Manifests] </li>
<li> [#cluster | Clusters] </li>
<li> [#ctrl | Control Artifacts] </li>
<li> [#ctrl | Control (a.k.a. Tag) Artifacts] </li>
<li> [#wikichng | Wiki Pages] </li>
<li> [#tktchng | Ticket Changes] </li>
<li> [#attachment | Attachments] </li>
<li> [#event | TechNotes] </li>
<li> [#forum | Forum Posts] </li>
</ul>

171
172
173
174
175
176
177
178




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

178
179
180
181
182
183
184
185
186
187
188







-
+
+
+
+







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 <b>N</b> cards.  The <b>N</b> card specifies the mimetype for the
text in the comment of the <b>C</b> card.  If the <b>N</b> card is omitted, a default mimetype
is used.
is used. Note that the <b>N</b> card has never actually been used by
any Fossil implementation.  The implementation has always interpreted
check-in comments according to the [/wiki_rules|Fossil Wiki formatting rules].
There are no current plans to ever change that.

A manifest has zero or one <b>P</b> cards.  Most manifests have one <b>P</b> card.
The <b>P</b> card has a varying number of arguments that
define other manifests from which the current manifest
is derived.  Each argument is a lowercase
hexadecimal artifact hash of a predecessor manifest.  All arguments
to the <b>P</b> card must be unique within that card.
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
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







-
+


-
-
+
+







+
+
+
+







the <b>Z</b> card of a manifest.  The argument to the <b>Z</b> card is the
lower-case hexadecimal representation of the MD5 checksum of all
prior cards in the cluster.  The <b>Z</b> card is required.

An example cluster from Fossil can be seen
[/artifact/d03dbdd73a2a8 | here].

<h3 id="ctrl">2.3 Control Artifacts</h3>
<h3 id="ctrl">2.3 Control (a.k.a. Tag) Artifacts</h3>

Control artifacts are used to assign properties to other artifacts
within the repository.
Allowed cards in a control artifact are as follows:
within the repository.  Allowed cards in a control artifact are as
follows:

<div class="indent">
<b>D</b> <i>time-and-date-stamp</i><br />
<b>T</b> (<b>+</b>|<b>-</b>|<b>*</b>)<i>tag-name</i> <i>artifact-id</i> ?<i>value</i>?<br />
<b>U</b> <i>user-name</i><br />
<b>Z</b> <i>checksum</i><br />
</div>

Control articles are also referred to as Tag artifacts, but tags can
also be applied via other artifact types, as described in
[#summary|the Card Summary table].

A control artifact must have one <b>D</b> card, one <b>U</b> card, one <b>Z</b> card and
one or more <b>T</b> cards.  No other cards or other text is
allowed in a control artifact.  Control artifacts might be PGP
clearsigned.

The <b>D</b> card and the <b>Z</b> card of a control artifact are the same
Changes to www/forum.wiki.
370
371
372
373
374
375
376
377

378
379
380
381
382
383
384
370
371
372
373
374
375
376

377
378
379
380
381
382
383
384







-
+







</ol>

<h2 id="close-post">Closing Forum Posts</h2>

As of version 2.23, the forum interface supports the notion of
"closing" posts. By default, only users with the [./caps/index.md|'s'
and 'a' capabilities] may close or re-open posts, or reply to closed
posts. If the [/help?cmd=forum-close-policy|forum-close-policy
posts. If the [/help/forum-close-policy|forum-close-policy
configuration option] is enabled then users with
[./caps/index.md|forum-moderator permissions] may also perform those
actions.

Closing a post has the following implications:

  *  Only authorized users may edit or respond to such posts, recursively
Changes to www/fossil-is-not-relational.md.
92
93
94
95
96
97
98
99

100
101
102
103
104
105
106
92
93
94
95
96
97
98

99
100
101
102
103
104
105
106







-
+







Notably, the artifact file format <u>does not</u>...

- Specify any specific storage mechanism for the SCM's raw bytes,
  which includes both artifacts themselves and client-side file
  content. The file format refers to all such content solely by its
  unique hash value.

- Specify any optimimizations such as storing file-level changes as
- Specify any optimizations such as storing file-level changes as
  deltas between two versions of that content.

Such aspects are all considered to be implementation details of
higher-level applications (be they in the main fossil binary or a
hypothetical 3rd-party application), and have no effect on the
underlying artifact data model. That said, in Fossil:

Changes to www/fossil-v-git.wiki.
103
104
105
106
107
108
109
110

111
112
113
114
115
116
117
103
104
105
106
107
108
109

110
111
112
113
114
115
116
117







-
+







Git provides file versioning services only, whereas Fossil adds
an integrated [./wikitheory.wiki | wiki],
[./bugtheory.wiki | ticketing &amp; bug tracking],
[./embeddeddoc.wiki | embedded documentation], 
[./event.wiki | technical notes], a [./forum.wiki | web forum],
and a [./chat.md | chat service],
all within a single nicely-designed [./customskin.md|skinnable] web
[/help?cmd=ui|UI],
[/help/ui|UI],
protected by [./caps/ | a fine-grained role-based
access control system].
These additional capabilities are available for Git as 3rd-party
add-ons, but with Fossil they are integrated into
the design, to the point that it approximates
"[https://github.com/ | GitHub]-in-a-box."

128
129
130
131
132
133
134
135
136


137
138
139
140
141
142
143
128
129
130
131
132
133
134


135
136
137
138
139
140
141
142
143







-
-
+
+








You get the same capability with several other Fossil
sub-commands as well, such as "<tt>fossil all changes</tt>" to get a list of files
that you forgot to commit prior to the end of your working day, across
all repos.

Whenever Fossil is told to modify the local checkout in some destructive
way ([/help?cmd=rm|fossil rm], [/help?cmd=update|fossil update],
[/help?cmd=revert|fossil revert], etc.) Fossil remembers the prior state
way ([/help/rm|fossil rm], [/help/update|fossil update],
[/help/revert|fossil revert], etc.) Fossil remembers the prior state
and is able to return the check-out directory to that state with a
<tt>fossil undo</tt> command. While you cannot undo a commit in Fossil
— [#history | on purpose!] — as long as the change remains confined to
the local check-out directory only, Fossil makes undo
[https://git-scm.com/book/en/v2/Git-Basics-Undoing-Things|easier than in
Git].

272
273
274
275
276
277
278
279

280
281
282
283
284
285
286
272
273
274
275
276
277
278

279
280
281
282
283
284
285
286







-
+







It is common in Fossil to ask to see
[/timeline?df=release&y=ci|all check-ins since the last release].
Git lets you see "what came before".  Fossil makes it just as
easy to also see "what came after".

Leaf check-ins in Git that lack a "ref" become "detached," making them
difficult to locate and subject to garbage collection. This
[http://gitfaq.org/1/01/what-is-a-detached-head/|detached head
[https://stackoverflow.com/q/3965676 | detached head
state] problem has caused grief for
[https://www.google.com/search?q=git+detached+head+state | many
Git users]. With
Fossil, detached heads are simply impossible because we can always find
our way back into the Merkle tree using one or more of the relations
in the SQL database.

340
341
342
343
344
345
346
347

348
349
350
351
352
353
354
340
341
342
343
344
345
346

347
348
349
350
351
352
353
354







-
+







Fossil's user-visible functionality.

Fossil isn't entirely C and SQL code. Its web UI [./javascript.md |
uses JavaScript where
necessary]. The server-side
UI scripting uses a custom minimal
[https://en.wikipedia.org/wiki/Tcl|Tcl] dialect called
[https://fossil-scm.org/xfer/doc/trunk/www/th1.md|TH1], which is
[./th1.md|TH1], which is
embedded into Fossil itself. Fossil's build system and test suite are
largely based on Tcl.⁵ All of this is quite portable.

About half of Git's code is POSIX C, and about a third is POSIX shell
code. This is largely why the so-called "Git for Windows" distributions
(both [https://git-scm.com/download/win|first-party] and
[https://gitforwindows.org/|third-party]) are actually an
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
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







-
+












-
+







     organizations].
 
  *  <b>No easy drive-by contributions:</b> Git
     [https://www.git-scm.com/docs/git-request-pull|pull requests] offer
     a low-friction path to accepting
     [https://www.jonobacon.com/2012/07/25/building-strong-community-structural-integrity/|drive-by
     contributions]. Fossil's closest equivalents are its unique
     [/help?cmd=bundle|bundle] and [/help?cmd=patch|patch] features, which require higher engagement
     [/help/bundle|bundle] and [/help/patch|patch] features, which require higher engagement
     than firing off a PR.⁷ This difference comes directly from the
     initial designed purpose for each tool: the SQLite project doesn't
     accept outside contributions from previously-unknown developers, but
     the Linux kernel does.
 
  *  <b>No rebasing:</b> When your local repo clone syncs changes
     up to its parent, those changes are sent exactly as they were
     committed locally. [#history|There is no rebasing mechanism in
     Fossil, on purpose.]
 
  *  <b>Sync over push:</b> Explicit pushes are uncommon in
     Fossil-based projects: the default is to rely on
     [/help?cmd=autosync|autosync mode] instead, in which each commit
     [/help/autosync|autosync mode] instead, in which each commit
     syncs immediately to its parent repository. This is a mode so you
     can turn it off temporarily when needed, such as when working
     offline. Fossil is still a truly distributed version control system;
     it's just that its starting default is to assume you're rarely out
     of communication with the parent repo.
     <br><br>
     This is not merely a reflection of modern always-connected computing
596
597
598
599
600
601
602
603

604
605
606
607
608
609
610
596
597
598
599
600
601
602

603
604
605
606
607
608
609
610







-
+








Because Git commingles the repository data with the initial checkout of
that repository, the default mode of operation in Git is to stick to that
single work/repo tree, even when that's a shortsighted way of working.

Fossil doesn't work that way. A Fossil repository is an SQLite database
file which is normally stored outside the working checkout directory. You can
[/help?cmd=open | open] a Fossil repository any number of times into
[/help/open | open] a Fossil repository any number of times into
any number of working directories. A common usage pattern is to have one
working directory per active working branch, so that switching branches
is done with a <tt>cd</tt> command rather than by checking out the
branches successively in a single working directory.

Fossil does allow you to switch branches within a working checkout
directory, and this is also often done. It is simply that there is no
680
681
682
683
684
685
686
687

688
689
690
691
692
693
694
680
681
682
683
684
685
686

687
688
689
690
691
692
693
694







-
+







including all of the messy errors, dead-ends, experimental branches, and
so forth.  One might argue that this
makes the history of a Fossil project "messy," but another point of view
is that this makes the history "accurate."  In actual practice, the
superior reporting tools available in Fossil mean that this incidental mess
is not a factor.

Like Git, Fossil has an [/help?cmd=amend|amend command] for modifying
Like Git, Fossil has an [/help/amend|amend command] for modifying
prior commits, but unlike in Git, this works not by replacing data in
the repository, but by adding a correction record to the repository that
affects how later Fossil operations present the corrected data. The old
information is still there in the repository, it is just overridden from
the amendment point forward.

Fossil lacks almost every other history rewriting mechanism listed on
710
711
712
713
714
715
716
717

718
719
720
721
722
723
724
710
711
712
713
714
715
716

717
718
719
720
721
722
723
724







-
+







shun certain committed artifacts, but a person cannot force their local
shun requests into another repo without having admin-level control over
the receiving repo as well. Fossil's shun feature isn't for fixing up
everyday bad commits, it's for dealing with extreme situations: public
commits of secret material, ticket/wiki/forum spam, law enforcement
takedown demands, etc.

There is also the experimental [/help?cmd=purge | <tt>purge</tt>
There is also the experimental [/help/purge | <tt>purge</tt>
command], which differs from shunning in ways that aren't especially
important in the context of this document. At a 30000 foot level, you
can think of purging as useful only when you've turned off Fossil's
autosync feature and want to pluck artifacts out of its hash tree before
they get pushed. In that sense, it's approximately the same as
<tt>git rebase -i, drop</tt>. However, given that Fossil defaults to
having autosync enabled [#devorg | for good reason], the purge command
819
820
821
822
823
824
825
826

827
828
829
830
831
832
833
819
820
821
822
823
824
825

826
827
828
829
830
831
832
833







-
+







much work gets applied — just one check-in or a whole branch — and the
merge direction.  This is the sort of thing we mean when we point out
that Fossil's command interface is simpler than Git's: there are fewer
concepts to keep track of in your mental model of Fossil's internal
operation.

Fossil's implementation of the feature is also simpler to describe. The
brief online help for <tt>[/help?cmd=merge | fossil merge]</tt> is
brief online help for <tt>[/help/merge | fossil merge]</tt> is
currently 41 lines long, to which you want to add the 600 lines of
[./branching.wiki | the branching document]. The equivalent
documentation in Git is the aggregation of the man pages for the above
three commands, which is over 1000 lines, much of it mutually redundant.
(e.g.  Git's <tt>--edit</tt> and <tt>--no-commit</tt> options get
described three times, each time differently.) Fossil's
documentation is not only more concise, it gives a nice split of brief
Changes to www/fossil_prompt.sh.
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



































1

2














3
4
5
6
7
8
9
10
11
12


13

14
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-

-

#-------------------------------------------------------------------------
#   get_fossil_data()
#
# If the current directory is part of a fossil checkout, then populate
# a series of global variables based on the current state of that
# checkout. Variables are populated based on the output of the [fossil info]
# command.
#
# If the current directory is not part of a fossil checkout, set global
# variable $fossil_info_project_name to an empty string and return.
#
function get_fossil_data() {
  fossil_info_project_name=""
  eval `get_fossil_data2`
}
function get_fossil_data2() {
  fossil info 2> /dev/null |tr '\042\047\140' _|grep "^[^ ]*:" |
  while read LINE ; do
    local field=`echo $LINE | sed 's/:.*$//' | sed 's/-/_/'`
    local value=`echo $LINE | sed 's/^[^ ]*: *//'`
    echo fossil_info_${field}=\"${value}\"
  done
}

#-------------------------------------------------------------------------
#   set_prompt()
#
# Set the PS1 variable. If the current directory is part of a fossil
# checkout then the prompt contains information relating to the state
# of the checkout. 
#
# Otherwise, if the current directory is not part of a fossil checkout, it
# is set to a fairly standard bash prompt containing the host name, user
# name and current directory.
#
function set_prompt() {
  get_fossil_data
  case `fossil status -b` in
  if [ -n "$fossil_info_project_name" ] ; then 
    # Color the path part of the prompt blue if this is a clean checkout
    # Or red if it has been edited in any way at all. Set $c1 to the escape
    # sequence required to change the type to the required color. And $c2
    # to the sequence that changes it back.
    #
    if [ -n "`fossil chang`" ] ; then
      c1="\[\033[1;31m\]"           # red
    else
      c1="\[\033[1;34m\]"           # blue
    fi
    c2="\[\033[0m\]"
    PS1="\[\033[01;32m\]\u@\h\[\033[00m\]:$c1\w\$$c2 "
  else
    clean)
       PS1="\[\e[1;32m\]\u@\h\[\e[0m\]:\[\e[1;36m\]\w\$\[\e[0m\] "
       ;;
    dirty)
       PS1="\[\e[1;32m\]\u@\h\[\e[0m\]:\[\e[38;5;202m\]\w\$\[\e[0m\] "
       ;;
    *)
       PS1="\[\e[1;32m\]\u@\h\[\e[0m\]:\w\$ "
       ;;
  esac
    PS1="\[\033[01;32m\]\u@\h\[\033[00m\]:\w\$ "
  fi
}

PROMPT_COMMAND=set_prompt
Changes to www/gitusers.md.
35
36
37
38
39
40
41
42

43
44
45
46
47
48
49
35
36
37
38
39
40
41

42
43
44
45
46
47
48
49







-
+







Forum][ffor].

While we do try to explain Fossil-specific terminology inline here
as-needed, you may find it helpful to skim [the Fossil glossary][gloss].
It will give you another take on our definitions here, and it may help
you to understand some of the other Fossil docs better.

[fbis]:  /help?cmd=bisect
[fbis]:  /help/bisect
[gbis]:  https://git-scm.com/docs/git-bisect
[ffor]:  https://fossil-scm.org/forum
[fvg]:   ./fossil-v-git.wiki


<a id="mwd"></a>
## Repositories and Checkouts Are Distinct
88
89
90
91
92
93
94
95

96
97
98
99
100
101
102
88
89
90
91
92
93
94

95
96
97
98
99
100
101
102







-
+







than `fossil checkout`. That said, one of those differences does match
up with Git users’ expectations: `fossil checkout` doesn’t pull changes
from the remote repository into the local clone as `fossil update` does.
We think this is less broadly useful, but that’s the subject of the next
section.

[ckwf]: ./ckout-workflows.md
[co]:   /help?cmd=checkout
[co]:   /help/checkout


#### <a id="pullup"></a> Update vs Pull

The closest equivalent to [`git pull`][gpull] is not
[`fossil pull`][fpull], but in fact [`fossil up`][up].

129
130
131
132
133
134
135
136

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

136
137
138
139
140
141
142
143







-
+







In fact, these are the same operation, so they’re the same command in
Fossil. The first form simply allows the `VERSION` to be implicit: the
tip of the current branch.

We think this is a more sensible command design than `git pull` vs
`git checkout`. ([…vs `git checkout` vs `git checkout`!][gcokoan])

[fpull]:   /help?cmd=pull
[fpull]:   /help/pull
[gpull]:   https://git-scm.com/docs/git-pull
[gcokoan]: https://stevelosh.com/blog/2013/04/git-koans/#s2-one-thing-well


#### <a id="close" name="dotfile"></a> Closing a Check-Out

The [`fossil close`][close] command dissociates a check-out directory from the
167
168
169
170
171
172
173
174

175
176
177
178
179
180
181
167
168
169
170
171
172
173

174
175
176
177
178
179
180
181







-
+








Closing a check-out directory is a rare operation. One use case
is that you’re about to delete the directory, so you want Fossil to forget about it
for the purposes of commands like [`fossil all`][all]. Even that isn’t
necessary, because Fossil will detect that this has happened and forget
the working directory for you.

[all]: /help?cmd=all
[all]: /help/all


#### <a id="worktree"></a> Git Worktrees

There are at least three different ways to get [Fossil-style multiple
check-out directories][mcw] with Git.

269
270
271
272
273
274
275
276
277


278
279
280
281
282
283





284
285
286
287
288
289
290
269
270
271
272
273
274
275


276
277
278





279
280
281
282
283
284
285
286
287
288
289
290







-
-
+
+

-
-
-
-
-
+
+
+
+
+







Git, not to commend this `.fsl`-at-project-root trick to you. A better
choice would be `~/museum/home/long-established-project.fossil`, if
you’re following [the directory scheme exemplified in the glossary](./glossary.md#repository). That said, it
does emphasize an earlier point: Fossil doesn’t care where you put the
repo DB file or what you name it.


[clone]:  /help?cmd=clone
[close]:  /help?cmd=close
[clone]:  /help/clone
[close]:  /help/close
[gloss]:  ./glossary.md
[open]:   /help?cmd=open
[set]:    /help?cmd=setting
[server]: /help?cmd=server
[stash]:  /help?cmd=stash
[undo]:   /help?cmd=undo
[open]:   /help/open
[set]:    /help/setting
[server]: /help/server
[stash]:  /help/stash
[undo]:   /help/undo


## <a id="log"></a> Fossil’s Timeline Is the “Log”

Git users often need to use the `git log` command to dig linearly through
commit histories due to its [weak data model][wdm], giving [O(n)
performance][ocomp].
414
415
416
417
418
419
420
421
422


423
424
425
426



427
428
429
430
431
432
433
414
415
416
417
418
419
420


421
422
423



424
425
426
427
428
429
430
431
432
433







-
-
+
+

-
-
-
+
+
+








Granted, that’s rather obscure, but you you can also choose something
intermediate like “`f time desc curr`”, which is reasonably clear.

[35pct]: https://www.sqlite.org/fasterthanfs.html
[btree]: https://sqlite.org/btreemodule.html
[gcn]:   https://git-scm.com/docs/gitrevisions
[infoc]: /help?cmd=info
[infow]: /help?cmd=/info
[infoc]: /help/info
[infow]: /help/www/info
[ocomp]: https://www.bigocheatsheet.com/
[tlc]:   /help?cmd=timeline
[tlw]:   /help?cmd=/timeline
[up]:    /help?cmd=update
[tlc]:   /help/timeline
[tlw]:   /help/www/timeline
[up]:    /help/update
[wdm]:   ./fossil-v-git.wiki#durable


## <a id="dhead"></a> Detached HEAD State

The SQL indexes in Fossil which we brought up above have a useful
side benefit: you cannot have a [detached HEAD state][gdh] in Fossil,
629
630
631
632
633
634
635
636

637
638
639
640
641
642
643
629
630
631
632
633
634
635

636
637
638
639
640
641
642
643







-
+








Fossil doesn’t need to be told what to push or where to push it: it just
keeps using the same remote server URL you gave it last
until you [tell it to do something different][rem]. It pushes all
branches, not just one named local branch.

[capt]: ./cap-theorem.md
[rem]:  /help?cmd=remote
[rem]:  /help/remote


<a id="autosync"></a>
## Autosync

Fossil’s [autosync][wflow] feature, normally enabled, has no
equivalent in Git. If you want Fossil to behave like Git, you can turn
939
940
941
942
943
944
945
946

947
948
949
950
951
952
953
939
940
941
942
943
944
945

946
947
948
949
950
951
952
953







-
+







diff implementation, bypassing [your local `diff-command` setting][dcset]
since the `--numstat` option has no effect when you have an external diff
command set.

If you leave off the `-v` flag in the second example, the `diffstat`
output won’t include info about any newly-added files.

[dcset]: https://fossil-scm.org/home/help?cmd=diff-command
[dcset]: https://fossil-scm.org/home/help/diff-command
[dst]:   https://invisible-island.net/diffstat/diffstat.html


<a id="btnames"></a>
## Branch and Tag Names

Fossil has no special restrictions on the names of tags and branches,
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
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







-
+
















-
+







release in a project that uses this tagging convention.

[The `fossil git export` command][fge] squashes repeated tags down to a
single instance to avoid confusing Git, exporting only the newest tag,
emulating Fossil’s own ambiguity resolution rule as best it can within
Git’s limitations.

[fge]:  /help?cmd=git
[fge]:  /help/git
[gcrf]: https://git-scm.com/docs/git-check-ref-format




<a id="cpickrev"></a>
## Cherry-Picking and Reverting Commits

Git’s separate "`git cherry-pick`" and “`git revert`” commands are
options to the [`fossil merge` command][merge]: `--cherrypick` and
`--backout`, respectively. We view this as sensible, since these are
both merge operations, and the two actions differ only in direction.

Unlike in Git, the Fossil file format remembers cherrypicks and backouts
and can later show them as dashed lines on the graphical timeline.

[merge]: /help?cmd=merge
[merge]: /help/merge



<a id="mvrm"></a>
## File Moves and Renames Are Soft by Default

The "[`fossil mv`][mv]" and "[`fossil rm`][rm]" commands work like they
1003
1004
1005
1006
1007
1008
1009
1010
1011


1012
1013
1014
1015
1016
1017
1018
1003
1004
1005
1006
1007
1008
1009


1010
1011
1012
1013
1014
1015
1016
1017
1018







-
-
+
+







this setting hasn’t been overridden locally.

If you want to keep Fossil’s soft `mv/rm` behavior most of the time, you
can cast it away on a per-command basis:

    fossil mv --hard old-name new-name

[mv]: /help?cmd=mv
[rm]: /help?cmd=rm
[mv]: /help/mv
[rm]: /help/rm


----


## <a id="cvdate" name="cs1"></a> Case Study 1: Checking Out a Version by Date

Changes to www/globs.md.
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
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







-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+

-
-
+
+

-
-
-
-
+
+
+
+
















-
-
-
+
+
+







than archiving the entire checkin.

The commands [`http`][], [`cgi`][], [`server`][], and [`ui`][] that
implement or support with web servers provide a mechanism to name some
files to serve with static content where a list of glob patterns
specifies what content may be served.

[`add`]:       /help?cmd=add
[`addremove`]: /help?cmd=addremove
[`changes`]:   /help?cmd=changes
[`clean`]:     /help?cmd=clean
[`commit`]:    /help?cmd=commit
[`extras`]:    /help?cmd=extras
[`merge`]:     /help?cmd=merge
[`settings`]:  /help?cmd=settings
[`status`]:    /help?cmd=status
[`touch`]:     /help?cmd=touch
[`unset`]:     /help?cmd=unset
[`add`]:       /help/add
[`addremove`]: /help/addremove
[`changes`]:   /help/changes
[`clean`]:     /help/clean
[`commit`]:    /help/commit
[`extras`]:    /help/extras
[`merge`]:     /help/merge
[`settings`]:  /help/settings
[`status`]:    /help/status
[`touch`]:     /help/touch
[`unset`]:     /help/unset

[`tarball`]:   /help?cmd=tarball
[`zip`]:       /help?cmd=zip
[`tarball`]:   /help/tarball
[`zip`]:       /help/zip

[`http`]:      /help?cmd=http
[`cgi`]:       /help?cmd=cgi
[`server`]:    /help?cmd=server
[`ui`]:        /help?cmd=ui
[`http`]:      /help/http
[`cgi`]:       /help/cgi
[`server`]:    /help/server
[`ui`]:        /help/ui


### Web Pages that Refer to Globs

The [`/timeline`][] page supports the query parameter `chng=GLOBLIST` that
names a list of glob patterns defining which files to focus the
timeline on. It also has the query parameters `t=TAG` and `r=TAG` that
names a tag to focus on, which can be configured with `ms=STYLE` to
use a glob pattern to match tag names instead of the default exact
match or a couple of other comparison styles.

The pages [`/tarball`][] and [`/zip`][] generate compressed archives
of a specific checkin. They may be further restricted by query
parameters that specify glob patterns that name files to include or
exclude rather than taking the entire checkin.

[`/timeline`]: /help?cmd=/timeline
[`/tarball`]:  /help?cmd=/tarball
[`/zip`]:      /help?cmd=/zip
[`/timeline`]: /help/www/timeline
[`/tarball`]:  /help/www/tarball
[`/zip`]:      /help/www/zip


## Platform Quirks

Fossil glob patterns are based on the glob pattern feature of POSIX
shells. Fossil glob patterns also have a quoting mechanism, discussed
[above](#syntax). Because other parts of your operating system may interpret glob
510
511
512
513
514
515
516
517
518


519
520
521
522
523
524
525
510
511
512
513
514
515
516


517
518
519
520
521
522
523
524
525







-
-
+
+








    $ fossil test-echo setting crlf-glob "*"
    C:\> echo * | fossil test-echo setting crlf-glob --args -

The [`test-glob`][] command is also handy to test if a string
matches a glob pattern.

[`test-echo`]: /help?cmd=test-echo
[`test-glob`]: /help?cmd=test-glob
[`test-echo`]: /help/test-echo
[`test-glob`]: /help/test-glob


## Converting `.gitignore` to `ignore-glob`

Many other version control systems handle the specific case of
ignoring certain files differently from Fossil: they have you create
individual "ignore" files in each folder, which specify things ignored
Changes to www/glossary.md.
88
89
90
91
92
93
94
95

96
97
98


99
100
101


102
103
104
105
106
107
108
88
89
90
91
92
93
94

95
96


97
98
99


100
101
102
103
104
105
106
107
108







-
+

-
-
+
+

-
-
+
+







    backup utility.

    As a counterexample, a project tracking your [Vim] configuration
    history is a much better use of Fossil, because it’s all held within
    `~/.vim`, and your user has full rights to that subdirectory.

[AIF]:     https://docs.asciidoctor.org/asciidoc/latest/directives/include/
[IGS]:     /help?cmd=ignore-glob
[IGS]:     /help/ignore-glob
[IFRS]:    ./image-format-vs-repo-size.md
[tarball]: /help?cmd=tarball
[tw]:      /help?cmd=/tarball
[tarball]: /help/tarball
[tw]:      /help/www/tarball
[Vim]:     https://www.vim.org/
[zip]:     /help?cmd=zip
[zw]:      /help?cmd=/zip
[zip]:     /help/zip
[zw]:      /help/www/zip


## Repository <a id="repository" name="repo"></a>

A single file that contains all historical versions of all files in a
project, which can be [cloned] to other machines and
[synchronized][sync] with them. Jargon: repo.
193
194
195
196
197
198
199
200

201
202
203
204
205
206
207





208
209
210
211
212
213
214
193
194
195
196
197
198
199

200
201
202





203
204
205
206
207
208
209
210
211
212
213
214







-
+


-
-
-
-
-
+
+
+
+
+







box "other/" fit
move right 0.1
line dotted right until even with previous line.end
move right 0.05
box invis "clones of Fossil itself, SQLite, etc." ljust
```

[asdis]:   /help?cmd=autosync
[asdis]:   /help/autosync
[backup]:  ./backup.md
[CAP]:     ./cap-theorem.md
[cloned]:  /help?cmd=clone
[pull]:    /help?cmd=pull
[push]:    /help?cmd=push
[svrcmd]:  /help?cmd=server
[sync]:    /help?cmd=sync
[cloned]:  /help/clone
[pull]:    /help/pull
[push]:    /help/push
[svrcmd]:  /help/server
[sync]:    /help/sync

[repository]:   #repo
[repositories]: #repo


## Version / Revision / Hash / UUID <a id="version" name="hash"></a>

308
309
310
311
312
313
314
315

316
317
318
319



320
321
322
323
324
325
326
308
309
310
311
312
313
314

315
316



317
318
319
320
321
322
323
324
325
326







-
+

-
-
-
+
+
+







  that Fossil will capture only changes to files you’ve [added][add] to
  the [repository], not to everything in [the check-out directory](#co)
  at the time of the snapshot. (Thus [the `extras` command][extras].)
  Contrast a snapshot taken by a virtual machine system or a
  [snapshotting file system][snfs], which captures changes to everything
  on the managed storage volume.

[add]:    /help?cmd=add
[add]:    /help/add
[ciname]: ./checkin_names.wiki
[extras]: /help?cmd=extras
[stash]:  /help?cmd=stash
[undo]:   /help?cmd=undo
[extras]: /help/extras
[stash]:  /help/stash
[undo]:   /help/undo



## Check-out <a id="check-out" name="co"></a>

A set of files extracted from a [repository] that represent a
particular [check-in](#ci) of the [project](#project).
363
364
365
366
367
368
369
370

371
372
373
374

375
376

377
378
379
380
381
382
383
363
364
365
366
367
368
369

370
371
372
373

374
375

376
377
378
379
380
381
382
383







-
+



-
+

-
+







    Fossil plugin for Visual Studio Code][fpvsc] defaults to storing the
    repo clone within the project directory as a file called `.fsl`, but
    this is because VSCode’s version control features assume it’s being
    used with Git, where the repository is the `.git` subdirectory
    contents. With Fossil, [different check-out workflows][cwork] are
    preferred.

[commit]: /help?cmd=commit
[commit]: /help/commit
[cwork]:  ./ckout-workflows.md
[h2cflp]: https://www.sqlite.org/howtocorrupt.html#_file_locking_problems
[fpvsc]:  https://marketplace.visualstudio.com/items?itemName=koog1000.fossil
[open]:   /help?cmd=open
[open]:   /help/open
[mwd]:    ./ckout-workflows.md#mcw
[update]: /help?cmd=update
[update]: /help/update


## <a id="docs"></a>Embedded Documentation

Serving as an alternative to Fossil’s built-in [wiki], the [embedded
documentation feature][edoc] stores the same type of marked-up text
files, but under Fossil’s powerful version control features.
Changes to www/grep.md.
97
98
99
100
101
102
103





104
105
106
107
108
109
110
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115







+
+
+
+
+







| `\S`    | Non-whitespace character: `[^ \t\r\n\v\f]`

There are several restrictions in Fossil `grep` relative to a fully
POSIX compatible regular expression engine. Among them are:

*   There is currently no support for POSIX character classes such as
    `[:lower:]`.

*   The values of `p` and `q` in the "`{p,q}`" syntax can be no greater
    than 999.  This is because the NFA that is used for regular expression
    matching is proportional in size to the largest p or q value, and hence
    allowing arbitrarily large values could result in a DoS attack.

*   Fossil `grep` does not currently attempt to take your operating
    system's locale settings into account when doing this match.  Since
    Fossil has no way to mark a given file as having a particular
    encoding, Fossil `grep` assumes the input files are in UTF-8 format.

    This means Fossil `grep` will not work correctly if the files in
Changes to www/gsoc-ideas.md.
22
23
24
25
26
27
28
29

30
31
32
33
34
35
36
22
23
24
25
26
27
28

29
30
31
32
33
34
35
36







-
+







features to work on in the UI.

# UI, Look and Feel

Tasks for those interested in graphic/web design:

* Add a quote button to the Forum, such as [discussed in this thread](https://fossil-scm.org/forum/forumpost/7ad03cd73d)
* Improve the documentation history-browsing page to enable selection of 2 arbitrary versions to diff, similar to the [Mediawiki history feature enabled on Wikipedia](https://en.wikipedia.org/w/index.php?title=Fossil_(software)&action=history)
* Improve the documentation history-browsing page to enable selection of 2 arbitrary versions to diff, similar to the [Mediawiki history feature enabled on Wikipedia](https://en.wikipedia.org/w/index.php?title=Fossil_\(software\)&action=history)
* Allow diffing of Forum posts
* General touch-ups in the existing skins. This may, depending on how deep one
  cares to dig, require digging into C code to find, and potentially modify, how
  the HTML is generated.
* Creation of one or more new skins. This does not specifically require any C
  know-how.
* Complete per-feature CSS facilities in [the Inskinerator](https://tangentsoft.com/inskinerator/dir) and add features to the Inskinerator
Changes to www/hashes.md.
143
144
145
146
147
148
149
150

151
152

153
154
143
144
145
146
147
148
149

150
151

152
153
154







-
+

-
+



[cin]:  ./checkin_names.wiki
[ctkt]: ./custom_ticket.wiki
[hpol]: ./hashpolicy.wiki
[japi]: ./json-api/
[jart]: ./json-api/api-artifact.md
[jtim]: ./json-api/api-timeline.md
[mset]: /help?cmd=manifest
[mset]: /help/manifest
[th1]:  ./th1.md
[trss]: /help?cmd=/timeline.rss
[trss]: /help/www/timeline.rss
[tvb]:  ./branching.wiki
[uuid]: https://en.wikipedia.org/wiki/Universally_unique_identifier
Changes to www/hashpolicy.wiki.
156
157
158
159
160
161
162
163

164
165
166
167
168
169
170
156
157
158
159
160
161
162

163
164
165
166
167
168
169
170







-
+







automatically switched to "sha3" mode and thereafter generated
only SHA3 hashes.

When a new repository is created by cloning, the hash policy is copied
from the parent.

For new repositories created using the
[/help?cmd=new|fossil new] command the default hash policy is "sha3".
[/help/new|fossil new] command the default hash policy is "sha3".
That means new repositories
will normally hold nothing except SHA3 hashes.  The hash policy for new
repositories can be overridden using the "--sha1" option to the
"fossil new" command.

If you are still on Fossil 2.1 through 2.9 but you want Fossil to go
ahead and start using SHA3 hashes, change the hash policy to
Changes to www/hints.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
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







-
+













-
+

-
+

















-
+

-
+







<title>Fossil Tips And Usage Hints</title>

A collection of useful hints and tricks in no particular order:

  1.  Click on two nodes of any timeline graph in succession
      to see a diff between the two versions.

  2.  Add the "--tk" option to "[/help?cmd=diff | fossil diff]" commands
  2.  Add the "--tk" option to "[/help/diff | fossil diff]" commands
      to get a pop-up
      window containing a complete side-by-side diff.  (NB:  The pop-up
      window is run as a separate Tcl/Tk process, so you will need to
      have Tcl/Tk installed on your machine for this to work.  Visit
      [http://www.activestate.com/activetcl] for a quick download of
      Tcl/Tk if you do not already have it on your system.)

  3.  The "[/help/clean | fossil clean -x]" command is a great
      alternative to "make clean". You can use "[/help/clean | fossil clean -f]"
      as a slightly safer alternative if the "ignore-glob" setting is
      not set. WARNING: make sure you did a "fossil add" for all source-files
      you plan to commit, otherwise those files will be deleted without warning.

  4.  Use "[/help?cmd=all | fossil all changes]" to look for any uncommitted
  4.  Use "[/help/all | fossil all changes]" to look for any uncommitted
      edits in any of your Fossil projects.  Use
      "[/help?cmd=all | fossil all pull]" on your laptop
      "[/help/all | fossil all pull]" on your laptop
      prior to going off network (for example, on a long plane ride)
      to make sure you have all the latest content locally.  Then run
      "[/help/all|fossil all push]" when you get back online to upload
      your changes.

  5.  To see an entire timeline, type "all" into the "Max:" entry box.

  6.  You can manually add a "c=CHECKIN" query parameter to the timeline
      URL to get a snapshot of what was going on about the time of some
      check-in.  The "CHECKIN" can be
      [./checkin_names.wiki | any valid check-in or version name], including
      tags, branch names, and dates.  For example, to see what was going
      on in the Fossil repository on 2008-01-01, visit
      [/timeline?c=2008-01-01].

  7.  Further to the previous two hints, there are lots of query parameters
      that you can add to timeline pages.  The available query parameters
      are tersely documented [/help?cmd=/timeline | here].
      are tersely documented [/help/www/timeline | here].

  8.  You can run "[/help?cmd=xdiff | fossil xdiff --tk $file1 $file2]"
  8.  You can run "[/help/xdiff | fossil xdiff --tk $file1 $file2]"
      to get a Tk pop-up window with side-by-side diffs of two files, even if
      neither of the two files is part of any Fossil repository.  Note that
      this command is "xdiff", not "diff".  Change <nobr>--tk</nobr> to
      <nobr>--by</nobr> to see the diff in your web browser.

  9.  On web pages showing the content of a file (for example
      [/artifact/c7dd1de9f]) you can manually
59
60
61
62
63
64
65
66

67
68
69
70
71
72
73

74
59
60
61
62
63
64
65

66
67
68
69
70
71
72

73
74







-
+






-
+

      a mimetype of text/plain, of course.

  10.  When editing documentation to be checked in as managed files, you can
       preview what the documentation will look like by using the special
       "ckout" branch name in the "doc" URL while running "fossil ui".
       See the [./embeddeddoc.wiki | embedded documentation] for details.

  11.  Use the "[/help?cmd=ui|fossil ui /]" command to bring up a menu of
  11.  Use the "[/help/ui|fossil ui /]" command to bring up a menu of
       all of your local Fossil repositories in your web browser.

  12.  If you have a bunch of Fossil repositories living on a remote machine
       that you are able to access using ssh using a command like
       "ssh login@remote", then you can bring up a user interface for all
       those remote repositories using the command:
       "[/help?cmd=ui|fossil ui login@remote:/]".  This works by tunneling
       "[/help/ui|fossil ui login@remote:/]".  This works by tunneling
       all HTTP traffic through SSH to the remote machine.
Changes to www/hooks.md.
114
115
116
117
118
119
120
121

122
123
124
125
126
127
128
114
115
116
117
118
119
120

121
122
123
124
125
126
127
128







-
+







     a write-transaction on the repository when the before-commit
     hook is running, so the repository needs to be in WAL mode if the
     script needs to access the repository.

  *  The %A substitution is the name of a "commit description file" that
     shows the details of the commit in progress.  To see what a
     "commit description file" looks like, set a before-commit hook
     with a command of "cat %Q" and then run a sample commit with
     with a command of "cat %A" and then run a sample commit with
     the --dry-run option.

  *  If any before-commit hook returns a non-zero exit code, then
     the commit is abandoned.  All
     before-commit hooks must exit(0) in order for the commit to
     proceed.

Changes to www/index.wiki.
82
83
84
85
86
87
88
89

90
91
92
93
94



95
96
97
98
99
100
101
82
83
84
85
86
87
88

89
90
91



92
93
94
95
96
97
98
99
100
101







-
+


-
-
-
+
+
+







      atomic even if interrupted by a power loss or system crash.
      Automatic [./selfcheck.wiki | self-checks] verify that all aspects of
      the repository are consistent prior to each commit.

  8.  <b>Free and Open-Source</b> — [../COPYRIGHT-BSD2.txt|2-clause BSD license].

<hr>
<h3>Latest Release: 2.24 ([/timeline?c=version-2.24|2024-04-23])</h3>
<h3>Latest Release: 2.27 ([/timeline?c=version-2.27|2025-09-30])</h3>

  *  [/uv/download.html|Download]
  *  [./changes.wiki#v2_24|Change Summary]
  *  [/timeline?p=version-2.24&bt=version-2.23&y=ci|Check-ins in version 2.24]
  *  [/timeline?df=version-2.24&y=ci|Check-ins derived from the 2.24 release]
  *  [./changes.wiki#v2_27|Change Summary]
  *  [/timeline?p=version-2.27&d=version-2.26&y=ci|Check-ins in version 2.27]
  *  [/timeline?df=version-2.27&y=ci|Check-ins derived from the 2.27 release]
  *  [/timeline?t=release|Timeline of all past releases]

<hr>
<h3>Quick Start</h3>

  1.  [/uv/download.html|Download] or install using a package manager or
      [./build.wiki|compile from sources].
Changes to www/inout.wiki.
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
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







-
+












-
-
-
+
+
+
+
-
-
+

+
-
-
+
+







interchange formats, and so for compatibility, use of the
--git option is recommended.

<a id="fx_git"></a>
Note that in new imports, Fossil defaults to using the email component of the
Git <em>committer</em> (or <em>author</em> if <code>--use-author</code> is
passed) to attribute check-ins in the imported repository. Alternatively, the
[/help?cmd=import | <code>--attribute</code>] option can be passed to have all
[/help/import | <code>--attribute</code>] option can be passed to have all
commits by a given committer attributed to a desired username. This will create
and populate the new <code>fx_git</code> table in the repository database to
maintain a record of correspondent usernames and email addresses that can be
used in subsequent exports or incremental imports.

<h3>Converting Repositories on Windows</h3>

The above commands work best on proper POSIX systems like Linux, macOS,
and the BSDs, where everything <tt>git</tt> sends is consumed by
<tt>fossil</tt> as soon as it can manage, with both programs working
concurrently.

For some reason, the current version of PowerShell included with Windows
chokes on the conversion when the in-flight repository size exceeds
available memory. We do not know why it buffers the entire stream
Historically, PowerShell indiscriminately sent objects — as opposed to raw
bytes — through its pipes, and buffered standard input for external processes.
This made it choke on the conversion when the in-flight repository size
exceeded available memory. Starting with version 7.4 (2023-11-16), PowerShell
emitted by "<tt>git fast-export</tt>" before sending it along to Fossil,
but we've seen the problem recur on multiple machines.
supports byte stream piping between native commands and file redirection.

If you are stuck with an older version, one workaround is to fall back to
While one workaround is to fall back to <tt>cmd.exe</tt> — which doesn't
seem to be affected by this problem — we instead recommend using
<tt>cmd.exe</tt> — which doesn't seem to be affected by this problem.
Nevertheless, we instead recommend using
Microsoft's own [https://learn.microsoft.com/en-us/windows/wsl/ | Windows
Subsystem for Linux] or either of the two popular "Git for Windows"
distributions based on MSYS2. They handle pipes the POSIX way, avoiding
any dependency on the amount of data involved.

<h2>Fossil → Git</h2>

Changes to www/interwiki.md.
62
63
64
65
66
67
68
69

70
71
72
73
74
75
76
62
63
64
65
66
67
68

69
70
71
72
73
74
75
76







-
+







<a id="intermap"></a>
## Intermap

The intermap defines a mapping from interwiki Tags to full URLs.  The
Intermap can be viewed and managed using the [fossil interwiki][iwiki]
command or the [/intermap][imap] webpage.

[iwiki]: /help?cmd=interwiki
[iwiki]: /help/interwiki
[imap]: /intermap

The current intermap for a server is seen on the [/intermap][imap] page
(which is read-only for non-Setup users) and at the bottom of the built-in
[Fossil Wiki rules](/wiki_rules) and [Markdown rules](/md_rules)
documentation pages.

98
99
100
101
102
103
104
105

106
107
108
109
110
111
112
113
114
115
116
117
98
99
100
101
102
103
104

105
106
107
108
109
110
111
112
113
114
115
116
117







-
+












     shows up in the "References" section of the target check-in.
     ([example](31af805348690958).  In other words, Fossil tracks not just
     "_source&rarr;target_", but it also tracks "_target&rarr;source_".
     But backtracking does not work for interwiki links, since the Fossil
     running on the target has no way of scanning the source text and
     hence has no way of knowing that it is a target of a link from the source.

[fcfg]: /help?cmd=config
[fcfg]: /help/config

## Intermap Storage Details

The intermap is stored in the CONFIG table of the repository database,
in entries with names of the form "<tt>interwiki:</tt><i>Tag</i>".  The
value for each such entry is a JSON string that defines the base URL
and extensions for Hash and Wiki links.

## See Also

  1. [](https://www.mediawiki.org/wiki/Manual:Interwiki)
  2. [](https://duckduckgo.com/?q=interwiki+links&ia=web)
Changes to www/javascript.md.
261
262
263
264
265
266
267
268

269
270
271
272
273
274

275
276
277
278
279
280
281
261
262
263
264
265
266
267

268
269
270
271
272
273

274
275
276
277
278
279
280
281







-
+





-
+








[2cbsd]:  https://fossil-scm.org/home/doc/trunk/COPYRIGHT-BSD2.txt
[ciu]:    https://caniuse.com/
[cskin]:  ./customskin.md
[dcsp]:   ./defcsp.md
[es2015]: https://ecma-international.org/ecma-262/6.0/
[es6dep]: https://caniuse.com/#feat=es6
[fcgi]:   /help?cmd=cgi
[fcgi]:   /help/cgi
[ffor]:   https://fossil-scm.org/forum/
[flic]:   /doc/trunk/COPYRIGHT-BSD2.txt
[fshome]: /doc/trunk/www/server/
[fslpl]:  /doc/trunk/www/fossil-v-git.wiki#portable
[fsrc]:   https://fossil-scm.org/home/file/src
[fsrv]:   /help?cmd=server
[fsrv]:   /help/server
[hljs]:   https://fossil-scm.org/forum/forumpost/9150bc22ca
[ie11x]:  https://techcommunity.microsoft.com/t5/microsoft-365-blog/microsoft-365-apps-say-farewell-to-internet-explorer-11-and/ba-p/1591666
[ns]:     https://noscript.net/
[pjs]:    https://fossil-scm.org/forum/forumpost/1198651c6d
[s1]:     https://blockmetry.com/blog/javascript-disabled
[s2]:     https://gds.blog.gov.uk/2013/10/21/how-many-people-are-missing-out-on-javascript-enhancement/
[s3]:     https://w3techs.com/technologies/overview/client_side_language/all
388
389
390
391
392
393
394
395

396
397
398
399
400
401
402
388
389
390
391
392
393
394

395
396
397
398
399
400
401
402







-
+







  ...write, write, write yet more...
:w !fossil wiki commit -                 # vi buffer updates article
```

Extending this concept to other text editors is an exercise left to the
reader.

[fwc]: /help?cmd=wiki
[fwc]: /help/wiki
[fwt]: ./wikitheory.wiki


### <a id="fedit"></a>The File Editor

Fossil’s [optional file editor feature][fedit] works
much like [the new wiki editor](#wedit), only on files committed to the
428
429
430
431
432
433
434
435

436
437
438
439
440
441
442
428
429
430
431
432
433
434

435
436
437
438
439
440
441
442







-
+







### <a id="ln"></a>Line Numbering

When viewing source files, Fossil offers to show line numbers in some
cases. ([Example][mainc].) Toggling them on and off is currently handled
in JavaScript, rather than forcing a page-reload via a button click.

_Workaround:_ Manually edit the URL to give the “`ln`” query parameter
per [the `/file` docs](/help?cmd=/file).
per [the `/file` docs](/help/www/file).

_Potential Better Workaround:_ Someone sufficiently interested could
[provide a patch][cg] to add a `<noscript>` wrapped HTML button that
would reload the page with this parameter included/excluded to implement
the toggle via a server round-trip.

A related feature is Fossil’s JavaScript-based interactive method
Changes to www/json-api/intro.md.
144
145
146
147
148
149
150
151

152
153
154
155
156
157
158
144
145
146
147
148
149
150

151
152
153
154
155
156
157
158







-
+








-   **Binary data:** JSON is a text serialization method, and it takes
    up the “payload” area of each HTTP request, so there is no
    reasonable way to include binary data in the JSON message without
    some sort of codec like Base64, for which there is no provision in
    the current JSON API. You will therefore find no JSON API for
    committing changes to a file in the repository, for example. Other
    Fossil APIs such as [`/raw`](/help?cmd=/raw) or
    Fossil APIs such as [`/raw`](/help/www/raw) or
    [`/fileedit`](../fileedit-page.md) may serve you better.
-   **64-bit integers:** The JSON standard does not specify integer precision,
    because it targets many different platforms, and not all of
    them can support more than 32 bits. JavaScript (from which JSON
    derives) supports 53 bits of integer precision, which may affect how
    a given client-side JSON implementation sends large integers to Fossil’s JSON
    API. Our JSON parser can cope with integers larger than 32 bits on input, and it
Changes to www/loadmgmt.md.
75
76
77
78
79
80
81
82

83
84
85
86
87
88
89
90
91
92
93

94
95
96
75
76
77
78
79
80
81

82
83
84
85
86
87
88
89
90
91
92

93
94
95
96







-
+










-
+




    chroot_jail_proc /home/www/proc proc ro 0 0

The `/home/www/proc` pathname should be adjusted so that the `/proc`
component is at the root of the chroot jail, of course.

To see if the load-average limiter is functional, visit the
[`/test_env`][hte] page of the server to view the current load average.
[`/test-env`][hte] 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. This can either be because it is in a
`chroot(2)` 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.


[503]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.4
[hte]: /help?cmd=/test_env
[hte]: /help/www/test-env
[gla]: https://linux.die.net/man/3/getloadavg
[lin]: http://www.linode.com
[sh]:  ./selfhost.wiki
Changes to www/makefile.wiki.
39
40
41
42
43
44
45
46

47
48
49
50
51
52
53
39
40
41
42
43
44
45

46
47
48
49
50
51
52
53







-
+







  6.  shell.c

All three SQLite source files are byte-for-byte copies of files by
the same name in the
standard [http://www.sqlite.org/amalgamation.html | amalgamation].
The sqlite3.c file implements the database engine.  The shell.c file
implements the command-line shell, which is accessed in fossil using
the [/help?cmd=sqlite3 | fossil sql] command.
the [/help/sqlite3 | fossil sql] command.

The shell.c command-line shell uses the [https://github.com/antirez/linenoise |
linenoise] library to implement line editing.  linenoise comprises two
source files which were copied from the upstream repository with only
very minor portability edits:

  7.  linenoise.c
70
71
72
73
74
75
76
77

78
79
80
81
82
83
84
70
71
72
73
74
75
76

77
78
79
80
81
82
83
84







-
+







byte-array constants that contain various resources such as scripts and
images.  The builtin_data.h header file is generated from the original
resource files using a small program called:

  12   [/file/tools/mkbuiltin.c | mkbuiltin.c]

Examples of built-in resources include the [/file/src/diff.tcl | diff.tcl]
script used to implement the --tk option to [/help?cmd=diff| fossil diff],
script used to implement the --tk option to [/help/diff| fossil diff],
the [/file/src/markdown.md | markdown documentation], and the various
CSS scripts, headers, and footers used to implement built-in skins.  New
resources files are added to the "extra_files" variable in
[/file/tools/makemake.tcl | makemake.tcl].

The src/ subdirectory also contains documentation about the
makeheaders preprocessor program:
263
264
265
266
267
268
269
270

271
272
273
274

275
276
277
278
279
280
281
263
264
265
266
267
268
269

270
271
272
273

274
275
276
277
278
279
280
281







-
+



-
+







  *  -DSQLITE_LIKE_DOESNT_MATCH_BLOBS=1
  *  -DSQLITE_THREADSAFE=0
  *  -DSQLITE_DEFAULT_FILE_FORMAT=4
  *  -DSQLITE_ENABLE_EXPLAIN_COMMENTS=1

The first three symbol definitions above are required; the others are merely
recommended.  Extension loading is omitted as a security measure. The dbstat
virtual table is needed for the [/help?cmd=/repo-tabsize|/repo-tabsize] page.
virtual table is needed for the [/help/www/repo-tabsize|/repo-tabsize] page.
FTS4 is needed for the search feature.  Fossil is single-threaded so mutexing
is disabled in SQLite as a performance enhancement.  The
SQLITE_ENABLE_EXPLAIN_COMMENTS option makes the output of "EXPLAIN" queries
in the  "[/help?cmd=sqlite3|fossil sql]" command much more readable.
in the  "[/help/sqlite3|fossil sql]" command much more readable.

When compiling the shell.c source file, these macros are required:

  *  -Dmain=sqlite3_main
  *  -DSQLITE_OMIT_LOAD_EXTENSION=1

The "main()" routine in the shell must be changed into sqlite3_main()
Changes to www/mdtest/test1.md.
29
30
31
32
33
34
35
36

37
38
39
40
41
42
43
29
30
31
32
33
34
35

36
37
38
39
40
41
42
43







-
+








The $ROOT prefix on markdown links is superfluous.  The same link
works without the $ROOT prefix.  (Though: the $ROOT prefix is required
for HTML documents.)

  *   Timeline:  [](/timeline)

  *   Help: [](/help?cmd=help)
  *   Help: [](/help/help)

  *   Site-map:  [](/sitemap)

## The Magic $CURRENT Document Version Translation

In URI text of the form `.../doc/$CURRENT/...` the
$CURRENT value is converted to the version number of the document
Changes to www/mirrorlimitations.md.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







# Limitations On Git Mirrors

The "<tt>[fossil git export](/help?cmd=git)</tt>" command can be used to
The "<tt>[fossil git export](/help/git)</tt>" command can be used to
mirror a Fossil repository to Git.
([Setup instructions](./mirrortogithub.md) and an
[example](https://github.com/drhsqlite/fossil-mirror).)
But the export to Git is not perfect. Some information is lost during
export due to limitations in Git.  This page describes what content of
Fossil is not included in an export to Git.

44
45
46
47
48
49
50



















51
52
53
54
55
56
57
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







check-in of each branch.  Depending on the check-in graph topology, this
is sufficient to infer the branch for many historical check-ins as well.
However, complex histories with lots of cross-merging
can lead to ambiguities.  Fossil keeps
track of historical branch names unambiguously, 
but the extra details about branch names that Fossil keeps
at hand cannot be exported to Git.

An example of the kinds of ambiguities that arise when branch names
are not tracked is a "diamond-merge" history. In a diamond-merge, a
long-running development branch merges enhancements from trunk from
time to time and also periodically merges the development changes back
to trunk at moments when the branch is stable.
An example of diamond-merge in the Fossil source tree itself
can be seen at on the [bv-corrections01 branch](/timeline?r=bv-corrections01).
The distinction between checkins on the branch and checkins on trunk would
be lost in Git, which does not track branches for individual checkins,
and so you cannot (easily) tell which checkins are part of the branch and
which are part of trunk in a diamond-merge history on Git.  For that
reason, diamond-merge histories are considered an anti-pattern in Git
and the usual recommendation for Git users is to employ
[rebase](./rebaseharm.md) to clean the history up.  The point here is
that if your project has a diamond-merge history that shows up cleanly
in Fossil, it will export to Git and still be technically correct, but 
the history display might be a jumbled mess that is difficult for humans
to comprehend.

## (4) Non-unique Tags

Git requires tags to be unique: each tag must refer to exactly one
check-in.  Fossil does not have this restriction, and so it is common
in Fossil to tag multiple check-ins with the same name.  For example,
it is common in Fossil to tag each check-in creating a release both
Changes to www/mirrortogithub.md.
128
129
130
131
132
133
134
135

136
137
138


139
140
141
142
143
144
145
128
129
130
131
132
133
134

135
136


137
138
139
140
141
142
143
144
145







-
+

-
-
+
+







     Fossil [UI][ui] browser window or with the [`user contact`][usercmd]
     subcommand on the command line. Alternatively, if this repository was
     previously imported from Git using the [`--attribute`][attr] option, the
     [`fx_git`][fxgit] table will be queried for correspondent email addresses.
     Only if neither of these methods produce a user specified email will the
     abovementioned generic address be used.

[attr]: /help?cmd=import
[attr]: /help/import
[fxgit]: ./inout.wiki#fx_git
[ui]: /help?cmd=ui
[usercmd]: /help?cmd=user
[ui]: /help/ui
[usercmd]: /help/user


## <a id='ex1'></a>Example GitHub Mirrors

As of this writing (2019-03-16) Fossil’s own repository is mirrored
on GitHub at:

Changes to www/password.wiki.
62
63
64
65
66
67
68
69

70
71
72
73
74
75
76
62
63
64
65
66
67
68

69
70
71
72
73
74
75
76







-
+







"developer", "reader", or "nobody" and the authentication protocol
for "anonymous" uses one-time captchas not persistent passwords.

<h2>Web Interface Authentication</h2>

When a user logs into Fossil using the web interface, the login name
and password are sent in the clear to the server.  For most modern fossil
server setups with [/help?cmd=redirect-to-https|redirect-to-https] enabled,
server setups with [/help/redirect-to-https|redirect-to-https] enabled,
this will be protected by the
SSL connection over HTTPS so it cannot be easily viewed. The server then
hashes the password and compares it against the value stored in USER.PW.
If they match, the server sets a cookie on the client to record the
login.  This cookie contains a large amount of high-quality randomness
and is thus intractable to guess.  The value of the cookie and the IP
address of the client is stored in the USER.COOKIE and USER.IPADDR fields
Changes to www/patchcmd.md.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







# The "fossil patch" command

The "[fossil patch](/help?cmd=patch)" command is designed to transfer
The "[fossil patch](/help/patch)" command is designed to transfer
uncommitted changes from one check-out to another, including transfering
those changes to other machines.

For example, if you are working on a Windows desktop and you want to
test your changes on a Linux server before you commit, you can use the
"fossil patch push" command to make a copy of all your changes on the
remote Linux server:
Changes to www/permutedindex.html.
103
104
105
106
107
108
109

110
111
112
113
114
115
116
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117







+







<li><a href="pop.wiki">Principles Of Operation</a></li>
<li><a href="qandc.wiki">Questions And Criticisms</a></li>
<li><a href="quotes.wiki">Quotes: What People Are Saying About Fossil, Git, and DVCSes in General</a></li>
<li><a href="rebaseharm.md">Rebase Considered Harmful</a></li>
<li><a href="reviews.wiki">Reviews</a></li>
<li><a href="chroot.md">Server Chroot Jail</a></li>
<li><a href="shunning.wiki">Shunning: Deleting Content From Fossil</a></li>
<li><a href="signing.md">Signing Check-ins</a></li>
<li><a href="../../../sitemap">Site Map</a></li>
<li><a href="style.wiki">Source Code Style Guidelines</a></li>
<li><a href="tech_overview.wiki">SQLite Databases Used By Fossil</a></li>
<li><a href="ssl-server.md">SSL/TLS Server Mode</a></li>
<li><a href="backoffice.md">The "Backoffice" mechanism of Fossil</a></li>
<li><a href="patchcmd.md">The "fossil patch" Command</a></li>
<li><a href="blame.wiki">The Annotate/Blame Algorithm Of Fossil</a></li>
Changes to www/private.wiki.
45
46
47
48
49
50
51
52

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

52
53
54
55
56
57
58
59







-
+








<div class="sidebar">
To avoid generating a missing artifact
reference on peer repositories without the private branch, the merge parent
is not recorded when merging the private branch into a public branch.  As a
consequence, the web UI timeline does not draw a merge line from the private
merge parent to the public merge child.  Moreover, repeat private-to-public
merge operations (without the [/help?cmd=merge | --force option]) with files
merge operations (without the [/help/merge | --force option]) with files
added on the private branch may only work once, but later abort with
"WARNING: no common ancestor for FILE", as the parent-child relationship is
not recorded. (See the [/doc/trunk/www/branching.wiki | Branching, Forking,
Merging, and Tagging] document for more information.)
</div>

The <code>--integrate</code> option of <code>fossil merge</code> (to close
Changes to www/quickstart.wiki.
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
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







-
+




-
+
-
-
-
-
+
+

















-
+
-





-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+






-
-
+
+

-
+







or <a href="build.wiki">compile it yourself</a> from sources.
Install Fossil by putting the fossil binary
someplace on your $PATH.

You can test that Fossil is present and working like this:

<pre><b>fossil version
This is fossil version 2.13 [309af345ab] 2020-09-28 04:02:55 UTC
This is fossil version 2.25 [8f798279d5] 2024-11-06 12:59:09 UTC
</b></pre>

<h2 id="workflow" name="fslclone">General Work Flow</h2>

Fossil works with repository files (a database in a single file with the project's
Fossil works with [./glossary.md#repository | repository files]
complete history) and with checked-out local trees (the working directory
you use to do your work). 
(See [./glossary.md | the glossary] for more background.)
The workflow looks like this:
and [./glossary.md#check-out | check-out directories] using a
workflow like this:

<ul>
    <li>Create or clone a repository file.  ([/help/init|fossil init] or
        [/help/clone | fossil clone])
    <li>Check out a local tree.  ([/help/open | fossil open])
    <li>Perform operations on the repository (including repository
        configuration).
</ul>

Fossil can be entirely driven from the command line. Many features
can also be conveniently accessed from the built-in web user interface.

The following sections give a brief overview of these
operations.

<h2 id="new">Starting A New Project</h2>

To start a new project with fossil create a new empty repository
To start a new project with Fossil, [/help/init | create a new empty repository]:
this way: ([/help/init | more info])

<pre><b>fossil init</b> <i>repository-filename</i>
</pre>

You can name the database anything you like, and you can place it anywhere in the filesystem.
The <tt>.fossil</tt> extension is traditional but only required if you are going to use the 
<tt>[/help/server | fossil server DIRECTORY]</tt> feature.
The <tt>.fossil</tt> extension is traditional, but it is only required if you are going to use the 
<tt>[/help/server | fossil server DIRECTORY]</tt> feature.

Next, do something along the lines of:

<pre>
<b>mkdir -p ~/src/project/trunk</b>
<b>cd ~/src/project/trunk</b>
<b>fossil open</b> <i>repository-filename</i>
<b>fossil add</b> foo.c bar.h qux.md
<b>fossil commit</b>
</pre>

If your project directory already exists, obviating the <b>mkdir</b>
step, you will instead need to add the <tt>--force</tt> flag to the
<b>open</b> command to authorize Fossil to open the repo into a
non-empty checkout directory. (This is to avoid accidental opens into,
for example, your home directory.)

The convention of naming your checkout directory after a long-lived
branch name like "trunk" is in support of Fossil's ability to have as
many open checkouts as you like. This author frequently has additional
checkout directories named <tt>../release</tt>, <tt>../scratch</tt>,
etc. The release directory is open to the branch of the same name, while
the scratch directory is used when disturbing one of the other
long-lived checkout directories is undesireable, as when performing a
[/help/bisect | bisect] operation.


<h2 id="clone">Cloning An Existing Repository</h2>

Most fossil operations interact with a repository that is on the
local disk drive, not on a remote system.  Hence, before accessing
a remote repository it is necessary to make a local copy of that
repository.  Making a local copy of a remote repository is called
"cloning".
repository, a process called
"[/help/clone | cloning]".

Clone a remote repository as follows: ([/help/clone | more info])
This is done as follows:

<pre><b>fossil clone</b> <i>URL repository-filename</i>
</pre>

The <i>URL</i> specifies the fossil repository
you want to clone.  The <i>repository-filename</i> is the new local
filename into which the cloned repository will be written.  For
79
80
81
82
83
84
85
86

87








88
89
90
91
92
93
94
102
103
104
105
106
107
108

109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125







-
+

+
+
+
+
+
+
+
+







Clone done, sent: 2424  received: 42965725  ip: 10.10.10.0
Rebuilding repository meta-data...
100% complete...
Extra delta compression... 
Vacuuming the database... 
project-id: 94259BB9F186226D80E49D1FA2DB29F935CCA0333
server-id:  016595e9043054038a9ea9bc526d7f33f7ac0e42
admin-user: exampleuser (password is "yoWgDR42iv")>
admin-user: exampleuser (intial remote-access password is "yoWgDR42iv")>
</b></pre>

This <i>exampleuser</i> will be used by Fossil as the author of commits when
you checkin changes to the repository.  It is also used by Fossil when you
make your repository available to others using the built-in server mode by
running <tt>[/help/server | fossil server]</tt> and will also be used when
running <tt>[/help/ui | fossil ui]</tt> to view the repository through 
the Fossil UI.  See the quick start topic for setting up a
<a href="#server">server</a> for more details.

If the remote repository requires a login, include a
userid in the URL like this:

<pre><b>fossil clone https://</b><i>remoteuserid</i><b>@www.example.org/ myclone.fossil</b></pre>

You will be prompted separately for the password.
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
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







-
+
-



-
+








-
-







[https://www.mercurial-scm.org/|Mercurial].
Fossil can also import [https://subversion.apache.org/|Subversion projects] directly.

<h2 id="checkout">Checking Out A Local Tree</h2>

To work on a project in fossil, you need to check out a local
copy of the source tree.  Create the directory you want to be
the root of your tree and cd into that directory.  Then
the root of your tree, <tt>cd</tt> into that directory, and then:
do this: ([/help/open | more info])

<pre><b>fossil open</b> <i>repository-filename</i></pre>

for example:
For example:

<pre><b>fossil open ../myclone.fossil
    BUILD.txt
    COPYRIGHT-BSD2.txt
    README.md
      ︙
</tt></b></pre>

(or "fossil open ..\myclone.fossil" on Windows).

This leaves you with the newest version of the tree
checked out.
From anywhere underneath the root of your local tree, you
can type commands like the following to find out the status of
your local tree:

<pre>
166
167
168
169
170
171
172
173

174
175
176
177
178
179
180
194
195
196
197
198
199
200

201
202
203
204
205
206
207
208







-
+








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.

To switch a checkout between different versions and branches,
use:<
use:

<pre>
<b>[/help/update | fossil update]</b>
<b>[/help/checkout | fossil checkout]</b>
</pre>

[/help/update | update] honors the "autosync" option and
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
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







-
-
+
+













-
+







the repository, being what you get when you "fossil open" a repository
without specifying a version, populating the working directory.

To see the most recent changes made to the repository by other users, use "fossil timeline" to
find out the most recent commit, and then "fossil diff" between that commit and the
current tree:

<pre><b>fossil timeline
=== 2021-03-28 === 
<pre><b><verbatim>fossil timeline
=== 2021-03-28 ===
03:18:54 [ad75dfa4a0] *CURRENT* Added details to frobnicate command (user: user-one tags: trunk) 
=== 2021-03-27 === 
23:58:05 [ab975c6632] Update README.md. (user: user-two tags: trunk) 
     ⋮ 

fossil diff --from current --to ab975c6632
Index: frobnicate.c
============================================================
--- frobnicate.c
+++ frobnicate.c
@@ -1,10 +1,11 @@
+/* made a change to the source file */
# Original text
</b></pre>
</verbatim></b></pre>

"current" is an alias for the checkout version, so the command
"fossil diff --from ad75dfa4a0 --to ab975c6632" gives identical results.

To commit your changes to a local-only repository:

<pre><b>fossil commit</b>     <i>(... Fossil will start your editor, if defined)</i><b>
266
267
268
269
270
271
272
273

274
275
276
277
278
279
280
294
295
296
297
298
299
300

301
302
303
304
305
306
307
308







-
+







specified Fossil uses line-editing in the terminal.

To commit your changes to a repository that was cloned from a remote
repository, you give the same command, but the results are different.
Fossil defaults to [./concepts.wiki#workflow|autosync] mode, a
single-stage commit that sends all changes committed to the local
repository immediately on to the remote parent repository. This only
works if you have write permission to the remote respository.
works if you have write permission to the remote repository.

<h2 id="naming">Naming of Files, Checkins, and Branches</h2>

Fossil deals with information artifacts. This Quickstart document only deals
with files and collections of files, but be aware there are also tickets, wiki pages and more. 
Every artifact in Fossil has a universally-unique hash id, and may also have a
human-readable name.
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
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







-
+

-
+
-
-
+
-
-
+





-
+


+
-
+
-
-
-
-
+
+
+





+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
+
+
+
+
+







the first checkin, and the default for any time a branch name is needed but not
specified.

This will get you started on identifying checkins. The
<a href="./checkin_names.wiki">Checkin Names document</a> is a complete reference, including
how timestamps can also be used.

<h2 id="config">Configuring Your Local Repository</h2>
<h2 id="config">Accessing Your Local Repository's Web User Interface</h2>

When you create a new repository, either by cloning an existing
After you create a new repository, you usually want to do some local
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
configuration.  This is most easily accomplished by firing up the Fossil
that is built into fossil.  Start the fossil web server like this:
([/help/ui | more info])
UI:

<pre>
<b>fossil ui</b> <i>repository-filename</i>
</pre>

You can omit the <i>repository-filename</i> from the command above
You can shorten that to just [/help/ui | <b>fossil ui</b>]
if you are inside a checked-out local tree.

This command starts an internal web server, after which Fossil
This starts a web server then automatically launches your
automatically launches your default browser, pointed at itself,
web browser and makes it point to this web server.  If your system
has an unusual configuration, fossil might not be able to figure out
how to start your web browser.  In that case, first tell fossil
where to find your web browser using a command like this:
presenting a special view of the repository, its web user interface.

You may override Fossil's logic for selecting the default browser so:

<pre>
<b>fossil setting web-browser</b> <i>path-to-web-browser</i>
</pre>

When launched this way, Fossil binds its internal web server to the IP
loopback address, 127.0.0.1, which it treats specially, bypassing all
user controls, effectively giving visitors the
[./caps/admin-v-setup.md#apsu | all-powerful Setup capabliity].
By default, fossil does not require a login for HTTP connections
coming in from the IP loopback address 127.0.0.1.  You can, and perhaps
should, change this after you create a few users.

Why is that a good idea, you ask? Because it is a safe
presumption that only someone with direct file access to the repository
database file could be using the resulting web interface. Anyone who can
modify the repo DB directly could give themselves any and all access
with a SQL query, or even by direct file manipulation; no amount of
access control matters to such a user.

(Contrast the [#server | many <i>other</i> ways] of setting Fossil up
as an HTTP server, where the repo DB is on the other side of the HTTP
server wall, inaccessible by all means other than Fossil's own
mediation. For this reason, the "localhost bypasses access control"
policy does <i>not</i> apply to these other interfaces. That is a very
good thing, since without this difference in policy, it would be unsafe
to bind a [/help/server | <b>fossil server</b>] instance to
localhost on a high-numbered port and then reverse-proxy it out to the
world via HTTPS, a practice this author does engage in, with confidence.)

When you are finished configuring, just press Control-C or use
the <b>kill</b> command to shut down the mini-server.
Once you are finished configuring Fossil, you may safely Control-C out
of the <b>fossil&nbsp;ui</b> command to shut down this privileged
built-in web server. Moreover, you may by grace of SQLite do this <i>at
any time</i>: all changes are either committed durably to the repo DB or
rolled back, in their totality. This includes configuration changes.

<h2 id="sharing">Sharing Changes</h2>

When [./concepts.wiki#workflow|autosync] is turned off,
the changes you [/help/commit | commit] are only
on your local repository.
To share those changes with other repositories, do:
382
383
384
385
386
387
388
389
390


391
392
393
394
395
396
397
398
399
400
401
429
430
431
432
433
434
435


436
437




438
439
440
441
442
443
444







-
-
+
+
-
-
-
-







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.

<h2 id="branch" name="merge">Branching And Merging</h2>

Use the --branch option to the [/help/commit | commit] command
to start a new branch.  Note that in Fossil, branches are normally
Use the --branch option to the [/help/commit | commit] command to start
a new branch at the point of need. ([./gitusers.md#bneed | Contrast git].)
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
until they are ready to commit.

To merge two branches back together, first
[/help/update | update] to the branch you want to merge into.
Then do a [/help/merge|merge] of the other branch that you want to incorporate
the changes from.  For example, to merge "featureX" changes into "trunk"
do this:

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
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







+
-
+
-



-


+
-
-
+
+
-

-
-
-
+
-
-
-
-
+
+
+
+
-
-
-
+
-

-
-
-
-
-
+
+


-
+
-
-
-
+
+
+
+




-
-
-
-
+
+
-
-
+







mistake.  Undo and redo only work for changes that have
not yet been checked in using commit and there is only a single
level of undo/redo.


<h2 id="server">Setting Up A Server</h2>

In addition to the inward-facing <b>fossil ui</b> mode covered [#config
Fossil can act as a stand-alone web server using one of these
| above], Fossil can also act as an outward-facing web server:
commands:

<pre>
<b>[/help/server | fossil server]</b> <i>repository-filename</i>
<b>[/help/ui | fossil ui]</b> <i>repository-filename</i>
</pre>

Just as with <b>fossil ui</b>, you may omit the
The <i>repository-filename</i> can be omitted when these commands
are run from within an open check-out, which is a particularly useful
<i>repository-filename</i> parameter when running this from within an open
check-out.
shortcut with the <b>fossil ui</b> command.

The <b>ui</b> command is intended for accessing the web user interface
from a local desktop. (We sometimes call this mode "Fossil UI.")
The <b>ui</b> command differs from the
<i>Unlike</i> <b>fossil ui</b> mode, Fossil binds to all network
<b>server</b> command by binding to the loopback IP
address only (thus making the web UI visible only on the
local machine) and by automatically starting your default web browser,
pointing it at the running UI
interfaces by default in this mode, and it enforces the configured
[./caps/ | role-based access controls]. Further, because it is meant to
provide external web service, it doesn't try to launch a local web
browser pointing to a "Fossil UI" presentation; external visitors see
server. The localhost restriction exists because it also gives anyone
who can access the resulting web UI full control over the
repository. (This is the [./caps/admin-v-setup.md#apsu | all-powerful
your repository's configured home page instead.
Setup capabliity].)

For cross-machine collaboration, use the <b>server</b> command instead,
which binds on all IP addresses, does not try to start a web browser,
and enforces [./caps/ | Fossil's role-based access control system].

Servers are also easily configured as:
To serve varying needs, there are additional ways to serve a Fossil repo
to external users:

<ul>
<li>[./server/any/inetd.md|inetd]
<li>[./server/any/cgi.md|CGI], as used by Fossil's [./selfhost.wiki |
<li>[./server/debian/service.md|systemd]
<li>[./server/any/cgi.md|CGI]
<li>[./server/any/scgi.md|SCGI]
    self-hosting repositories]
<li>[./server/any/scgi.md|SCGI]
<li>[./server/any/inetd.md|inetd]
<li>[./server/debian/service.md|systemd]
</ul>

…along with [./server/#matrix | several other options].

The [./selfhost.wiki | self-hosting fossil repositories] use
CGI.

You might <i>need</i> to set up a server, whether you know it yet or
We recommend that you read the [./server/whyuseaserver.wiki | Benefits
of a Fossil Server] article, because you might <i>need</i> to do this
not.  See the [./server/whyuseaserver.wiki | Benefits of a Fossil Server]
article for details.
and not yet know it.

<h2 id="proxy">HTTP Proxies</h2>

If you are behind a restrictive firewall that requires you to use
an HTTP proxy to reach the internet, then you can configure the proxy
in three different ways.  You can tell fossil about your proxy using
a command-line option on commands that use the network,
Changes to www/rebaseharm.md.
371
372
373
374
375
376
377
378

379
380
381
382
383
384
385
371
372
373
374
375
376
377

378
379
380
381
382
383
384
385







-
+







developers to make intuitive leaps that the original developer was
unable to make. In other words, you are asking your future maintenance
developers to be smarter than the original developers!  That's a
beautiful wish, but there's a sharp limit to how far you can carry it.
Eventually you hit the limits of human brilliance.

When the operation of some bit of code is not obvious, both Fossil and
Git let you run a [`blame`](/help?cmd=blame) on the code file to get
Git let you run a [`blame`](/help/blame) on the code file to get
information about each line of code, and from that which check-in last
touched a given line of code. If you squash the check-ins on a branch
down to a single check-in, you throw away the information leading up to
that finished form. Fossil not only preserves the check-ins surrounding
the one that included the line of code you're trying to understand, its
[superior data model][sdm] lets you see the surrounding check-ins in
both directions; not only what lead up to it, but what came next. Git
Changes to www/relatedwork.md.
64
65
66
67
68
69
70
71

72
73
74
75
76
77
78
64
65
66
67
68
69
70

71
72
73
74
75
76
77
78







-
+







[ChiselApp]:     https://chiselapp.com/
[CLion]:         https://www.jetbrains.com/clion/
[corec66]:       https://corecursive.com/066-sqlite-with-richard-hipp/
[Darcs]:         http://darcs.net/
[db2w]:          https://youtu.be/2eaQzahCeh4
[emacsfsl]:      https://chiselapp.com/user/venks/repository/emacs-fossil/doc/tip/README.md
[floss26]:       https://twit.tv/shows/floss-weekly/episodes/26
[fnc]:           https://fnc.bsdbox.org
[fnc]:           https://fnc.sh
[fsl]:           http://fossil.0branch.com/fsl
[Fuel]:          https://fuel-scm.org/fossil/index
[Git]:           https://git-scm.com
[GoLand]:        https://www.jetbrains.com/go/
[got]:           https://gameoftrees.org
[Inskinerator]:  https://tangentsoft.com/inskinerator
[IntelliJ]:      https://www.jetbrains.com/idea/
Changes to www/reviews.wiki.
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
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







-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+






-
-
+
+












-
+







another branch after the fact and shared-by-default branches are good
features. Also not having a misanthropic command line interface.
</div>

<b>Stephan Beal writes on 2009-01-11:</b>

<div class="indent">
Sometime in late 2007 I came across a link to fossil on
<a href="http://www.sqlite.org/">sqlite.org</a>. It
was a good thing I bookmarked it, because I was never able to find the
link again (it might have been in a bug report or something). The
reasons I first took a close look at it were (A) it stemmed from the
sqlite project, which I've held in high regards for years (e.g. I
Sometime in late 2007 I came across a link to fossil on <a
href="https://sqlite.org/">sqlite.org</a>. It was a good thing I
bookmarked it, because I was never able to find the link again (it
might have been in a bug report or something). The reasons I first
took a close look at it were (A) it stemmed from the sqlite project,
which I've held in high regards for years (e.g. I wrote bindings for
wrote JavaScript bindings for it:
<a href="http://spiderape.sourceforge.net/plugins/sqlite/">
http://spiderape.sourceforge.net/plugins/sqlite/</a>), and (B) it could
run as a CGI. That second point might seem a bit archaic, but in
practice CGI is the only way most hosted sites can set up a shared
source repository with multiple user IDs. (i'm not about to give out
my only account password or SSH key for my hosted sites, no matter how
much I trust the other developers, and none of my hosters allow me to
run standalone servers or add Apache modules.)
it for Mozilla's SpiderMonkey JavaScript engine), and (B) it could run
as a CGI. That second point might seem a bit archaic, but in practice
CGI is the only way most hosted sites can set up a shared source
repository with multiple user IDs. (i'm not about to give out my only
account password or SSH key for my hosted sites, no matter how much I
trust the other developers, and none of my hosters allow me to run
standalone servers or add Apache modules.)

So I tried it out. The thing which bugged me most about it was having
to type "commit" or "com" instead of "ci" for checking in (as is
custom in all other systems I've used), despite the fact that fossil
uses "ci" as a filter in things like the timeline view. Looking back
now, I have used fossil for about about 95% of my work in the past
year (<a href="http://blog.s11n.net/?p=71"><i>dead link</i></a>), in 
over 15 source trees, and I now get tripped up when I have to use svn or cvs.
year, in over 15 source trees, and I now get tripped up when I have to
use svn or cvs.

So, having got over typing "fossil com -m ...", here's why I love it so much...

Point #1: CGI

Again, this sounds archaic, but fossil has allowed me to share source
trees which I cannot justifiably host in other projects I work on
(they don't belong to those projects), which I cannot host in google
code (because google code doesn't allow/recognize Public Domain as a
license, and I refuse to relicense just to accommodate them), and for
which SourceForge is overkill (and way too slow). With fossil I can
create a new repo, have it installed on my hoster
(http://fossil.wanderinghorse.net), and be commiting code to it within
(https://fossil.wanderinghorse.net), and be commiting code to it within
5 minutes.

Point #2: Wiki

I hate wikis. I really do. Always have. They all have a different
syntax and the content tends to get really disorganized really
quickly. Their nature makes it difficult to reorganize them without
Changes to www/selfhost.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
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


-
+




-
+
















-
+







<title>Fossil Self-Hosting Repositories</title>

Fossil has self-hosted since 2007-07-21.  As of 2017-07-25
Fossil has self-hosted since 2007-07-21.  As of 2025-02-11
there are three publicly accessible repositories for the Fossil source code:

  1.  [https://fossil-scm.org/]
  2.  [https://www2.fossil-scm.org/]
  3.  [https://www3.fossil-scm.org/site.cgi]
  3.  [https://www3.fossil-scm.org/]

The canonical repository is (1).  Repositories (2) and (3) automatically
stay in synchronization with (1) via a
<a href="http://en.wikipedia.org/wiki/Cron">cron job</a> that invokes
"fossil sync" at regular intervals.
Repository (2) also publishes a
[https://github.com/drhsqlite/fossil-mirror|GitHub mirror of Fossil]
as a demonstration of [./mirrortogithub.md|how that can be done].

Note that the two secondary repositories are more than just read-only mirrors.
All three servers support full read/write capabilities.
Changes (such as new tickets or wiki or check-ins) can be implemented
on any of the three servers and those changes automatically propagate to the
other two servers.

Server (1) runs as a [./aboutcgi.wiki|CGI script] on a
<a href="http://www.linode.com/">Linode 8192</a> located in Dallas, TX
<a href="http://www.linode.com/">Linode</a> located in Dallas, TX
- on the same virtual machine that
hosts <a href="http://www.sqlite.org/">SQLite</a> and over a
dozen other smaller projects.  This demonstrates that Fossil can run on
a low-power host processor.
Multiple fossil-based projects can easily be hosted on the same machine,
even if that machine is itself one of several dozen virtual machines on
a single physical box.  The CGI script that runs the canonical Fossil
55
56
57
58
59
60
61

62
63
64
65
66
67
68
69
70
71
72
73

74
75
76

77
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

74
75
76

77
78







+











-
+


-
+

repository: /home/hwaci/fossil/fossil.fossil
</pre>

In recent years, virtual private servers have become a more flexible and
less expensive hosting option compared to shared hosting accounts.
So on 2017-07-25, server (3) was moved
onto a $5/month "droplet" [https://en.wikipedia.org/wiki/Virtual_private_server|VPS]
(update: $6/month now)
from [https://www.digitalocean.com|Digital Ocean]
located in San Francisco.

Server (3) is synchronized with the canonical server (1) by running
a command similar to the following via cron:

<pre>
/usr/local/bin/fossil all sync -u
</pre>

Server (2) is a
<a href="http://www.linode.com/">Linode 4096</a> located in Newark, NJ
<a href="http://www.linode.com/">Linode</a> located in Newark, NJ
and set up just like the canonical server (1) with the addition of a
cron job for synchronization.  The same cron job also runs the
[/help?cmd=git|fossil git export] command after each sync in order to
[/help/git|fossil git export] command after each sync in order to
[./mirrortogithub.md#ex1|mirror all changes to GitHub].
Changes to www/server/debian/service.md.
50
51
52
53
54
55
56
57

58
59
60
61
62
63
64
50
51
52
53
54
55
56

57
58
59
60
61
62
63
64







-
+







restrictions on TCP ports in every OS where `systemd` runs, but you can
create a listener socket on a high-numbered (&ge; 1024) TCP port,
suitable for sharing a Fossil repo to a workgroup on a private LAN.

To do this, write the following in
`~/.local/share/systemd/user/fossil.service`:

```dosini
> ```dosini
[Unit]
Description=Fossil user server
After=network-online.target

[Service]
WorkingDirectory=/home/fossil/museum
ExecStart=/home/fossil/bin/fossil server --port 9000 repo.fossil
162
163
164
165
166
167
168
169

170
171
172
173
174
175
176
162
163
164
165
166
167
168

169
170
171
172
173
174
175
176







-
+







socket listener, which `systemd` calls “[socket activation][sa],”
roughly equivalent to [the ancient `inetd` method](../any/inetd.md).
It’s more complicated, but it has some nice properties.

We first need to define the privileged socket listener by writing
`/etc/systemd/system/fossil.socket`:

```dosini
> ```dosini
[Unit]
Description=Fossil socket

[Socket]
Accept=yes
ListenStream=80
NoDelay=true
187
188
189
190
191
192
193
194

195
196
197
198
199
200
201
187
188
189
190
191
192
193

194
195
196
197
198
199
200
201







-
+







This configuration says more or less the same thing as the socket part
of an `inetd` entry [exemplified elsewhere in this
documentation](../any/inetd.md).

Next, create the service definition file in that same directory as
`fossil@.service`:

```dosini
> ```dosini
[Unit]
Description=Fossil socket server
After=network-online.target

[Service]
WorkingDirectory=/home/fossil/museum
ExecStart=/home/fossil/bin/fossil http repo.fossil
Changes to www/server/index.html.
36
37
38
39
40
41
42
43

44
45
46
47
48

49
50
51
52
53
54
55
36
37
38
39
40
41
42

43
44
45
46
47

48
49
50
51
52
53
54
55







-
+




-
+







Fossil server, with links to more detailed instructions specific to
particular systems, should you want extra help.</p>


<h2 id="prep">Repository Prep</h2>

<p>Prior to serving a Fossil repository to others, consider running <a
href="$ROOT/help?cmd=ui"><tt>fossil ui</tt></a> locally and taking these
href="$ROOT/help/ui"><tt>fossil ui</tt></a> locally and taking these
minimum recommended preparation steps:</p>

<ol>
  <li><p>Fossil creates only one user in a <a
  href="$ROOT/help?cmd=new">new repository</a> and gives it the <a
  href="$ROOT/help/new">new repository</a> and gives it the <a
  href="../caps/admin-v-setup.md#apsu">all-powerful Setup capability</a>.
  The 10-digit random password generated for that user is fairly strong
  against remote attack, even without explicit password guess rate
  limiting, but because that user has so much power, you may want to
  give it a much stronger password under Admin → Users.</a></li>

  <li><p>Run the Admin → Security-Audit tool to verify that other
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
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







-
+


















-
+












-
+









-
+








<h3 id="cgi">CGI</h3>

<p>Most ordinary web servers can <a href="any/cgi.md">run Fossil as a
CGI script</a>. This method is known to work with Apache,
<tt>lighttpd</tt>, and <a
href="any/althttpd.md"><tt>althttpd</tt></a>.  The Fossil server
administrator places a <a href="$ROOT/help?cmd=cgi">short CGI script</a> in
administrator places a <a href="$ROOT/help/cgi">short CGI script</a> in
the web server's document hierarchy and when a client requests the URL
that corresponds to that script, Fossil runs and generates the
response.</p>

<p>CGI is a good choice for merging Fossil into an existing web site,
particularly on hosts that have CGI set up and working.
The Fossil <a href="../selfhost.wiki">self-hosting repositories</a> are
implemented with CGI underneath <tt>althttpd</tt>.</p>

<h3 id="slist">Socket Listener</h3>

<p>Socket listener daemons such as
<a id="inetd" href="any/inetd.md"><tt>inetd</tt></a>, <a id="xinetd"
href="any/xinetd.md"><tt>xinetd</tt></a>, <a id="stunnel"
href="any/stunnel.md"><tt>stunnel</tt></a>, <a
href="macos/service.md"><tt>launchd</tt></a>, and <a
href="debian/service.md"><tt>systemd</tt></a>
can be configured to invoke the the
<a href="$ROOT/help?cmd=http"><tt>fossil http</tt></a> command to handle
<a href="$ROOT/help/http"><tt>fossil http</tt></a> command to handle
each incoming HTTP request.  The "<tt>fossil http</tt>" command reads
the HTTP request off of standard input, computes an appropriate
reply, and writes the reply on standard output.  There is a separate
invocation of the "<tt>fossil http</tt>" command for each HTTP request.
The socket listener daemon takes care of relaying content to and from
the client, and (in the case of <a href="any/stunnel.md">stunnel</a>) 
handling TLS decryption and encryption.

<h3 id="standalone">Stand-alone HTTP Server</h3>

<p>This is the <a href="any/none.md">easiest method</a>.
A stand-alone server uses the
<a href="$ROOT/help?cmd=server"><tt>fossil server</tt></a> command to run a
<a href="$ROOT/help/server"><tt>fossil server</tt></a> command to run a
process that listens for incoming HTTP requests on a socket and then
dispatches a copy of itself to deal with each incoming request. You can
expose Fossil directly to the clients in this way or you can interpose a
<a href="https://en.wikipedia.org/wiki/Reverse_proxy">reverse proxy</a>
layer between the clients and Fossil.</p>

<h3 id="scgi">SCGI</h3>

<p>The Fossil standalone server can also handle <a href="any/scgi.md">SCGI</a>.
When the <a href="$ROOT/help?cmd=server"><tt>fossil server</tt></a> command is
When the <a href="$ROOT/help/server"><tt>fossil server</tt></a> command is
run with the extra <tt>--scgi</tt> option, it listens for incoming
SCGI requests rather than HTTP requests. This allows Fossil to
respond to requests from web servers <a href="debian/nginx.md">such as
nginx</a> that don't support CGI. SCGI is a simpler protocol to proxy
than HTTP, since the HTTP doesn't have to be re-interpreted in terms of
the proxy's existing HTTP implementation, but it's more complex to set
up because you also have to set up an SCGI-to-HTTP proxy for it. It is
285
286
287
288
289
290
291
292

293
294
295
296
297
298
299
285
286
287
288
289
290
291

292
293
294
295
296
297
298
299







-
+







  <li><p>If the repository includes <a
  href="../embeddeddoc.wiki">embedded documentation</a>, consider
  activating the search feature (Admin → Search) so that visitors can do
  full-text search on your documentation.</p></li>

  <li><p>Now that others can be making changes to the repository,
  consider monitoring them via <a href="../alerts.md">email alerts</a>
  or the <a href="$ROOT/help?cmd=/timeline.rss">timeline RSS
  or the <a href="$ROOT/help/www/timeline.rss">timeline RSS
  feed</a>.</p></li>

  <li><p>Turn on the various logging features.</p></li>
</ol>

<p>Reload the Admin → Security-Audit page occasionally during this
process to double check that you have not mistakenly configured the
Changes to www/server/windows/service.md.
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
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







-
+






-
+


















-
+







When the Fossil server will be used at times that files may be locked
during virus scanning, it is prudent to arrange that its directory used
for temporary files is exempted from such scanning. Ordinarily, this
will be a subdirectory named "fossil" in the temporary directory given
by the Windows GetTempPath(...) API, [namely](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-gettemppathw#remarks)
the value of the first existing environment variable from `%TMP%`, `%TEMP%`,
`%USERPROFILE%`, and `%SystemRoot%`; you can look for their actual values in
your system by accessing the `/test_env` webpage. 
your system by accessing the `/test-env` webpage. 
Excluding this subdirectory will avoid certain rare failures where the
fossil.exe process is unable to use the directory normally during a scan.

### <a id='PowerShell'></a>Advanced service installation using PowerShell

As great as `fossil winsrv` is, it does not have one to one reflection of all of
the `fossil server` [options](/help?cmd=server).  When you need to use some of
the `fossil server` [options](/help/server).  When you need to use some of
the more advanced options, such as `--https`, `--skin`, or `--extroot`, you will
need to use PowerShell to configure and install the Windows service.

PowerShell provides the [New-Service](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/new-service?view=powershell-5.1)
command, which we can use to install and configure Fossil as a service.  The
below should all be entered as a single line in an Administrative PowerShell
console.

```PowerShell
New-Service -Name fossil -DisplayName fossil -BinaryPathName '"C:\Program Files\FossilSCM\fossil.exe" server --port 8080 --repolist "D:/Path/to/Repos"' -StartupType Automatic
```

Please note the use of forward slashes in the repolist path passed to Fossil.
Windows will accept either back slashes or forward slashes in path names, but
Fossil has a preference for forward slashes.  The use of `--repolist` will make
this a multiple repository server.  If you want to serve only a single
repository, then leave off the `--repolist` parameter and provide the full path
to the proper repository file. Other options are listed in the
[fossil server](/help?cmd=server) documentation.
[fossil server](/help/server) documentation.

The service will be installed by default to use the Local Service account.
Since Fossil only needs access to local files, this is fine and causes no
issues.  The service will not be running once installed.  You will need to start
it to proceed (the `-StartupType Automatic` parameter to `New-Service` will
result in the service auto-starting on boot).  This can be done by entering

Changes to www/serverext.wiki.
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
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







-
+












-
+



-
+

+
-
-
+
+
+
+







an "Extension Root Directory" or "extroot" as part of the 
[./server/index.html|server setup].
If the Fossil server is itself run as 
[./server/any/cgi.md|CGI], then add a line to the 
[./cgi.wiki#extroot|CGI script file] that says:

<pre>
extroot: <i>DIRECTORY</i>
    extroot: <i>DIRECTORY</i>
</pre>

Or, if the Fossil server is being run using the 
"[./server/any/none.md|fossil server]" or
"[./server/any/none.md|fossil ui]" or 
"[./server/any/inetd.md|fossil http]" commands, then add an extra 
"--extroot <i>DIRECTORY</i>" option to that command.

The <i>DIRECTORY</i> is the DOCUMENT_ROOT for the CGI.
Files in the DOCUMENT_ROOT are accessed via URLs like this:

<pre>
https://example-project.org/ext/<i>FILENAME</i>
    https://example-project.org/ext/<i>FILENAME</i>
</pre>

In other words, access files in DOCUMENT_ROOT by appending the filename
relative to DOCUMENT_ROOT to the [/help?cmd=/ext|/ext]
relative to DOCUMENT_ROOT to the [/help/www/ext|/ext]
page of the Fossil server.

Files that are readable but not executable are returned as static
content.  Files that are executable are run as CGI.
  *  Files that are readable but not executable are returned as static
     content.

  *  Files that are executable are run as CGI.

<h3>2.1 Example #1</h3>

The source code repository for SQLite is a Fossil server that is run
as CGI.  The URL for the source code repository is [https://sqlite.org/src].
The CGI script looks like this:

115
116
117
118
119
120
121

























122
123
124
125
126
127

128
129
130
131
132
133
134
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





-
+







script.  (The extension mechanism is not required to use Wapp.  You can use
any kind of program you like.  But the creator of SQLite and Fossil is fond
of [https://www.tcl.tk|Tcl/Tk] and so he tends to gravitate toward Tcl-based
technologies like Wapp.)  The fileup1 script is a demo program that lets
the user upload a file using a form, and then displays that file in the reply.
There is a link on the page that causes the fileup1 script to return a copy
of its own source-code, so you can see how it works.

<h3>2.3 Example #3</h3>

For Fossil versions dated 2025-03-23 and later, the "--extpage FILENAME"
option to the [/help/ui|fossil ui] command is a short cut that treats
FILENAME as a CGI extension.  When the ui command starts up a new web browser
pages, it points that page to the FILENAME extension.  So if FILENAME is
a static content file (such as an HTML file or 
[/md_rules|Markdown] or [/wiki_rules|Wiki] document), then the
rendered content of the file is displayed.  Meanwhile, the user can be
editing the source text for that document in a separate window, and
periodically pressing "Reload" on the web browser to instantly view the
rendered results.

For example, the author of this documentation page is running
"<tt>fossil ui --extpage www/serverext.wiki</tt>" while editing this
very paragraph, and presses Reload from time to time to view his
edits.

The same idea applies when developing new CGI applications using a script
language (for example using [https://wapp.tcl.tk|Wapp]).  Run the
command "<tt>fossil ui --extpage SCRIPT</tt>" where SCRIPT is the name
of the application script, while editing that script in a separate
window, then press Reload periodically on the web browser to test the
script.

<h2 id="cgi-inputs">3.0 CGI Inputs</h2>

The /ext extension mechanism is an ordinary CGI interface.  Parameters
are passed to the CGI program using environment variables.  The following
standard CGI environment variables are supported:
standard CGI environment variables are supplied:

  *  AUTH_TYPE
  *  AUTH_CONTENT
  *  CONTENT_LENGTH
  *  CONTENT_TYPE
  *  DOCUMENT_ROOT
  *  GATEWAY_INTERFACE
159
160
161
162
163
164
165
166

167
168
169
170
171
172
173
187
188
189
190
191
192
193

194
195
196
197
198
199
200
201







-
+







Do a web search for
"[https://duckduckgo.com/?q=cgi+environment_variables|cgi environment variables]"
to find more detail about what each of the above variables mean and how
they are used.
Live listings of the values of some or all of these environment variables
can be found at links like these:

  *  [https://fossil-scm.org/home/test_env]
  *  [https://fossil-scm.org/home/test-env]
  *  [https://sqlite.org/src/ext/checklist/top/env]

In addition to the standard CGI environment variables listed above, 
Fossil adds the following:

  *  FOSSIL_CAPABILITIES
  *  FOSSIL_NONCE
308
309
310
311
312
313
314







315
316
317
318
319
320
321
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356







+
+
+
+
+
+
+







in the same way as a traditional web-server.

CGI programs that want to restrict access 
can examine the FOSSIL_CAPABILITIES and/or FOSSIL_USER environment variables.
In other words, access control is the responsibility of the individual
extension programs.

<h3>6.1 Restricting Robot Access To Extensions</h3>

If the "ext" tag is found in the [/help/robot-restrict|robot-restrict setting]
then clients are tested to see if they are robots before granting
access to any extension.  If the "ext" tag is omitted but a tag
of the form "ext/PATH" is found on the robot-restrict setting, then
robots are restricted from the particular extension at PATH.

<h2>7.0 Trouble-Shooting Hints</h2>

Remember that the /ext will return any file in the extroot directory
hierarchy as static content if the file is readable but not executable.
When initially setting up the /ext mechanism, it is sometimes helpful
to verify that you are able to receive static content prior to starting
Changes to www/settings.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
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


-
+









-
+















-
+







<title>Fossil Settings</title>

<h2>Using Fossil Settings</h2>
<h1>Using Fossil Settings</h1>

Settings control the behaviour of fossil. They are set with the
<tt>fossil settings</tt> command, or through the web interface in
the Settings page in the Admin section.

For a list of all settings, view the Settings page, or type
<tt>fossil help settings</tt> from the command line.


<h3 id="repo">Repository settings</h3>
<h2 id="repo">1.0 Repository settings</h2>

Settings are set on a per-repository basis. When you clone a repository,
a subset of settings are copied to your local repository.

If you make a change to a setting on your local repository, it is not
synced back to the server when you <tt>push</tt> or <tt>sync</tt>. If
you make a change on the server, you need to manually make the change on
all repositories which are cloned from this repository.

You can also set a setting globally on your local machine. The value
will be used for all repositories cloned to your machine, unless
overridden explicitly in a particular repository. Global settings can be
set by using the <tt>-global</tt> option on the <tt>fossil settings</tt>
command.

<h3 id="versionable">"Versionable" settings</h3>
<h2 id="versionable">2.0 "Versionable" settings</h2>

Most of the settings control the behaviour of fossil on your local
machine, largely acting to reflect your preference on how you want to
use Fossil, how you communicate with the server, or options for hosting
a repository on the web.

However, for historical reasons, some settings affect how you work with
Added www/signing.md.




























































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
# Signing Check-ins

Fossil can sign check-in manifests. A basic concept in public-key
cryptography, signing can bring some advantages such as authentication and
non-repudiation. In practice, a serious obstacle is the public key
infrastructure – that is, the problem of reliably verifying that a given
public key belongs to its supposed owner (also known as _"signing is easy,
verifying is hard"_).

Fossil neither creates nor verifies signatures by itself, instead relying on
external tools that have to be installed side-by-side. Historically, the tool
most employed for this task was [GnuPG](https://gnupg.org); recently, there has
been an increase in the usage of [OpenSSH](https://openssh.com) (the minimum
required version is 8.1, released on 2019-10-09).

## Signing a check-in

The `clearsign` setting must be on; this will cause every check-in to be signed
(unless you provide the `--nosign` flag to `fossil commit`). To this end,
Fossil calls the command given by the `pgp-command` setting.

Fossil needs a non-detached signature that includes the rest of the usual
manifest. For GnuPG, this is no problem, but as of 2025 (version 9.9p1) OpenSSH
can create **and verify** only detached signatures; Fossil itself must
attach this signature to the manifest prior to committing. This makes the 
verification more complex, as additional steps are needed to extract the
signature and feed it into OpenSSH.

### GnuPG

The `pgp-command` setting defaults to
`gpg --clearsign -o`.
(A possible interesting option to `gpg --clearsign` is `-u`, to specify the
user to be used for signing.)

### OpenSSH

A reasonable value for `pgp-command` is

```
ssh-keygen -q -Y sign -n fossilscm -f ~/.ssh/id_ed25519
```

for Linux, and

```
ssh-keygen -q -Y sign -n fossilscm -f %USERPROFILE%/.ssh/id_ed25519
```

for Windows, changing as appropriate `-f` to the path of the private key to be
used.

The value for `-n` (the _namespace_) can be changed at will, but care has to be
taken to use the same value when verifying the signature.

Fossil versions prior to 2.26 do not understand SSH signatures and
will treat artifacts signed this way as opaque blobs, not Fossil
artifacts.


## Verifying a signature

Fossil does not provide an internal method for verifying signatures and
relies – like it does for signing – on external tools.

### GnuPG

Assuming you used the
default GPG command for signing, one can verify the signature using

```
fossil artifact <CHECK-IN> | gpg --verify
```

### OpenSSH

The user and the key that was used to create the signature must be listed
together in the `ALLOWED_SIGNERS` file (see
[documentation](https://man.openbsd.org/ssh-keygen#ALLOWED_SIGNERS)).
Note that in that file, the "@DOMAIN" bit for the principal is only a
recommendation; you can (or even _should_) simply use your Fossil user name.

As mentioned, for lack of an OpenSSH built-in non-detached signature mechanism,
the burden of extracting the relevant part of the signed check-in is on the
user.

The following recipes are provided only as examples and can be easily extended 
to fully-fledged scripts.

#### For Linux:

```bash
fsig=$(mktemp /tmp/__fsig.XXXXXX) && \
fusr=$(fossil artifact tip \
  | awk -v m="${fsig}" -v s="${fsig}.sig" \
      '/^-----BEGIN SSH SIGNED/{of=m;next} \
       /^-----BEGIN SSH SIGNATURE/{of=s} \
       /^U /{usr=$2} \
       /./{if(!of){exit 42};print >> of} END{print usr}') && \
ssh-keygen -Y verify -f ~/.ssh/allowed_signers -I ${fusr} -n fossilscm \
  -s "${fsig}.sig" < "${fsig}" || echo "No SSH signed check-in" && \
rm -f "${fsig}.sig" "${fsig}" && \
unset -v fsig fusr
```

#### For Windows (cmd):

The following incantation makes use of `awk` and `dos2unix`, standard Unix
tools but requiring separate installation on Windows (for example,using
[BusyBox](https://frippery.org/busybox/#downloads)). The usage of `awk` can be
replaced with the Windows basic tool `findstr`, leading to a longer recipe.

```bat
fossil artifact <CHECK-IN> | awk -v m="__fsig" -v s="__fsig.sig" ^
  "/^-----BEGIN SSH SIGNED/{of=m;next} /^-----BEGIN SSH SIGNATURE/{of=s} /./{if(!of){exit 42};print >> of}"
if %errorlevel% equ 42 (echo No SSH signed check-in)
REM ---Skip remaining lines if no SSH signed message---
for /f "tokens=2" %i in ('findstr /b "U " __fsig') do set fusr=%i
dos2unix __fsig __fsig.sig
ssh-keygen -Y verify -f %USERPROFILE%\.ssh\allowed_signers -I "%fusr%" ^
  -n fossilscm -s __fsig.sig < __fsig
del __fsig __fsig.sig 2>nul & set "fusr="
```

Changes to www/ssl-server.md.
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178



179
180

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
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



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





-
+

-
+








-
+

-
-
-
+
+
+

-
+
-
-
-
-
-
+

-
+


-
+



-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
+

+
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+

+
-
-
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
+
+
+
+

-
-
+
+
+
+

-
-
-
+

-
+

+
-
+

-
-
+
+


-
+




















-
+


+
+
-
-
-
+
+
+







-
-
+
+

+
+
+
+
+
+
+
+
-
+
+

+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
-
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+

-
-
-
-
+
+


-
+


-
-
-
+
+
+
-
-
-
+

+
-
-
+
+
+

-
-
+
+
+
+

+
+
-
+
+
+


-
+

-
-
-
+
+
+
+


















-
-
+
+
+


+
# SSL/TLS Server Mode

## History

Fossil has supported [client-side SSL/TLS][0] since [2010][1].  This means
that commands like "[fossil sync](/help?cmd=sync)" could use SSL/TLS when
that commands like "[fossil sync](/help/sync)" could use SSL/TLS when
contacting a server.  But on the server side, commands like
"[fossil server](/help?cmd=server)" operated in clear-text only.  To implement
"[fossil server](/help/server)" operated in clear-text only.  To implement
an encrypted server, you had to put Fossil behind a web server or reverse
proxy that handled the SSL/TLS decryption/encryption and passed cleartext
down to Fossil.

[0]: ./ssl.wiki
[1]: /timeline?c=b05cb4a0e15d0712&y=ci&n=13

Beginning in [late December 2021](/timeline?c=f6263bb64195b07f&y=a&n=13),
this has been fixed.  Commands like
Fossil servers are now able to converse directly over TLS.  Commands like

  * "[fossil server](/help?cmd=server)"
  * "[fossil ui](/help?cmd=ui)", and
  * "[fossil http](/help?cmd=http)"
  * "[fossil server](/help/server)"
  * "[fossil ui](/help/ui)", and
  * "[fossil http](/help/http)"

now all handle server-mode SSL/TLS encryption natively.  It is now possible
may now handle the encryption natively when suitably configured, without
to run a secure Fossil server without having to put Fossil behind an encrypting
web server or reverse proxy.  Hence, it is now possible to stand up a complete
Fossil project website on an inexpensive VPS with no added software other than
Fossil itself and something like [certbot](https://certbot.eff.org) for
obtaining a CA-signed certificate.
requiring a third-party proxy layer.

## Usage
## <a id="usage"></a>Usage

To put any of the Fossil server commands into SSL/TLS mode, simply
add the "--cert" command-line option.
add the "`--cert`" command-line option:

    fossil ui --cert unsafe-builtin

The --cert option is what tells Fossil to use TLS encryption.
Normally, the argument to --cert is the name of a file containing
the certificate (the "fullchain.pem" file) for the website.  In this
example, the magic name "unsafe-builtin" is used, which causes Fossil
to use a self-signed cert rather than a real cert obtained from a
[Certificate Authority](https://en.wikipedia.org/wiki/Certificate_authority)
or "CA".  As the name implies, this self-signed cert is not secure and
should only be used for testing.  Your web-browser will complain bitterly 
and will refuse to display the pages using the "unsafe-builtin" cert.
Firefox will allow you to click an "I know the risks" button and continue.
Other web browsers will stubornly refuse to display the page, under the theory
that weak encryption is worse than no encryption at all.  Continue reading
to see how to solve this.
Here, we are passing the magic name "unsafe-builtin" to cause Fossil to
use a [hard-coded self-signed cert][hcssc] rather than one obtained from
a recognized [Certificate Authority][CA], or "CA".

As the name implies, this self-signed cert is _not secure_ and should
only be used for testing. Your web browser is likely to complain
bitterly about it and will refuse to display the pages using the
"unsafe-builtin" cert until you placate it. The complexity of the
ceremony demanded depends on how paranoid your browser’s creators have
decided to be. It may require as little as clicking a single big "I know
the risks" type of button, or it may require a sequence be several
clicks designed to discourage the “yes, yes, just let me do the thing”
crowd lest they run themselves into trouble by disregarding well-meant
warnings.

Our purpose here is to show you an alternate path that will avoid the
issue entirely, not weigh in on which browser handles self-signed
certificates best.

[CA]: https://en.wikipedia.org/wiki/Certificate_authority
[hcssc]: /info/c2a7b14c3f541edb96?ln=89-116

## About Certs
## <a id="about"></a>About Certs

The X.509 certificate system used by browsers to secure TLS connections
Certs are based on public-key or asymmetric cryptography.  To create a cert,
you first create a new "key pair" consisting of a public key and a private key.
is based on asymmetric public-key cryptography. The methods for
obtaining one vary widely, with a resulting tradeoff we may summarize as
trustworthiness versus convenience, the latter characteristic falling as
the former rises.(^No strict correlation exists. CAs have invented
highly inconvenient certification schemes that offer little additional
real-world trustworthiness. Extreme cases along this axis may be fairly
characterized as [security theater][st]. We focus in this document on
well-balanced trade-offs between decreasing convenience and useful
levels of trustworthiness gained thereby.)

The public key can be freely shared with the world, but you must keep the
private key secret.  If anyone gains access to your private key then he will be
able to impersonate you and break into your system.
The self-signed method demonstrated above offers approximately zero
trustworthiness, though not zero _value_ since it does still provide
connection encryption.

More trustworthy methods are necessarily less convenient. One such is to
To obtain a cert, you send your public key and the name of the domain you
want to protect to a certificate authority.  The CA then digitally signs
send your public key and the name of the domain you want to protect to a
recognized CA, which then performs one or more tests to convince itself
that the requester is in control of that domain. If the CA’s tests all
pass, it produces an X.509 certificate bound to that domain, which
the combination of those two things using their own private key and sends
the signed combination back to you.  The CA's digital signature of your
public key and domain name is the cert.
includes assorted other information under the CAs digital signature
attesting to the validity of the document’s contents. The result is sent
back to the requester, which may then use it to transitively attest to
these tests’ success: presuming one cannot fake the type of signature
used, the document must have been signed by the trusted, recognized CA.

There is one element of the assorted information included with a
certificate that is neither supplied by the requester nor rubber-stamped
on it in passing by the CA. It also generates a one-time key pair and
stores the public half in the certificate. The cryptosystem this keypair
is intended to work with varies both by the CA and by time, as older
systems become obsolete. Details aside, the CA then puts this matching
private half of the key in a separate file, often encrypted under a
separate cryptosystem for security.

SSL/TLS servers need two things in order to prove their identity to clients:
SSL/TLS servers need both resulting halves to make these attestations,
but they send only the public half to the client when establishing the
connection. The client then makes its own checks to determine whether it
trusts the attestations being made.

  1.  The cert that was signed by a CA
  2.  The private key
A properly written and administered server never releases the private
key to anyone. Ideally, it goes directly from the CA to the requesting
server and never moves from there; then when it expires, the server
deletes it permanently.

The SSL/TLS servers send the cert to each client, so that the client
can verify it.  But the private key is kept strictly private and is never
shared with anyone.
[st]: https://en.wikipedia.org/wiki/Security_theater

## How To Tell Fossil About Your Cert And Private Key
## <a id="startup"></a>How To Tell Fossil About Your Cert And Private Key

As we saw [above](#usage),
If you do not have your own cert and private key, you can ask Fossil
if you do not have your own cert and private key, you can ask Fossil
to use "unsafe-builtin", which is a self-signed cert that is built into
Fossil.  This is wildly insecure, since the private key is not really private - 
it is [in plain sight](/info/c2a7b14c3f541edb96?ln=89-116) in the Fossil
Fossil.  This is wildly insecure, since the private key is not really private;
it is [in plain sight][hcssc] in the Fossil
source tree for anybody to read.  <b>Never add the private key that is
built into Fossil to your OS's trust store</b> as doing so will severely
compromise your computer.  The built-in cert is only useful for testing.
compromise your computer.[^ssattack] This built-in cert is only useful for testing.
If you want actual security, you will need to come up with your own private
key and cert.

Fossil wants to read certs and public keys in the 
[PEM format](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail).
PEM is a pure ASCII text format.  The private key consists of text
like this:

    -----BEGIN PRIVATE KEY-----
    *base-64 encoding of the private key*  
    -----END PRIVATE KEY-----

Similarly, a PEM-encoded cert will look like this:

    -----BEGIN CERTIFICATE-----
    *base-64 encoding of the certificate*  
    -----END CERTIFICATE-----

In both formats, text outside of the delimiters is ignored.  That means
that if you have a PEM-formatted private key and a separate PEM-formatted
certificate, you can concatenate the two into a single file and the
certificate, you can concatenate the two into a single file, and the
individual components will still be easily accessible.

### <a id="cat"></a>Separate or Concatenated?

If you have a single file that holds both your private key and your
cert, you can hand it off to the "[fossil server](/help?cmd=server)"
command using the --cert option.  Like this:
Given a single concatenated file that holds both your private key and your
cert, you can hand it off to the "[fossil server](/help/server)"
command using the `--cert` option, like this:

    fossil server --port 443 --cert mycert.pem /home/www/myproject.fossil

The command above is sufficient to run a fully-encrypted web site for
the "myproject.fossil" Fossil repository.  This command must be run as
root, since it wants to listen on TCP port 443, and only root processes are
allowed to do that.  This is safe, however, since before reading any
information off of the wire, Fossil will put itself inside a chroot jail
at /home/www and drop all root privileges.
information off of the wire, Fossil will [put itself inside a chroot
jail](./chroot.md) at `/home/www` and drop all root privileges.

This method of combining your cert and private key into a single big PEM
file carries risks, one of which is that the system administrator must
make both halves readable by the user running the Fossil server. Given
the chroot jail feature, a more secure scheme separates the halves so
that only root can read the private half, which then means that when
Fossil drops its root privileges, it becomes unable to access the
private key on disk. Fossil’s `server` feature includes the `--pkey`
option to allow for that use case:
### Keeping The Cert And Private Key In Separate Files

    fossil server --port 443 --cert fullchain.pem --pkey privkey.pem /home/www/myproject.fossil

[^ssattack]: ^How, you ask? Because the keys are known, they can be used
    to provide signed certificates for **any** other domain. One foolish
    enough to tell their OS’s TLS mechanisms to trust the signing
    certificate is implicitly handing over all TLS encryption controls
    to any attacker that knows they did this. Don’t do it.
If you do not want to combine your cert and private key into a single
big PEM file, you can keep them separate using the --pkey option to
Fossil.

### <a id="chain"></a>Chains and Links

The file name “`fullchain.pem`” used above is a reference to a term of
art within this world of TLS protocols and their associated X.509
certificates. Within the simplistic scheme originally envisioned by the
creators of SSL — the predecessor to TLS — we were all expected to agree
on a single set of CA root authorities, and we would all agree to get
our certificates from one of them. The real world is more complicated:

*   The closest we have to universal acceptance of CAs is via the
    [CA/Browser Forum][CAB], and even within its select membership there
    is continual argument over which roots are trustworthy. (Hashing
    that out is arguably this group’s key purpose.)

*   CAB’s decision regarding trustworthiness may not match that of any
    given system’s administrator. There are solid, defensible reasons to
    prune back the stock CA root set included with your browser, then to
    augment it with ones CAB _doesn’t_ trust.
    fossil server --port 443 --cert fullchain.pem --pkey privkey.pem /home/www/myproject.fossil

*   TLS isn’t limited to use between web browsers and public Internet
    sites. Several common use cases preclude use of the process CAB
    envisions, with servers able to contact Internet-based CA roots as
    part of proving their identity. Different use cases demand different
    CA root authority stores.

    The most common of these divergent cases are servers behind strict
    firewalls and edge devices that never interact with the public
    Internet. This class ranges from cheap home IoT devices to the
    internal equipment managed by IT for a massive global corporation.

Your private Fossil server is liable to fall into that last category.
This may then require that you generate a more complicated “chain” of
certificates for Fossil to use here, without which the client may not be
able to get back to a CA root it trusts. This is true regardless of
whether that client is another copy of Fossil or a web browser
traversing Fossil’s web UI, though that fact complicates matters by
allowing for multiple classes of client, each of which may have their
own rules for modifying the stock certificate scheme.

This is distressingly common, in fact: Fossil links to OpenSSL to
provide its TLS support, but there is a good chance that your browser
uses another TLS implementation entirely. They may or may not agree on a
single CA root store.

How you accommodate all this complexity varies by the CA and other
details. As but one example, Firefox’s “View Certificate” feature offers
_two_ ways to download a given web site’s certificate: the cert alone or
the “chain” leading back to the root. Depending on the use case, the
standalone certificate might suffice, or you might need some type of
cert chain.  Complicating this is that the last link in the chain may be
left off when it is for a mutually trusted CA root, implicitly
completing the chain.

[CAB]: https://en.wikipedia.org/wiki/CA/Browser_Forum

## The ACME Protocol
## <a id="acme"></a>The ACME Protocol

The [ACME Protocol][2] simplifies all this by automating the process of
proving to a recognized public CA that you are in control of a given
website. Without this proof, no valid CA will issue a cert for that
domain, as that allows fraudulent impersonation.

The [ACME Protocol][2] is used to prove to a CA that you control a
website.  CAs require proof that you control a domain before they
will issue a cert for that domain.  The usual means of dealing
with ACME is to run the separate [certbot](https://certbot.eff.org) tool.
The primary implementation of ACME is [certbot], a product of the Let’s
Encrypt organization.
Here is, in a nutshell, what certbot will do to obtain your cert:

  1.  Certbot sends your "signing request" (the document that contains
  1.  It sends your "signing request" (the document that contains
      your public key and your domain name) to the CA.

  2.  After receiving the signing request, the CA needs to verify that you
      control the domain of the cert.  To do this (or, one common
      way of doing this, at least) the CA sends a secret token back to
  2.  After receiving the signing request, the CA needs to verify that
      you control the domain of the cert. One of several methods certbot has
      for accomplishing this is to create a secret token and place it at
      certbot through a secure backchannel, and instructs certbot to make
      that token accessible on the (unencrypted, ordinary "http:") web site
      for the domain in a particular file under the ".well-known" subdirectory.
      a well-known location, then tell the CA about it over ACME.

  3.  The CA then tries pulling that token, which if successful proves
  3.  Certbot puts the token where the CA requested it, then notifies the
      CA that it is there.
      that the requester is able to create arbitrary data on the server,
      implicitly proving control over that server. This must be done
      over the unencrypted HTTP protocol since TLS isn’t working yet.

  4.  The CA accesses the token to confirm that you do indeed control the
      website.  It then creates the cert and sends it back to certbot.
  4.  If satisfied by this proof of control, the CA then creates the
      keypair described above and bakes the public half into the
      certificate it signs. It then sends this and the private half of
      the key back to certbot.

  5.  Certbot stores these halves separately for the reasons sketched
      out above.
  5.  Certbot stores your cert and deletes the ".well-known" token.

  6.  It then deletes the secret one-time-use token it used to prove
      domain control. ACME’s design precludes replay attacks.

In order for all of this to happen, certbot needs to be able to create
a subdirectory named ".well-known", within a directory you specify, and
a subdirectory named ".well-known", within a directory you specify,
then populate that subdirectory with a token file of some kind.  To support
this, the "[fossil server](/help?cmd=server)" and
"[fossil http](/help?cmd=http)" commands have the --acme option.
When the --acme option is specified and Fossil sees a URL where the path
this, the "[fossil server](/help/server)" and
"[fossil http](/help/http)" commands have the --acme option.

When specified, Fossil sees a URL where the path
begins with ".well-known", then instead of doing its normal processing, it
looks for a file with that pathname and returns it to the client.  If
the "server" or "http" command is referencing a single Fossil repository,
then the ".well-known" sub-directory should be in the same directory as
the repository file.  If the "server" or "http" command are run against
a directory full of Fossil repositories, then the ".well-known" sub-directory
should be in that top-level directory.

Thus, to set up a project website, you should first run Fossil in ordinary
unencrypted HTTP mode like this:

    fossil server --port 80 --acme /home/www/myproject.fossil

Then you create your public/private key pair and run certbot, giving it
a --webroot of /home/www.  Certbot will create the sub-directory
named "/home/www/.well-known" and put token files there, which the CA
will verify.  Then certbot will store your new cert in a particular file.

Once certbot has obtained your cert, then you can concatenate that
cert with your private key and run Fossil in SSL/TLS mode as shown above.
Once certbot has obtained your cert,  you may either pass the two halves
to Fossil separately using the `--pkey` and `--cert` options described
above, or you may concatenate them and pass that via `--cert` alone.

[2]: https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment
[certbot]: https://certbot.eff.org
Changes to www/sync.wiki.
22
23
24
25
26
27
28
29

30
31
32
33


34
35
36
37
38
39
40
22
23
24
25
26
27
28

29
30
31


32
33
34
35
36
37
38
39
40







-
+


-
-
+
+







employed that usually reduce the number of hashes that need to be
shared to a few dozen.

Each repository also has local state.  The local state determines
the web-page formatting preferences, authorized users, ticket formats,
and similar information that varies from one repository to another.
The local state is not usually transferred during a sync.  Except,
some local state is transferred during a [/help?cmd=clone|clone]
some local state is transferred during a [/help/clone|clone]
in order to initialize the local state of the new repository.  Also,
an administrator can sync local state using
the [/help?cmd=configuration|config push] and
[/help?cmd=configuration|config pull]
the [/help/configuration|config push] and
[/help/configuration|config pull]
commands.

<h3 id="crdt">1.1 Conflict-Free Replicated Datatypes</h3>

The "bag of artifacts" data model used by Fossil is apparently an
implementation of a particular
[https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type|Conflict-Free
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
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







-
+

-
+








-
-
+
+








All communication between client and server is via HTTP requests.
The server is listening for incoming HTTP requests.  The client
issues one or more HTTP requests and receives replies for each
request.

The server might be running as an independent server
using the [/help?cmd=server|"fossil server" command], or it
using the [/help/server|"fossil server" command], or it
might be launched from inetd or xinetd using the
["fossil http" command|/help?cmd=http].  Or the server might
[/help/http|"fossil http" command].  Or the server might
be [./server/any/cgi.md|launched from CGI] or from
[./server/any/scgi.md|SCGI].
(See "[./server/|How To Configure A Fossil Server]" for details.)
The specifics of how the server listens
for incoming HTTP requests is immaterial to this protocol.
The important point is that the server is listening for requests and
the client is the issuer of the requests.

A single [/help?cmd=push|push], 
[/help?cmd=pull|pull], or [/help?cmd=sync|sync]
A single [/help/push|push],
[/help/pull|pull], or [/help?cmd=sync|sync]
might involve multiple HTTP requests.
The client maintains state between all requests.  But on the server
side, each request is independent.  The server does not preserve
any information about the client from one request to the next.

Note: Throughout this article, we use the terms "server" and "client"
to represent the listener and initiator of the interaction, respectively.
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
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







-
+




-
+







-
+







typing in the common case where the client does multiple syncs to
the same server.)

The client modifies the URL by appending the method name "<b>/xfer</b>"
to the end.  For example, if the URL specified on the client command
line is

<pre>https://fossil-scm.org/fossil</pre>
<pre>https://fossil-scm.org/home</pre>

Then the URL that is really used to do the synchronization will
be:

<pre>https://fossil-scm.org/fossil/xfer</pre>
<pre>https://fossil-scm.org/home/xfer</pre>

<h3 id="req-format">2.2 HTTP Request Format</h3>

The client always sends a POST request to the server.  The
general format of the POST request is as follows:

<pre>
POST /fossil/xfer HTTP/1.0
POST /home/xfer HTTP/1.0
Host: fossil-scm.hwaci.com:80
Content-Type: application/x-fossil
Content-Length: 4216
</pre>

<i><pre>content...</pre></i>

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
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







-
-
-
-
-
+
+
+
+
+
+

-
-
-
+
+
+
-
-
-
+
+

+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+














-
+







<h3 id="login">3.2 Login Cards</h3>

Every message from client to server begins with one or more login
cards.  Each login card has the following format:

<pre><b>login</b>  <i>userid  nonce  signature</i></pre>

The userid is the name of the user that is requesting service
from the server.  The nonce is the SHA1 hash of the remainder of
the message - all text that follows the newline character that
terminates the login card.  The signature is the SHA1 hash of
the concatenation of the nonce and the users password.
The userid is the name of the user that is requesting service from the
server, encoded in "fossilized" form (exactly as described for <a
href="#error">the error card</a>).  The nonce is the SHA1 hash of the
remainder of the message - all text that follows the newline character
that terminates the login card.  The signature is the SHA1 hash of the
concatenation of the nonce and the users password.

For each login card, the server looks up the user and verifies
that the nonce matches the SHA1 hash of the remainder of the
message.  It then checks the signature hash to make sure the
When receving a login card, the server looks up the user and verifies
that the nonce matches the SHA1 hash of the remainder of the message.
It then checks the signature hash to make sure the signature matches.
signature matches.  If everything
checks out, then the client is granted all privileges of the
specified user.
If everything checks out, then the client is granted all privileges of
the specified user.

Only one login card is permitted. A second login card will trigger
a sync error. (Prior to 2025-07-21, the protocol permitted multiple
logins, treating the login as the union of all privileges from all
login cards. That capability was never used and has been removed.)
Privileges are cumulative.  There can be multiple successful
login cards.  The session privilege is the union of all
privileges from all login cards.

As of version 2.27, Fossil supports transfering of the login card
externally to the request payload via a Cookie HTTP header:

<verbatim>
  Cookie: x-f-x-l=...
</verbatim>

Where "..." is the URL-encoded login cookie. <code>x-f-x-l</code> is
short for X-Fossil-Xfer-Login.


<h3 id="file">3.3 File Cards</h3>

Artifacts are transferred using either "file" cards, or "cfile"
or "uvfile" cards.
The name "file" card comes from the fact that most artifacts correspond to
files that are under version control.
The "cfile" name is an abbreviation for "compressed file".
The "uvfile" name is an abbreviation for "unversioned file".

<h4 id="ordinary-fc">3.3.1 Ordinary File Cards</h4>

For sync protocols, artifacts are transferred using "file"
cards.  File cards come in two different formats depending
on whether the artifact is sent directly or as a 
on whether the artifact is sent directly or as a
[./delta_format.wiki|delta] from some
other artifact.

<pre>
<b>file</b> <i>artifact-id size</i> <b>\n</b> <i>content</i>
<b>file</b> <i>artifact-id delta-artifact-id size</i> <b>\n</b> <i>content</i>
</pre>
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
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







-
+












-
-
+
+
+
+
+
+







The first argument of a file card is the ID of the artifact that
is being transferred.  The artifact ID is the lower-case hexadecimal
representation of the name hash for the artifact.
The last argument of the file card is the number of bytes of
payload that immediately follow the file card.  If the file
card has only two arguments, that means the payload is the
complete content of the artifact.  If the file card has three
arguments, then the payload is a 
arguments, then the payload is a
[./delta_format.wiki|delta] and the second argument is
the ID of another artifact that is the source of the delta.

File cards are sent in both directions: client to server and
server to client.  A delta might be sent before the source of
the delta, so both client and server should remember deltas
and be able to apply them when their source arrives.

<h4 id="compressed-fc">3.3.2 Compressed File Cards</h4>

A client that sends a clone protocol version "3" or greater will
receive artifacts as "cfile" cards while cloning.  This card was
introduced to improve the speed of the transfer of content by sending the
compressed artifact directly from the server database to the client.
introduced to improve the speed of the transfer of content by sending
the compressed artifact directly from the server database to the
client.  In this case, the containing response body is <em>not</em>
compressed separately because the vast majority of the response is
already compressed in cfile cards. In practice, version "3" is
significantly faster than version "2".

Compressed File cards are similar to File cards, sharing the same
in-line "payload" data characteristics and also the same treatment of
direct content or delta content.  Cfile cards come in two different formats
depending on whether the artifact is sent directly or as a delta from
some other artifact.

409
410
411
412
413
414
415
416

417
418
419
420
421
422
423
425
426
427
428
429
430
431

432
433
434
435
436
437
438
439







-
+







operations.  Instead of sending "file" cards, the server will send "cfile"
cards

<h4>3.5.2 Protocol 2</h4>

The sequence-number sent is the number
of artifacts received so far.  For the first clone message, the
sequence number is 0.  The server will respond by sending file
sequence number is 1.  The server will respond by sending file
cards for some number of artifacts up to the maximum message size.

The server will also send a single "clone_seqno" card to the client
so that the client can know where the server left off.

<pre>
<b>clone_seqno</b>  <i>sequence-number</i>
787
788
789
790
791
792
793








794
795
796
797
798
799
800
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824







+
+
+
+
+
+
+
+








<li><b>ci-unlock</b> <i>CLIENT-ID</i> A client sends the "ci-unlock" pragma to the server after
a successful commit.  This instructs the server to release
any lock on any check-in previously held by that client.
The ci-unlock pragma helps to avoid false-positive lock warnings
that might arise if a check-in is aborted and then restarted
on a branch.

<li><b>req-clusters</b> A client sends the "req-clusters" pragma
to the server to ask the server to reply with "igot" cards for
every [./fileformat.wiki#cluster|cluster artifact] that it holds.  The
client typically does this when it thinks that it might be attempting
to pull a long chain of cluster artifacts.  Sending the artifacts
all at once can dramatically reduce the number of round trip
messages needed to complete the synchronization.
</ol>

<h3 id="comment">3.12 Comment Cards</h3>

Any card that begins with "#" (ASCII 0x23) is a comment card and
is silently ignored.

1028
1029
1030
1031
1032
1033
1034
1035

1036
1037
1038
1039
1040
1041
1042
1052
1053
1054
1055
1056
1057
1058

1059
1060
1061
1062
1063
1064
1065
1066







-
+







    card per line.
<li>Card formats are:
    <ul>
    <li> <b>login</b> <i>userid nonce signature</i>
    <li> <b>push</b> <i>servercode projectcode</i>
    <li> <b>pull</b> <i>servercode projectcode</i>
    <li> <b>clone</b>
    <li> <b>clone_seqno</b> <i>sequence-number</i>
    <li> <b>clone</b> <i>protocol-version sequence-number</i>
    <li> <b>file</b> <i>artifact-id size</i> <b>\n</b> <i>content</i>
    <li> <b>file</b> <i>artifact-id delta-artifact-id size</i> <b>\n</b> <i>content</i>
    <li> <b>cfile</b> <i>artifact-id size</i> <b>\n</b> <i>content</i>
    <li> <b>cfile</b> <i>artifact-id delta-artifact-id size</i> <b>\n</b> <i>content</i>
    <li> <b>uvfile</b> <i>name mtime hash size flags</i> <b>\n</b> <i>content</i>
    <li> <b>private</b>
    <li> <b>igot</b> <i>artifact-id</i> ?<i>flag</i>?
Changes to www/tech_overview.wiki.
160
161
162
163
164
165
166
167

168
169
170
171
172
173
174
160
161
162
163
164
165
166

167
168
169
170
171
172
173
174







-
+








The second case is the one that usually determines the name. Note that the
FOSSIL_HOME environment variable can always be set to determine the 
location of the configuration database.  Note also that the configuration
database file itself is called ".fossil" or "fossil.db" on unix but
"_fossil" on windows.

The [/help?cmd=info|fossil info] command will show the location of
The [/help/info|fossil info] command will show the location of
the configuration database on a line that starts with "config-db:".

<h3>2.2 Repository Databases</h3>

The repository database is the file that is commonly referred to as
"the repository".  This is because the repository database contains,
among other things, the complete revision, ticket, and wiki history for
Changes to www/th1.md.
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
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







-
+
-






-
+
+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+






-
+







TH1 began as a minimalist re-implementation of the Tcl scripting language.
There was a need to test the SQLite library on Symbian phones, but at that
time all of the test cases for SQLite were written in Tcl and Tcl could not
be easily compiled on the SymbianOS.  So TH1 was developed as a cut-down
version of Tcl that would facilitate running the SQLite test scripts on
SymbianOS.

Fossil was first being designed at about the same time that TH1 was
Fossil was first being designed at about the same time.
being developed for testing SQLite on SymbianOS.
Early prototypes of Fossil were written in pure Tcl.  But as the development
shifted toward the use of C-code, the need arose to have a Tcl-like
scripting language to help with code generation.  TH1 was small and
light-weight and used minimal resources and seemed ideally suited for the
task.

The name "TH1" stands "Test Harness 1", since that was its original purpose.
The name "TH1" stands for "Test Harness 1",
since its original purpose was to serve as testing harness
for SQLite.

Where TH1 Is Used In Fossil
---------------------------

  *  In the header and footer for [skins](./customskin.md)
     text within `<th1>...</th1>` is run as a TH1 script.
     ([example](/builtin/skins/default/header.txt))

  *  This display of [tickets](./bugtheory.wiki) is controlled by TH1
     scripts, so that the ticket format can be customized for each
     project.  Administrators can visit the <b>/tktsetup</b> page in
     their repositories to view and customize these scripts.
     ([example usage](./custom_ticket.wiki))

Overview
--------
Overview Of The Tcl/TH1 Language
--------------------------------

TH1 is a string-processing language.  All values are strings.  Any numerical
operations are accomplished by converting from string to numeric, performing
the computation, then converting the result back into a string.  (This might
seem inefficient, but it is faster than people imagine, and numeric
computations do not come up very often for the kinds of work that TH1 does,
so it has never been a factor.)
so it has never been an issue.)

A TH1 script consists of a sequence of commands.
Each command is terminated by the first *unescaped* newline or ";" character.
The text of the command (excluding the newline or semicolon terminator)
is broken into space-separated tokens.  The first token is the command
name and subsequent tokens are the arguments.  In this sense, TH1 syntax
is similar to the familiar command-line shell syntax.
66
67
68
69
70
71
72
73

74
75
76
77
78
79
80
80
81
82
83
84
85
86

87
88
89
90
91
92
93
94







-
+







of the command, is `if`.
The second token is `$current eq "dev"` - an expression.  (The outer {...}
are removed from each token by the command parser.)  The third token
is the `puts "hello"`, with its whitespace and newlines.  The fourth token
is `else` and the fifth and last token is `puts "world"`.

The `if` command evaluates its first argument (the second token)
as an expression, and if that expression is true, evaluates its
as an expression, and if that expression is true, it evaluates its
second argument (the third token) as a TH1 script.
If the expression is false and the third argument is `else`, then
the fourth argument is evaluated as a TH1 expression.

So, you see, even though the example above spans five lines, it is really
just a single command.

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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    return [lindex [regexp -line -inline -nocase -- \
        {^uuid:\s+([0-9A-F]{40}) } [eval [getFossilCommand \
        $repository "" info trunk]]] end]

Those backslashes allow the command to wrap nicely within a standard
terminal width while telling the interpreter to consider those three
lines as a single command.

<a id="taint"></a>Tainted And Untainted Strings
-----------------------------------------------

Beginning with Fossil version 2.26 (circa 2025), TH1 distinguishes between
"tainted" and "untainted" strings.  Tainted strings are strings that are
derived from user inputs that might contain text that is designed to subvert
the script.  Untainted strings are known to come from secure sources and
are assumed to contain no malicious content.

Beginning with Fossil version 2.26, and depending on the value of the
[vuln-report setting](/help/vuln-report), TH1 will prevent tainted
strings from being used in ways that might lead to XSS or SQL-injection
attacks.  This feature helps to ensure that XSS and SQL-injection
vulnerabilities are not *accidentally* added to Fossil when
custom TH1 scripts for headers or footers or tickets are added to a
repository.  Note that the tainted/untainted distinction in strings does
not make it impossible to introduce XSS and SQL-injections vulnerabilities
using poorly-written TH1 scripts; it just makes it more difficult and
less likely to happen by accident.  Developers must still consider the
security implications of TH1 customizations they add to Fossil, and take
appropriate precautions when writing custom TH1.  Peer review of TH1
script changes is encouraged.

In Fossil version 2.26, if the vuln-report setting is set to "block"
or "fatal", the [html](#html) and [query](#query) TH1 commands will
fail with an error if their argument is a tainted string.  This helps
to prevent XSS and SQL-injection attacks, respectively.  Note that
the default value of the vuln-report setting is "log", which allows those
commands to continue working and only writes a warning message into the
error log.  <b>Future versions of Fossil may change the default value
of the vuln-report setting to "block" or "fatal".</b>  Fossil users
with customized TH1 scripts are encouraged to audit their customizations
and fix any potential vulnerabilities soon, so as to avoid breakage
caused by future upgrades.  <b>Future versions of Fossil might also
place additional restrictions on the use of tainted strings.</b>
For example, it is likely that future versions of Fossil will disallow
using tainted strings as script, for example as the body of a "for"
loop or of a "proc".


Summary of Core TH1 Commands
----------------------------

The original Tcl language (after which TH1 is modeled) has a very rich
repertoire of commands.  TH1, as it is designed to be minimalist and
145
146
147
148
149
150
151



152
153
154
155
156
157
158
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214







+
+
+







  *  string index STRING INDEX
  *  string is CLASS STRING
  *  string last NEEDLE HAYSTACK ?START-INDEX?
  *  string match PATTERN STRING
  *  string length STRING
  *  string range STRING FIRST LAST
  *  string repeat STRING COUNT
  *  string trim STRING
  *  string trimleft STRING
  *  string trimright STRING
  *  unset VARNAME
  *  uplevel ?LEVEL? SCRIPT
  *  upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR?

All of the above commands work as in the original Tcl.  Refer to the
<a href="https://www.tcl-lang.org/man/tcl/contents.htm">Tcl documentation</a>
for details.
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
236
237
238
239
240
241
242

243
244
245
246
247
248
249







-







  *  [checkout](#checkout)
  *  [combobox](#combobox)
  *  [copybtn](#copybtn)
  *  [date](#date)
  *  [decorate](#decorate)
  *  [defHeader](#defHeader)
  *  [dir](#dir)
  *  [enable\_htmlify](#enable_htmlify)
  *  [enable\_output](#enable_output)
  *  [encode64](#encode64)
  *  [getParameter](#getParameter)
  *  [glob\_match](#glob_match)
  *  [globalState](#globalState)
  *  [hascap](#hascap)
  *  [hasfeature](#hasfeature)
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
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







+







+






+







  *  [setParameter](#setParameter)
  *  [setting](#setting)
  *  [stime](#stime)
  *  [styleHeader](#styleHeader)
  *  [styleFooter](#styleFooter)
  *  [styleScript](#styleScript)
  *  [submenu](#submenu)
  *  [taint](#taintCmd)
  *  [tclEval](#tclEval)
  *  [tclExpr](#tclExpr)
  *  [tclInvoke](#tclInvoke)
  *  [tclIsSafe](#tclIsSafe)
  *  [tclMakeSafe](#tclMakeSafe)
  *  [tclReady](#tclReady)
  *  [trace](#trace)
  *  [untaint](#untaintCmd)
  *  [unversioned content](#unversioned_content)
  *  [unversioned list](#unversioned_list)
  *  [utime](#utime)
  *  [verifyCsrf](#verifyCsrf)
  *  [verifyLogin](#verifyLogin)
  *  [wiki](#wiki)
  *  [wiki_assoc](#wiki_assoc)

Each of the commands above is documented by a block comment above their
implementation in the th\_main.c or th\_tcl.c source files.

All commands starting with "tcl", with the exception of "tclReady",
require the Tcl integration subsystem be included at compile-time.
Additionally, the "tcl" repository setting must be enabled at runtime
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
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







-
+












-
+









<a id="bireqjs"></a>TH1 builtin_request_js Command
--------------------------------------------------

  *  builtin_request_js NAME

NAME must be the name of one of the 
NAME must be the name of one of the
[built-in javascript source files](/dir?ci=trunk&type=flat&name=src&re=js$).
This command causes that javascript file to be appended to the delivered
document.



<a id="capexpr"></a>TH1 capexpr Command
-----------------------------------------------------

  *  capexpr CAPABILITY-EXPR

The capability expression is a list. Each term of the list is a
cluster of [capability letters](./caps/ref.html). 
cluster of [capability letters](./caps/ref.html).
The overall expression is true if any
one term is true. A single term is true if all letters within that
term are true. Or, if the term begins with "!", then the term is true
if none of the terms are true. Or, if the term begins with "@" then
the term is true if all of the capability letters in that term are
available to the "anonymous" user. Or, if the term is "*" then it is
always true.
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
468
469
470
471
472
473
474


















475
476
477
478
479
480
481







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-








Returns a list containing all files in CHECKIN. If GLOB is given only
the files matching the pattern GLOB within CHECKIN will be returned.
If DETAILS is non-zero, the result will be a list-of-lists, with each
element containing at least three elements: the file name, the file
size (in bytes), and the file last modification time (relative to the
time zone configured for the repository).

<a id="enable_htmlify"></a>TH1 enable\_htmlify Command
------------------------------------------------------

  *  enable\_htmlify
  *  enable\_htmlify ?TRACE-LABEL? BOOLEAN

By default, certain output from `puts` and similar commands is escaped
for HTML. The first call form returns the current state of that
feature: `1` for on and `0` for off. The second call form enables
(non-0) or disables (0) that feature and returns the *pre-call* state
of that feature (so that a second call can pass that value to restore
it to its previous state). The optional `TRACE-LABEL` argument causes
the TH1 tracing output (if enabled) to add a marker when the second
form of this command is invoked, and includes that label and the
boolean argument's value in the trace. If tracing is disabled, that
argument has no effect.


<a id="enable_output"></a>TH1 enable\_output Command
------------------------------------------------------

  *  enable\_output BOOLEAN

Enable or disable sending output when the combobox, copybtn, puts, or wiki
524
525
526
527
528
529
530
531















532
533
534
535
536
537
538
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







-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







raise a script error.

<a id="html"></a>TH1 html Command
-----------------------------------

  *  html STRING

Outputs the STRING escaped for HTML.
Outputs the STRING literally.  It is assumed that STRING contains
valid HTML, or that if STRING contains any characters that are
significant to HTML (such as `<`, `>`, `'`, or `&`) have already
been escaped, perhaps by the [htmlize](#htmlize) command.  Use the
[puts](#puts) command to output text that might contain unescaped
HTML markup.

**Beware of XSS attacks!**  If the STRING value to the html command
can be controlled by a hostile user, then he might be able to sneak
in malicious HTML or Javascript which could result in a
cross-site scripting (XSS) attack.  Be careful that all text that
in STRING that might come from user input has been sanitized by the
[htmlize](#htmlize) command or similar.  In recent versions of Fossil,
the STRING value must be [untainted](#taint) or else the "html" command 
will fail.

<a id="htmlize"></a>TH1 htmlize Command
-----------------------------------------

  *  htmlize STRING

Escape all characters of STRING which have special meaning in HTML.
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
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







-
-
+
+
+
+
+
+











-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







Returns the value of the cryptographic nonce for the request being processed.

<a id="puts"></a>TH1 puts Command
-----------------------------------

  *  puts STRING

Outputs the STRING unchanged, where "unchanged" might, depending on
the context, mean "with some characters escaped for HTML."
Outputs STRING.  Characters within STRING that have special meaning
in HTML are escaped prior to being output.  Thus is it safe for STRING
to be derived from user inputs.  See also the [html](#html) command
which behaves similarly except does not escape HTML markup.  This
command ("puts") is safe to use on [tainted strings](#taint), but the "html"
command is not.

<a id="query"></a>TH1 query Command
-------------------------------------

  *  query ?-nocomplain? SQL CODE

Runs the SQL query given by the SQL argument.  For each row in the result
set, run CODE.

In SQL, parameters such as $var are filled in using the value of variable
"var".  Result values are stored in variables with the column name prior
to each invocation of CODE.
to each invocation of CODE.  The names of the variables in which results
are stored can be controlled using "AS name" clauses in the SQL.  As
the database will often contain content that originates from untrusted
users, all result values are marked as [tainted](#taint).

**Beware of SQL injections in the `query` command!**
The SQL argument to the query command should always be literal SQL
text enclosed in {...}.  The SQL argument should never be a double-quoted
string or the value of a \$variable, as those constructs can lead to
an SQL Injection attack.  If you need to include the values of one or
more TH1 variables as part of the SQL, then put \$variable inside the
{...}.  The \$variable keyword will then get passed down into the SQLite
parser which knows to look up the value of \$variable in the TH1 symbol
table.  For example:

~~~
   query {SELECT res FROM tab1 WHERE key=$mykey} {...}
~~~

SQLite will see the \$mykey token in the SQL and will know to resolve it
to the value of the "mykey" TH1 variable, safely and without the possibility
of SQL injection.  The following is unsafe:

~~~
   query "SELECT res FROM tab1 WHERE key='$mykey'" {...}  ;# <-- UNSAFE!
~~~

In this second example, TH1 does the expansion of `$mykey` prior to passing
the text down into SQLite.  So if `$mykey` contains a single-quote character,
followed by additional hostile text, that will result in an SQL injection.

To help guard against SQL-injections, recent versions of Fossil require
that the SQL argument be [untainted](#taint) or else the "query" command
will fail.

<a id="randhex"></a>TH1 randhex Command
-----------------------------------------

  *  randhex N

Returns a string of N*2 random hexadecimal digits with N<50.  If N is
635
636
637
638
639
640
641


642
643
644
645
646
647
648
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741







+
+







---------------------------------------

  *  regexp ?-nocase? ?--? exp string

Checks the string against the specified regular expression and returns
non-zero if it matches.  If the regular expression is invalid or cannot
be compiled, an error will be generated.

See [fossil grep](./grep.md) for details on the regexp syntax.

<a id="reinitialize"></a>TH1 reinitialize Command
---------------------------------------------------

  *  reinitialize ?FLAGS?

Reinitializes the TH1 interpreter using the specified flags.
738
739
740
741
742
743
744














745
746
747
748
749
750
751
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+







<a id="submenu"></a>TH1 submenu Command
-----------------------------------------

  *  submenu link LABEL URL

Add hyperlink to the submenu of the current page.

<a id="taintCmd"></a>TH1 taint Command
-----------------------------------------

  *  taint STRING

This command returns a copy of STRING that has been marked as
[tainted](#taint).  Tainted strings are strings which might be
controlled by an attacker and might contain hostile inputs and
are thus unsafe to use in certain contexts.  For example, tainted
strings should not be output as part of a webpage as they might
contain rogue HTML or Javascript that could lead to an XSS
vulnerability.  Similarly, tainted strings should not be run as
SQL since they might contain an SQL-injection vulerability.

<a id="tclEval"></a>TH1 tclEval Command
-----------------------------------------

**This command requires the Tcl integration feature.**

  *  tclEval arg ?arg ...?

809
810
811
812
813
814
815












816
817
818
819
820
821
822
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







+
+
+
+
+
+
+
+
+
+
+
+







<a id="trace"></a>TH1 trace Command
-------------------------------------

  *  trace STRING

Generates a TH1 trace message if TH1 tracing is enabled.

<a id="untaintCmd"></a>TH1 taint Command
-----------------------------------------

  *  untaint STRING

This command returns a copy of STRING that has been marked as
[untainted](#taint).  Untainted strings are strings which are
believed to be free of potentially hostile content.  Use this
command with caution, as it overwrites the tainted-string protection
mechanisms that are built into TH1.  If you do not understand all
the implications of executing this command, then do not use it.

<a id="unversioned_content"></a>TH1 unversioned content Command
-----------------------------------------------------------------

  *  unversioned content FILENAME

Attempts to locate the specified unversioned file and return its contents.
An error is generated if the repository is not open or the unversioned file
859
860
861
862
863
864
865









866
867
868
869
870
871
872
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000







+
+
+
+
+
+
+
+
+








<a id="wiki"></a>TH1 wiki Command
-----------------------------------

  *  wiki STRING

Renders STRING as wiki content.

<a id="wiki_assoc"></a>TH1 wiki_assoc Command
-----------------------------------

  *  wiki_assoc STRING STRING

Renders the special wiki. The first string refers to the namespace
(checkin, branch, tag, ticket). The second string specifies the
concrete wiki page to be rendered.

Tcl Integration Commands
------------------------

When the Tcl integration subsystem is enabled, several commands are added
to the Tcl interpreter.  They are used to allow Tcl scripts access to the
Fossil functionality provided via TH1.  The following is a summary of the
Added www/title-test.md.












1
2
3
4
5
6
7
8
9
10
11
12
+
+
+
+
+
+
+
+
+
+
+
+
# Markdown Doc Title > & " ' < Test

Test of unusual characters in the title of Markdown formatted documents.
The title should read:

>  Markdown Doc Title > & " ' < Test

See also:

  *  [](/doc/trunk/www/title-test.wiki)
  *  [](/wiki?name=Test+Wiki+>+%26+%22+%27+%3c+Title&p)
  *  [](/forumpost/481ab1f9)
Added www/title-test.wiki.














1
2
3
4
5
6
7
8
9
10
11
12
13
14
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<title>Wiki Doc Title > & " ' < Test</title>

Test of unusual characters in the title of Fossil-wiki formatted documents.
The title should read:

<big><b><verbatim>
   Wiki Doc Title > & " ' < Test
</verbatim></b></big>

See also:

  *  [/doc/trunk/www/title-test.md]
  *  [/wiki?name=Test+Wiki+>+%26+%22+%27+%3c+Title&p]
  *  [/forumpost/481ab1f9]
Changes to www/unvers.wiki.
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
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







-
-
+
+


-
+












-
+


-
+



-
+







Unversioned files are intended to be accessible as web pages using
URLs of the form: "<tt>https://example.com/cgi-script/<b>uv</b>/<i>FILENAME</i></tt>".
In other words, the URI method "<b>uv</b>" (short for "unversioned")
followed by the name of the unversioned file will retrieve the content
of the file.  The MIME type is inferred from the filename suffix.

The content of unversioned files can also be retrieved using the
[/help?cmd=unversioned|fossil unvers cat <i>FILENAME...</i>]
or [/help?cmd=unversioned|fossil unvers export <i>FILENAME</i>] commands.
[/help/unversioned|fossil unvers cat <i>FILENAME...</i>]
or [/help/unversioned|fossil unvers export <i>FILENAME</i>] commands.

A list of all unversioned files on a server can be seen using
the [/help?cmd=/uvlist|/uvlist] URL.  ([/uvlist|example].)
the [/help/www/uvlist|/uvlist] URL.  ([/uvlist|example].)

<h2>Syncing Unversioned Files</h2>

Unversioned content does not sync between repositories by default.
One must request it via commands such as:

<pre>
fossil sync <b>-u</b>
fossil clone <b>-u</b> <i>URL local-repo-name</i>
fossil unversioned sync
</pre>

The [/help?cmd=sync|fossil sync] and [/help?cmd=clone|fossil clone]
The [/help/sync|fossil sync] and [/help/clone|fossil clone]
commands will synchronize unversioned content if and only if they're
given the "-u" (or "--unversioned") command-line option.  The
[/help?cmd=unversioned|fossil unversioned sync] command synchronizes the
[/help/unversioned|fossil unversioned sync] command synchronizes the
unversioned content without synchronizing anything else.

Notice that the "-u" option does not work on
[/help?cmd=push|fossil push] or [/help?cmd=pull|fossil pull].
[/help/push|fossil push] or [/help?cmd=pull|fossil pull].
The "-u" option is only available on "sync" and "clone".
A rough equivalent of an unversioned pull would be the
[/help?cmd=unversioned|fossil unversioned revert] command.  The
"unversioned revert"
command causes the unversioned content on the local repository to be
overwritten by the unversioned content found on the remote repository.