Fossil SCM

Merge trunk.

danield 2025-01-04 23:28 ssh-signing merge
Commit 02cdfa5e0866368cbb2d5bf881b544fbcf924c6ad76d20f42c91f62ae266eb7b
86 files changed +1 -1 +6 -6 +1 -1 +448 -156 +1286 -709 +54 -8 +22 -17 +4 -1 +7 -6 +2 -3 +23 -6 +8 -1 +38 -27 +49 -1 +2 -2 +1 -1 +77 -16 +3 -4 +110 -40 +10 -4 +50 -48 +49 -39 +145 -6 +6 -2 +97 -21 +26 -1 +8 -1 +18 +1 -1 +94 -8 +94 -8 +60 -35 +80 -26 +3 -1 +1 -1 +28 -12 +6 +4 -1 +466 -36 +2 -1 +13 -1 +24 -10 +14 -1 +386 +477 -14 +581 +804 -168 +151 -24 +50 -17 +1 +14 -1 +9 -3 +7 -7 +12 -1 +7 -5 +5 -40 +4 -4 +2 +474 -462 +1 -1 +71 -13 +4 -4 +68 +55 -2 +25 +28 -25 +6 -4 +2 +1 -1 +3 -1 +1 -1 +16 -5 +10 -4 +13 +12 +2 -2 +1 -1 +51 -1 +1 -1 +1 -1 +2 -2 +1 -1 +4 -4 +1 -1 +197 -88 +8
+1 -1
--- BUILD.txt
+++ BUILD.txt
@@ -42,11 +42,11 @@
4242
mkdir build
4343
cd build
4444
../configure
4545
make
4646
47
-This will now keep all generates files separate from the maintained
47
+This will now keep all generated files separate from the maintained
4848
source code.
4949
5050
--------------------------------------------------------------------------
5151
5252
Here are some notes on what is happening behind the scenes:
5353
--- BUILD.txt
+++ BUILD.txt
@@ -42,11 +42,11 @@
42 mkdir build
43 cd build
44 ../configure
45 make
46
47 This will now keep all generates files separate from the maintained
48 source code.
49
50 --------------------------------------------------------------------------
51
52 Here are some notes on what is happening behind the scenes:
53
--- BUILD.txt
+++ BUILD.txt
@@ -42,11 +42,11 @@
42 mkdir build
43 cd build
44 ../configure
45 make
46
47 This will now keep all generated files separate from the maintained
48 source code.
49
50 --------------------------------------------------------------------------
51
52 Here are some notes on what is happening behind the scenes:
53
+6 -6
--- Dockerfile
+++ Dockerfile
@@ -3,19 +3,19 @@
33
44
## ---------------------------------------------------------------------
55
## STAGE 1: Build static Fossil binary
66
## ---------------------------------------------------------------------
77
8
-### We aren't pinning to a more stable version of Alpine because we want
8
+### We don't pin a more stable version of our base layer because we want
99
### to build with the latest tools and libraries available in case they
1010
### fixed something that matters to us since the last build. Everything
1111
### below depends on this layer, and so, alas, we toss this container's
1212
### cache on Alpine's release schedule, roughly once a month.
1313
FROM alpine:latest AS bld
1414
WORKDIR /fsl
1515
16
-### Bake the basic Alpine Linux into a base layer so it only changes
16
+### Bake the build-time userland into a base layer so it only changes
1717
### when the upstream image is updated or we change the package set.
1818
RUN set -x \
1919
&& apk update \
2020
&& apk upgrade --no-cache \
2121
&& apk add --no-cache \
@@ -23,19 +23,19 @@
2323
linux-headers musl-dev \
2424
openssl-dev openssl-libs-static \
2525
zlib-dev zlib-static
2626
2727
### Build Fossil as a separate layer so we don't have to rebuild the
28
-### Alpine environment for each iteration of Fossil's dev cycle.
28
+### userland for each iteration of Fossil's dev cycle.
2929
###
3030
### We must cope with a bizarre ADD misfeature here: it unpacks tarballs
3131
### automatically when you give it a local file name but not if you give
3232
### it a /tarball URL! It matters because we default to a URL in case
3333
### you're building outside a Fossil checkout, but when building via the
34
-### container-image target, we avoid a costly hit on fossil-scm.org
35
-### by leveraging its DVCS nature via the "tarball" command and passing
36
-### the resulting file's name in.
34
+### container-image target, we avoid a costly hit on fossil-scm.org by
35
+### leveraging its DVCS nature via the "tarball" command and passing the
36
+### resulting file's name in.
3737
ARG FSLCFG=""
3838
ARG FSLVER="trunk"
3939
ARG FSLURL="https://fossil-scm.org/home/tarball/src?r=${FSLVER}"
4040
ENV FSLSTB=/fsl/src.tar.gz
4141
ADD $FSLURL $FSLSTB
4242
--- Dockerfile
+++ Dockerfile
@@ -3,19 +3,19 @@
3
4 ## ---------------------------------------------------------------------
5 ## STAGE 1: Build static Fossil binary
6 ## ---------------------------------------------------------------------
7
8 ### We aren't pinning to a more stable version of Alpine because we want
9 ### to build with the latest tools and libraries available in case they
10 ### fixed something that matters to us since the last build. Everything
11 ### below depends on this layer, and so, alas, we toss this container's
12 ### cache on Alpine's release schedule, roughly once a month.
13 FROM alpine:latest AS bld
14 WORKDIR /fsl
15
16 ### Bake the basic Alpine Linux into a base layer so it only changes
17 ### when the upstream image is updated or we change the package set.
18 RUN set -x \
19 && apk update \
20 && apk upgrade --no-cache \
21 && apk add --no-cache \
@@ -23,19 +23,19 @@
23 linux-headers musl-dev \
24 openssl-dev openssl-libs-static \
25 zlib-dev zlib-static
26
27 ### Build Fossil as a separate layer so we don't have to rebuild the
28 ### Alpine environment for each iteration of Fossil's dev cycle.
29 ###
30 ### We must cope with a bizarre ADD misfeature here: it unpacks tarballs
31 ### automatically when you give it a local file name but not if you give
32 ### it a /tarball URL! It matters because we default to a URL in case
33 ### you're building outside a Fossil checkout, but when building via the
34 ### container-image target, we avoid a costly hit on fossil-scm.org
35 ### by leveraging its DVCS nature via the "tarball" command and passing
36 ### the resulting file's name in.
37 ARG FSLCFG=""
38 ARG FSLVER="trunk"
39 ARG FSLURL="https://fossil-scm.org/home/tarball/src?r=${FSLVER}"
40 ENV FSLSTB=/fsl/src.tar.gz
41 ADD $FSLURL $FSLSTB
42
--- Dockerfile
+++ Dockerfile
@@ -3,19 +3,19 @@
3
4 ## ---------------------------------------------------------------------
5 ## STAGE 1: Build static Fossil binary
6 ## ---------------------------------------------------------------------
7
8 ### We don't pin a more stable version of our base layer because we want
9 ### to build with the latest tools and libraries available in case they
10 ### fixed something that matters to us since the last build. Everything
11 ### below depends on this layer, and so, alas, we toss this container's
12 ### cache on Alpine's release schedule, roughly once a month.
13 FROM alpine:latest AS bld
14 WORKDIR /fsl
15
16 ### Bake the build-time userland into a base layer so it only changes
17 ### when the upstream image is updated or we change the package set.
18 RUN set -x \
19 && apk update \
20 && apk upgrade --no-cache \
21 && apk add --no-cache \
@@ -23,19 +23,19 @@
23 linux-headers musl-dev \
24 openssl-dev openssl-libs-static \
25 zlib-dev zlib-static
26
27 ### Build Fossil as a separate layer so we don't have to rebuild the
28 ### userland for each iteration of Fossil's dev cycle.
29 ###
30 ### We must cope with a bizarre ADD misfeature here: it unpacks tarballs
31 ### automatically when you give it a local file name but not if you give
32 ### it a /tarball URL! It matters because we default to a URL in case
33 ### you're building outside a Fossil checkout, but when building via the
34 ### container-image target, we avoid a costly hit on fossil-scm.org by
35 ### leveraging its DVCS nature via the "tarball" command and passing the
36 ### resulting file's name in.
37 ARG FSLCFG=""
38 ARG FSLVER="trunk"
39 ARG FSLURL="https://fossil-scm.org/home/tarball/src?r=${FSLVER}"
40 ENV FSLSTB=/fsl/src.tar.gz
41 ADD $FSLURL $FSLSTB
42
+1 -1
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1
-2.25
1
+2.26
22
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1 2.25
2
--- VERSION
+++ VERSION
@@ -1,1 +1,1 @@
1 2.26
2
+448 -156
--- extsrc/shell.c
+++ extsrc/shell.c
@@ -120,13 +120,10 @@
120120
#include <math.h>
121121
#include "sqlite3.h"
122122
typedef sqlite3_int64 i64;
123123
typedef sqlite3_uint64 u64;
124124
typedef unsigned char u8;
125
-#if SQLITE_USER_AUTHENTICATION
126
-# include "sqlite3userauth.h"
127
-#endif
128125
#include <ctype.h>
129126
#include <stdarg.h>
130127
131128
#if !defined(_WIN32) && !defined(WIN32)
132129
# include <signal.h>
@@ -408,21 +405,21 @@
408405
wchar_t *b1, *b2;
409406
int sz1, sz2;
410407
411408
sz1 = (int)strlen(zFilename);
412409
sz2 = (int)strlen(zMode);
413
- b1 = malloc( (sz1+1)*sizeof(b1[0]) );
414
- b2 = malloc( (sz2+1)*sizeof(b1[0]) );
410
+ b1 = sqlite3_malloc( (sz1+1)*sizeof(b1[0]) );
411
+ b2 = sqlite3_malloc( (sz2+1)*sizeof(b1[0]) );
415412
if( b1 && b2 ){
416413
sz1 = MultiByteToWideChar(CP_UTF8, 0, zFilename, sz1, b1, sz1);
417414
b1[sz1] = 0;
418415
sz2 = MultiByteToWideChar(CP_UTF8, 0, zMode, sz2, b2, sz2);
419416
b2[sz2] = 0;
420417
fp = _wfopen(b1, b2);
421418
}
422
- free(b1);
423
- free(b2);
419
+ sqlite3_free(b1);
420
+ sqlite3_free(b2);
424421
simBinaryOther = 0;
425422
return fp;
426423
}
427424
428425
@@ -434,21 +431,21 @@
434431
wchar_t *b1, *b2;
435432
int sz1, sz2;
436433
437434
sz1 = (int)strlen(zCommand);
438435
sz2 = (int)strlen(zMode);
439
- b1 = malloc( (sz1+1)*sizeof(b1[0]) );
440
- b2 = malloc( (sz2+1)*sizeof(b1[0]) );
436
+ b1 = sqlite3_malloc( (sz1+1)*sizeof(b1[0]) );
437
+ b2 = sqlite3_malloc( (sz2+1)*sizeof(b1[0]) );
441438
if( b1 && b2 ){
442439
sz1 = MultiByteToWideChar(CP_UTF8, 0, zCommand, sz1, b1, sz1);
443440
b1[sz1] = 0;
444441
sz2 = MultiByteToWideChar(CP_UTF8, 0, zMode, sz2, b2, sz2);
445442
b2[sz2] = 0;
446443
fp = _wpopen(b1, b2);
447444
}
448
- free(b1);
449
- free(b2);
445
+ sqlite3_free(b1);
446
+ sqlite3_free(b2);
450447
return fp;
451448
}
452449
453450
/*
454451
** Work-alike for fgets() from the standard C library.
@@ -458,16 +455,26 @@
458455
/* When reading from the command-prompt in Windows, it is necessary
459456
** to use _O_WTEXT input mode to read UTF-16 characters, then translate
460457
** that into UTF-8. Otherwise, non-ASCII characters all get translated
461458
** into '?'.
462459
*/
463
- wchar_t *b1 = malloc( sz*sizeof(wchar_t) );
460
+ wchar_t *b1 = sqlite3_malloc( sz*sizeof(wchar_t) );
464461
if( b1==0 ) return 0;
465
- _setmode(_fileno(in), IsConsole(in) ? _O_WTEXT : _O_U8TEXT);
466
- if( fgetws(b1, sz/4, in)==0 ){
467
- sqlite3_free(b1);
468
- return 0;
462
+#ifndef SQLITE_USE_STDIO_FOR_CONSOLE
463
+ DWORD nRead = 0;
464
+ if( IsConsole(in)
465
+ && ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), b1, sz, &nRead, 0)
466
+ ){
467
+ b1[nRead] = 0;
468
+ }else
469
+#endif
470
+ {
471
+ _setmode(_fileno(in), IsConsole(in) ? _O_WTEXT : _O_U8TEXT);
472
+ if( fgetws(b1, sz/4, in)==0 ){
473
+ sqlite3_free(b1);
474
+ return 0;
475
+ }
469476
}
470477
WideCharToMultiByte(CP_UTF8, 0, b1, -1, buf, sz, 0, 0);
471478
sqlite3_free(b1);
472479
return buf;
473480
}else{
@@ -519,24 +526,37 @@
519526
if( !UseWtextForOutput(out) ){
520527
/* Writing to a file or other destination, just write bytes without
521528
** any translation. */
522529
return fputs(z, out);
523530
}else{
524
- /* When writing to the command-prompt in Windows, it is necessary
525
- ** to use O_U8TEXT to render Unicode U+0080 and greater. Go ahead
526
- ** use O_U8TEXT for everything in text mode.
531
+ /* One must use UTF16 in order to get unicode support when writing
532
+ ** to the console on Windows.
527533
*/
528534
int sz = (int)strlen(z);
529
- wchar_t *b1 = malloc( (sz+1)*sizeof(wchar_t) );
535
+ wchar_t *b1 = sqlite3_malloc( (sz+1)*sizeof(wchar_t) );
530536
if( b1==0 ) return 0;
531537
sz = MultiByteToWideChar(CP_UTF8, 0, z, sz, b1, sz);
532538
b1[sz] = 0;
533
- _setmode(_fileno(out), _O_U8TEXT);
534
- if( UseBinaryWText(out) ){
535
- piecemealOutput(b1, sz, out);
536
- }else{
537
- fputws(b1, out);
539
+
540
+#ifndef SQLITE_STDIO_FOR_CONSOLE
541
+ DWORD nWr = 0;
542
+ if( IsConsole(out)
543
+ && WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),b1,sz,&nWr,0)
544
+ ){
545
+ /* If writing to the console, then the WriteConsoleW() is all we
546
+ ** need to do. */
547
+ }else
548
+#endif
549
+ {
550
+ /* For non-console I/O, or if SQLITE_USE_STDIO_FOR_CONSOLE is defined
551
+ ** then write using the standard library. */
552
+ _setmode(_fileno(out), _O_U8TEXT);
553
+ if( UseBinaryWText(out) ){
554
+ piecemealOutput(b1, sz, out);
555
+ }else{
556
+ fputws(b1, out);
557
+ }
538558
}
539559
sqlite3_free(b1);
540560
return 0;
541561
}
542562
}
@@ -2347,11 +2367,11 @@
23472367
**
23482368
******************************************************************************
23492369
**
23502370
** This SQLite extension implements functions that compute SHA3 hashes
23512371
** in the way described by the (U.S.) NIST FIPS 202 SHA-3 Standard.
2352
-** Two SQL functions are implemented:
2372
+** Three SQL functions are implemented:
23532373
**
23542374
** sha3(X,SIZE)
23552375
** sha3_agg(Y,SIZE)
23562376
** sha3_query(Z,SIZE)
23572377
**
@@ -5070,14 +5090,14 @@
50705090
char **pzErrMsg,
50715091
const sqlite3_api_routines *pApi
50725092
){
50735093
int rc = SQLITE_OK;
50745094
unsigned int i;
5075
-#if defined(SQLITE3_H) || defined(SQLITE_STATIC_PERCENTILE)
5076
- (void)pApi; /* Unused parameter */
5077
-#else
5095
+#ifdef SQLITE3EXT_H
50785096
SQLITE_EXTENSION_INIT2(pApi);
5097
+#else
5098
+ (void)pApi; /* Unused parameter */
50795099
#endif
50805100
(void)pzErrMsg; /* Unused parameter */
50815101
for(i=0; i<sizeof(aPercentFunc)/sizeof(aPercentFunc[0]); i++){
50825102
rc = sqlite3_create_window_function(db,
50835103
aPercentFunc[i].zName,
@@ -6831,35 +6851,41 @@
68316851
idxNum |= 0x40;
68326852
}
68336853
continue;
68346854
}
68356855
if( pConstraint->iColumn<SERIES_COLUMN_START ){
6836
- if( pConstraint->iColumn==SERIES_COLUMN_VALUE ){
6856
+ if( pConstraint->iColumn==SERIES_COLUMN_VALUE && pConstraint->usable ){
68376857
switch( op ){
68386858
case SQLITE_INDEX_CONSTRAINT_EQ:
68396859
case SQLITE_INDEX_CONSTRAINT_IS: {
68406860
idxNum |= 0x0080;
68416861
idxNum &= ~0x3300;
68426862
aIdx[5] = i;
68436863
aIdx[6] = -1;
6864
+#ifndef ZERO_ARGUMENT_GENERATE_SERIES
68446865
bStartSeen = 1;
6866
+#endif
68456867
break;
68466868
}
68476869
case SQLITE_INDEX_CONSTRAINT_GE: {
68486870
if( idxNum & 0x0080 ) break;
68496871
idxNum |= 0x0100;
68506872
idxNum &= ~0x0200;
68516873
aIdx[5] = i;
6874
+#ifndef ZERO_ARGUMENT_GENERATE_SERIES
68526875
bStartSeen = 1;
6876
+#endif
68536877
break;
68546878
}
68556879
case SQLITE_INDEX_CONSTRAINT_GT: {
68566880
if( idxNum & 0x0080 ) break;
68576881
idxNum |= 0x0200;
68586882
idxNum &= ~0x0100;
68596883
aIdx[5] = i;
6884
+#ifndef ZERO_ARGUMENT_GENERATE_SERIES
68606885
bStartSeen = 1;
6886
+#endif
68616887
break;
68626888
}
68636889
case SQLITE_INDEX_CONSTRAINT_LE: {
68646890
if( idxNum & 0x0080 ) break;
68656891
idxNum |= 0x1000;
@@ -14167,11 +14193,11 @@
1416714193
/* A view. Or a trigger on a view. */
1416814194
if( zSql ) rc = expertSchemaSql(p->dbv, zSql, pzErrmsg);
1416914195
}else{
1417014196
IdxTable *pTab;
1417114197
rc = idxGetTableInfo(p->db, zName, &pTab, pzErrmsg);
14172
- if( rc==SQLITE_OK ){
14198
+ if( rc==SQLITE_OK && ALWAYS(pTab!=0) ){
1417314199
int i;
1417414200
char *zInner = 0;
1417514201
char *zOuter = 0;
1417614202
pTab->pNext = p->pTable;
1417714203
p->pTable = pTab;
@@ -16235,11 +16261,31 @@
1623516261
**
1623616262
** Similar compiler commands will work on different systems. The key
1623716263
** invariants are (1) you must have -DSQLITE_ENABLE_VFSTRACE so that
1623816264
** the shell.c source file will know to include the -vfstrace command-line
1623916265
** option and (2) you must compile and link the three source files
16240
-** shell,c, test_vfstrace.c, and sqlite3.c.
16266
+** shell,c, test_vfstrace.c, and sqlite3.c.
16267
+**
16268
+** RUNTIME CONTROL OF VFSTRACE OUTPUT
16269
+**
16270
+** The application can use the "vfstrace" pragma to control which VFS
16271
+** APIs are traced. To disable all output:
16272
+**
16273
+** PRAGMA vfstrace('-all');
16274
+**
16275
+** To enable all output (which is the default setting):
16276
+**
16277
+** PRAGMA vfstrace('+all');
16278
+**
16279
+** Individual APIs can be enabled or disabled by name, with or without
16280
+** the initial "x" character. For example, to set up for tracing lock
16281
+** primatives only:
16282
+**
16283
+** PRAGMA vfstrace('-all, +Lock,Unlock,ShmLock');
16284
+**
16285
+** The argument to the vfstrace pragma ignores capitalization and any
16286
+** characters other than alphabetics, '+', and '-'.
1624116287
*/
1624216288
#include <stdlib.h>
1624316289
#include <string.h>
1624416290
/* #include "sqlite3.h" */
1624516291
@@ -16249,10 +16295,12 @@
1624916295
*/
1625016296
typedef struct vfstrace_info vfstrace_info;
1625116297
struct vfstrace_info {
1625216298
sqlite3_vfs *pRootVfs; /* The underlying real VFS */
1625316299
int (*xOut)(const char*, void*); /* Send output here */
16300
+ unsigned int mTrace; /* Mask of interfaces to trace */
16301
+ u8 bOn; /* Tracing on/off */
1625416302
void *pOutArg; /* First argument to xOut */
1625516303
const char *zVfsName; /* Name of this trace-VFS */
1625616304
sqlite3_vfs *pTraceVfs; /* Pointer back to the trace VFS */
1625716305
};
1625816306
@@ -16265,10 +16313,42 @@
1626516313
vfstrace_info *pInfo; /* The trace-VFS to which this file belongs */
1626616314
const char *zFName; /* Base name of the file */
1626716315
sqlite3_file *pReal; /* The real underlying file */
1626816316
};
1626916317
16318
+/*
16319
+** Bit values for vfstrace_info.mTrace.
16320
+*/
16321
+#define VTR_CLOSE 0x00000001
16322
+#define VTR_READ 0x00000002
16323
+#define VTR_WRITE 0x00000004
16324
+#define VTR_TRUNC 0x00000008
16325
+#define VTR_SYNC 0x00000010
16326
+#define VTR_FSIZE 0x00000020
16327
+#define VTR_LOCK 0x00000040
16328
+#define VTR_UNLOCK 0x00000080
16329
+#define VTR_CRL 0x00000100
16330
+#define VTR_FCTRL 0x00000200
16331
+#define VTR_SECSZ 0x00000400
16332
+#define VTR_DEVCHAR 0x00000800
16333
+#define VTR_SHMLOCK 0x00001000
16334
+#define VTR_SHMMAP 0x00002000
16335
+#define VTR_SHMBAR 0x00004000
16336
+#define VTR_SHMUNMAP 0x00008000
16337
+#define VTR_OPEN 0x00010000
16338
+#define VTR_DELETE 0x00020000
16339
+#define VTR_ACCESS 0x00040000
16340
+#define VTR_FULLPATH 0x00080000
16341
+#define VTR_DLOPEN 0x00100000
16342
+#define VTR_DLERR 0x00200000
16343
+#define VTR_DLSYM 0x00400000
16344
+#define VTR_DLCLOSE 0x00800000
16345
+#define VTR_RAND 0x01000000
16346
+#define VTR_SLEEP 0x02000000
16347
+#define VTR_CURTIME 0x04000000
16348
+#define VTR_LASTERR 0x08000000
16349
+
1627016350
/*
1627116351
** Method declarations for vfstrace_file.
1627216352
*/
1627316353
static int vfstraceClose(sqlite3_file*);
1627416354
static int vfstraceRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
@@ -16329,15 +16409,17 @@
1632916409
const char *zFormat,
1633016410
...
1633116411
){
1633216412
va_list ap;
1633316413
char *zMsg;
16334
- va_start(ap, zFormat);
16335
- zMsg = sqlite3_vmprintf(zFormat, ap);
16336
- va_end(ap);
16337
- pInfo->xOut(zMsg, pInfo->pOutArg);
16338
- sqlite3_free(zMsg);
16414
+ if( pInfo->bOn ){
16415
+ va_start(ap, zFormat);
16416
+ zMsg = sqlite3_vmprintf(zFormat, ap);
16417
+ va_end(ap);
16418
+ pInfo->xOut(zMsg, pInfo->pOutArg);
16419
+ sqlite3_free(zMsg);
16420
+ }
1633916421
}
1634016422
1634116423
/*
1634216424
** Try to convert an error code into a symbolic name for that error code.
1634316425
*/
@@ -16431,18 +16513,26 @@
1643116513
int i = *pI;
1643216514
while( zAppend[0] ){ z[i++] = *(zAppend++); }
1643316515
z[i] = 0;
1643416516
*pI = i;
1643516517
}
16518
+
16519
+/*
16520
+** Turn tracing output on or off according to mMask.
16521
+*/
16522
+static void vfstraceOnOff(vfstrace_info *pInfo, unsigned int mMask){
16523
+ pInfo->bOn = (pInfo->mTrace & mMask)!=0;
16524
+}
1643616525
1643716526
/*
1643816527
** Close an vfstrace-file.
1643916528
*/
1644016529
static int vfstraceClose(sqlite3_file *pFile){
1644116530
vfstrace_file *p = (vfstrace_file *)pFile;
1644216531
vfstrace_info *pInfo = p->pInfo;
1644316532
int rc;
16533
+ vfstraceOnOff(pInfo, VTR_CLOSE);
1644416534
vfstrace_printf(pInfo, "%s.xClose(%s)", pInfo->zVfsName, p->zFName);
1644516535
rc = p->pReal->pMethods->xClose(p->pReal);
1644616536
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
1644716537
if( rc==SQLITE_OK ){
1644816538
sqlite3_free((void*)p->base.pMethods);
@@ -16461,10 +16551,11 @@
1646116551
sqlite_int64 iOfst
1646216552
){
1646316553
vfstrace_file *p = (vfstrace_file *)pFile;
1646416554
vfstrace_info *pInfo = p->pInfo;
1646516555
int rc;
16556
+ vfstraceOnOff(pInfo, VTR_READ);
1646616557
vfstrace_printf(pInfo, "%s.xRead(%s,n=%d,ofst=%lld)",
1646716558
pInfo->zVfsName, p->zFName, iAmt, iOfst);
1646816559
rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst);
1646916560
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
1647016561
return rc;
@@ -16480,10 +16571,11 @@
1648016571
sqlite_int64 iOfst
1648116572
){
1648216573
vfstrace_file *p = (vfstrace_file *)pFile;
1648316574
vfstrace_info *pInfo = p->pInfo;
1648416575
int rc;
16576
+ vfstraceOnOff(pInfo, VTR_WRITE);
1648516577
vfstrace_printf(pInfo, "%s.xWrite(%s,n=%d,ofst=%lld)",
1648616578
pInfo->zVfsName, p->zFName, iAmt, iOfst);
1648716579
rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst);
1648816580
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
1648916581
return rc;
@@ -16494,10 +16586,11 @@
1649416586
*/
1649516587
static int vfstraceTruncate(sqlite3_file *pFile, sqlite_int64 size){
1649616588
vfstrace_file *p = (vfstrace_file *)pFile;
1649716589
vfstrace_info *pInfo = p->pInfo;
1649816590
int rc;
16591
+ vfstraceOnOff(pInfo, VTR_TRUNC);
1649916592
vfstrace_printf(pInfo, "%s.xTruncate(%s,%lld)", pInfo->zVfsName, p->zFName,
1650016593
size);
1650116594
rc = p->pReal->pMethods->xTruncate(p->pReal, size);
1650216595
vfstrace_printf(pInfo, " -> %d\n", rc);
1650316596
return rc;
@@ -16518,10 +16611,11 @@
1651816611
else if( flags & SQLITE_SYNC_NORMAL ) strappend(zBuf, &i, "|NORMAL");
1651916612
if( flags & SQLITE_SYNC_DATAONLY ) strappend(zBuf, &i, "|DATAONLY");
1652016613
if( flags & ~(SQLITE_SYNC_FULL|SQLITE_SYNC_DATAONLY) ){
1652116614
sqlite3_snprintf(sizeof(zBuf)-i, &zBuf[i], "|0x%x", flags);
1652216615
}
16616
+ vfstraceOnOff(pInfo, VTR_SYNC);
1652316617
vfstrace_printf(pInfo, "%s.xSync(%s,%s)", pInfo->zVfsName, p->zFName,
1652416618
&zBuf[1]);
1652516619
rc = p->pReal->pMethods->xSync(p->pReal, flags);
1652616620
vfstrace_printf(pInfo, " -> %d\n", rc);
1652716621
return rc;
@@ -16532,10 +16626,11 @@
1653216626
*/
1653316627
static int vfstraceFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
1653416628
vfstrace_file *p = (vfstrace_file *)pFile;
1653516629
vfstrace_info *pInfo = p->pInfo;
1653616630
int rc;
16631
+ vfstraceOnOff(pInfo, VTR_FSIZE);
1653716632
vfstrace_printf(pInfo, "%s.xFileSize(%s)", pInfo->zVfsName, p->zFName);
1653816633
rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
1653916634
vfstrace_print_errcode(pInfo, " -> %s,", rc);
1654016635
vfstrace_printf(pInfo, " size=%lld\n", *pSize);
1654116636
return rc;
@@ -16560,10 +16655,11 @@
1656016655
*/
1656116656
static int vfstraceLock(sqlite3_file *pFile, int eLock){
1656216657
vfstrace_file *p = (vfstrace_file *)pFile;
1656316658
vfstrace_info *pInfo = p->pInfo;
1656416659
int rc;
16660
+ vfstraceOnOff(pInfo, VTR_LOCK);
1656516661
vfstrace_printf(pInfo, "%s.xLock(%s,%s)", pInfo->zVfsName, p->zFName,
1656616662
lockName(eLock));
1656716663
rc = p->pReal->pMethods->xLock(p->pReal, eLock);
1656816664
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
1656916665
return rc;
@@ -16574,10 +16670,11 @@
1657416670
*/
1657516671
static int vfstraceUnlock(sqlite3_file *pFile, int eLock){
1657616672
vfstrace_file *p = (vfstrace_file *)pFile;
1657716673
vfstrace_info *pInfo = p->pInfo;
1657816674
int rc;
16675
+ vfstraceOnOff(pInfo, VTR_UNLOCK);
1657916676
vfstrace_printf(pInfo, "%s.xUnlock(%s,%s)", pInfo->zVfsName, p->zFName,
1658016677
lockName(eLock));
1658116678
rc = p->pReal->pMethods->xUnlock(p->pReal, eLock);
1658216679
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
1658316680
return rc;
@@ -16588,10 +16685,11 @@
1658816685
*/
1658916686
static int vfstraceCheckReservedLock(sqlite3_file *pFile, int *pResOut){
1659016687
vfstrace_file *p = (vfstrace_file *)pFile;
1659116688
vfstrace_info *pInfo = p->pInfo;
1659216689
int rc;
16690
+ vfstraceOnOff(pInfo, VTR_CRL);
1659316691
vfstrace_printf(pInfo, "%s.xCheckReservedLock(%s,%d)",
1659416692
pInfo->zVfsName, p->zFName);
1659516693
rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut);
1659616694
vfstrace_print_errcode(pInfo, " -> %s", rc);
1659716695
vfstrace_printf(pInfo, ", out=%d\n", *pResOut);
@@ -16607,10 +16705,11 @@
1660716705
int rc;
1660816706
char zBuf[100];
1660916707
char zBuf2[100];
1661016708
char *zOp;
1661116709
char *zRVal = 0;
16710
+ vfstraceOnOff(pInfo, VTR_FCTRL);
1661216711
switch( op ){
1661316712
case SQLITE_FCNTL_LOCKSTATE: zOp = "LOCKSTATE"; break;
1661416713
case SQLITE_GET_LOCKPROXYFILE: zOp = "GET_LOCKPROXYFILE"; break;
1661516714
case SQLITE_SET_LOCKPROXYFILE: zOp = "SET_LOCKPROXYFILE"; break;
1661616715
case SQLITE_LAST_ERRNO: zOp = "LAST_ERRNO"; break;
@@ -16635,10 +16734,83 @@
1663516734
case SQLITE_FCNTL_OVERWRITE: zOp = "OVERWRITE"; break;
1663616735
case SQLITE_FCNTL_VFSNAME: zOp = "VFSNAME"; break;
1663716736
case SQLITE_FCNTL_POWERSAFE_OVERWRITE: zOp = "POWERSAFE_OVERWRITE"; break;
1663816737
case SQLITE_FCNTL_PRAGMA: {
1663916738
const char *const* a = (const char*const*)pArg;
16739
+ if( a[1] && strcmp(a[1],"vfstrace")==0 && a[2] ){
16740
+ const u8 *zArg = (const u8*)a[2];
16741
+ if( zArg[0]>='0' && zArg[0]<=9 ){
16742
+ pInfo->mTrace = (sqlite3_uint64)strtoll(a[2], 0, 0);
16743
+ }else{
16744
+ static const struct {
16745
+ const char *z;
16746
+ unsigned int m;
16747
+ } aKw[] = {
16748
+ { "all", 0xffffffff },
16749
+ { "close", VTR_CLOSE },
16750
+ { "read", VTR_READ },
16751
+ { "write", VTR_WRITE },
16752
+ { "truncate", VTR_TRUNC },
16753
+ { "sync", VTR_SYNC },
16754
+ { "filesize", VTR_FSIZE },
16755
+ { "lock", VTR_LOCK },
16756
+ { "unlock", VTR_UNLOCK },
16757
+ { "checkreservedlock", VTR_CRL },
16758
+ { "filecontrol", VTR_FCTRL },
16759
+ { "sectorsize", VTR_SECSZ },
16760
+ { "devicecharacteristics", VTR_DEVCHAR },
16761
+ { "shmlock", VTR_SHMLOCK },
16762
+ { "shmmap", VTR_SHMMAP },
16763
+ { "shmummap", VTR_SHMUNMAP },
16764
+ { "shmbarrier", VTR_SHMBAR },
16765
+ { "open", VTR_OPEN },
16766
+ { "delete", VTR_DELETE },
16767
+ { "access", VTR_ACCESS },
16768
+ { "fullpathname", VTR_FULLPATH },
16769
+ { "dlopen", VTR_DLOPEN },
16770
+ { "dlerror", VTR_DLERR },
16771
+ { "dlsym", VTR_DLSYM },
16772
+ { "dlclose", VTR_DLCLOSE },
16773
+ { "randomness", VTR_RAND },
16774
+ { "sleep", VTR_SLEEP },
16775
+ { "currenttime", VTR_CURTIME },
16776
+ { "currenttimeint64", VTR_CURTIME },
16777
+ { "getlasterror", VTR_LASTERR },
16778
+ };
16779
+ int onOff = 1;
16780
+ while( zArg[0] ){
16781
+ int jj, n;
16782
+ while( zArg[0]!=0 && zArg[0]!='-' && zArg[0]!='+'
16783
+ && !isalpha(zArg[0]) ) zArg++;
16784
+ if( zArg[0]==0 ) break;
16785
+ if( zArg[0]=='-' ){
16786
+ onOff = 0;
16787
+ zArg++;
16788
+ }else if( zArg[0]=='+' ){
16789
+ onOff = 1;
16790
+ zArg++;
16791
+ }
16792
+ while( !isalpha(zArg[0]) ){
16793
+ if( zArg[0]==0 ) break;
16794
+ zArg++;
16795
+ }
16796
+ if( zArg[0]=='x' && isalpha(zArg[1]) ) zArg++;
16797
+ for(n=0; isalpha(zArg[n]); n++){}
16798
+ for(jj=0; jj<(int)(sizeof(aKw)/sizeof(aKw[0])); jj++){
16799
+ if( sqlite3_strnicmp(aKw[jj].z,(const char*)zArg,n)==0 ){
16800
+ if( onOff ){
16801
+ pInfo->mTrace |= aKw[jj].m;
16802
+ }else{
16803
+ pInfo->mTrace &= ~aKw[jj].m;
16804
+ }
16805
+ break;
16806
+ }
16807
+ }
16808
+ zArg += n;
16809
+ }
16810
+ }
16811
+ }
1664016812
sqlite3_snprintf(sizeof(zBuf), zBuf, "PRAGMA,[%s,%s]",a[1],a[2]);
1664116813
zOp = zBuf;
1664216814
break;
1664316815
}
1664416816
case SQLITE_FCNTL_BUSYHANDLER: zOp = "BUSYHANDLER"; break;
@@ -16730,10 +16902,11 @@
1673016902
*/
1673116903
static int vfstraceSectorSize(sqlite3_file *pFile){
1673216904
vfstrace_file *p = (vfstrace_file *)pFile;
1673316905
vfstrace_info *pInfo = p->pInfo;
1673416906
int rc;
16907
+ vfstraceOnOff(pInfo, VTR_SECSZ);
1673516908
vfstrace_printf(pInfo, "%s.xSectorSize(%s)", pInfo->zVfsName, p->zFName);
1673616909
rc = p->pReal->pMethods->xSectorSize(p->pReal);
1673716910
vfstrace_printf(pInfo, " -> %d\n", rc);
1673816911
return rc;
1673916912
}
@@ -16743,10 +16916,11 @@
1674316916
*/
1674416917
static int vfstraceDeviceCharacteristics(sqlite3_file *pFile){
1674516918
vfstrace_file *p = (vfstrace_file *)pFile;
1674616919
vfstrace_info *pInfo = p->pInfo;
1674716920
int rc;
16921
+ vfstraceOnOff(pInfo, VTR_DEVCHAR);
1674816922
vfstrace_printf(pInfo, "%s.xDeviceCharacteristics(%s)",
1674916923
pInfo->zVfsName, p->zFName);
1675016924
rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal);
1675116925
vfstrace_printf(pInfo, " -> 0x%08x\n", rc);
1675216926
return rc;
@@ -16754,25 +16928,43 @@
1675416928
1675516929
/*
1675616930
** Shared-memory operations.
1675716931
*/
1675816932
static int vfstraceShmLock(sqlite3_file *pFile, int ofst, int n, int flags){
16933
+ static const char *azLockName[] = {
16934
+ "WRITE",
16935
+ "CKPT",
16936
+ "RECOVER",
16937
+ "READ0",
16938
+ "READ1",
16939
+ "READ2",
16940
+ "READ3",
16941
+ "READ4",
16942
+ };
1675916943
vfstrace_file *p = (vfstrace_file *)pFile;
1676016944
vfstrace_info *pInfo = p->pInfo;
1676116945
int rc;
1676216946
char zLck[100];
1676316947
int i = 0;
16948
+ vfstraceOnOff(pInfo, VTR_SHMLOCK);
1676416949
memcpy(zLck, "|0", 3);
1676516950
if( flags & SQLITE_SHM_UNLOCK ) strappend(zLck, &i, "|UNLOCK");
1676616951
if( flags & SQLITE_SHM_LOCK ) strappend(zLck, &i, "|LOCK");
1676716952
if( flags & SQLITE_SHM_SHARED ) strappend(zLck, &i, "|SHARED");
1676816953
if( flags & SQLITE_SHM_EXCLUSIVE ) strappend(zLck, &i, "|EXCLUSIVE");
1676916954
if( flags & ~(0xf) ){
1677016955
sqlite3_snprintf(sizeof(zLck)-i, &zLck[i], "|0x%x", flags);
1677116956
}
16772
- vfstrace_printf(pInfo, "%s.xShmLock(%s,ofst=%d,n=%d,%s)",
16773
- pInfo->zVfsName, p->zFName, ofst, n, &zLck[1]);
16957
+ if( ofst>=0 && ofst<(int)(sizeof(azLockName)/sizeof(azLockName[0])) ){
16958
+ vfstrace_printf(pInfo, "%s.xShmLock(%s,ofst=%d(%s),n=%d,%s)",
16959
+ pInfo->zVfsName, p->zFName, ofst, azLockName[ofst],
16960
+ n, &zLck[1]);
16961
+ }else{
16962
+ vfstrace_printf(pInfo, "%s.xShmLock(%s,ofst=5d,n=%d,%s)",
16963
+ pInfo->zVfsName, p->zFName, ofst,
16964
+ n, &zLck[1]);
16965
+ }
1677416966
rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags);
1677516967
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
1677616968
return rc;
1677716969
}
1677816970
static int vfstraceShmMap(
@@ -16783,26 +16975,29 @@
1678316975
void volatile **pp
1678416976
){
1678516977
vfstrace_file *p = (vfstrace_file *)pFile;
1678616978
vfstrace_info *pInfo = p->pInfo;
1678716979
int rc;
16980
+ vfstraceOnOff(pInfo, VTR_SHMMAP);
1678816981
vfstrace_printf(pInfo, "%s.xShmMap(%s,iRegion=%d,szRegion=%d,isWrite=%d,*)",
1678916982
pInfo->zVfsName, p->zFName, iRegion, szRegion, isWrite);
1679016983
rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp);
1679116984
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
1679216985
return rc;
1679316986
}
1679416987
static void vfstraceShmBarrier(sqlite3_file *pFile){
1679516988
vfstrace_file *p = (vfstrace_file *)pFile;
1679616989
vfstrace_info *pInfo = p->pInfo;
16990
+ vfstraceOnOff(pInfo, VTR_SHMBAR);
1679716991
vfstrace_printf(pInfo, "%s.xShmBarrier(%s)\n", pInfo->zVfsName, p->zFName);
1679816992
p->pReal->pMethods->xShmBarrier(p->pReal);
1679916993
}
1680016994
static int vfstraceShmUnmap(sqlite3_file *pFile, int delFlag){
1680116995
vfstrace_file *p = (vfstrace_file *)pFile;
1680216996
vfstrace_info *pInfo = p->pInfo;
1680316997
int rc;
16998
+ vfstraceOnOff(pInfo, VTR_SHMUNMAP);
1680416999
vfstrace_printf(pInfo, "%s.xShmUnmap(%s,delFlag=%d)",
1680517000
pInfo->zVfsName, p->zFName, delFlag);
1680617001
rc = p->pReal->pMethods->xShmUnmap(p->pReal, delFlag);
1680717002
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
1680817003
return rc;
@@ -16826,10 +17021,11 @@
1682617021
sqlite3_vfs *pRoot = pInfo->pRootVfs;
1682717022
p->pInfo = pInfo;
1682817023
p->zFName = zName ? fileTail(zName) : "<temp>";
1682917024
p->pReal = (sqlite3_file *)&p[1];
1683017025
rc = pRoot->xOpen(pRoot, zName, p->pReal, flags, pOutFlags);
17026
+ vfstraceOnOff(pInfo, VTR_OPEN);
1683117027
vfstrace_printf(pInfo, "%s.xOpen(%s,flags=0x%x)",
1683217028
pInfo->zVfsName, p->zFName, flags);
1683317029
if( p->pReal->pMethods ){
1683417030
sqlite3_io_methods *pNew = sqlite3_malloc( sizeof(*pNew) );
1683517031
const sqlite3_io_methods *pSub = p->pReal->pMethods;
@@ -16871,10 +17067,11 @@
1687117067
*/
1687217068
static int vfstraceDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
1687317069
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
1687417070
sqlite3_vfs *pRoot = pInfo->pRootVfs;
1687517071
int rc;
17072
+ vfstraceOnOff(pInfo, VTR_DELETE);
1687617073
vfstrace_printf(pInfo, "%s.xDelete(\"%s\",%d)",
1687717074
pInfo->zVfsName, zPath, dirSync);
1687817075
rc = pRoot->xDelete(pRoot, zPath, dirSync);
1687917076
vfstrace_print_errcode(pInfo, " -> %s\n", rc);
1688017077
return rc;
@@ -16891,10 +17088,11 @@
1689117088
int *pResOut
1689217089
){
1689317090
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
1689417091
sqlite3_vfs *pRoot = pInfo->pRootVfs;
1689517092
int rc;
17093
+ vfstraceOnOff(pInfo, VTR_ACCESS);
1689617094
vfstrace_printf(pInfo, "%s.xAccess(\"%s\",%d)",
1689717095
pInfo->zVfsName, zPath, flags);
1689817096
rc = pRoot->xAccess(pRoot, zPath, flags, pResOut);
1689917097
vfstrace_print_errcode(pInfo, " -> %s", rc);
1690017098
vfstrace_printf(pInfo, ", out=%d\n", *pResOut);
@@ -16913,10 +17111,11 @@
1691317111
char *zOut
1691417112
){
1691517113
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
1691617114
sqlite3_vfs *pRoot = pInfo->pRootVfs;
1691717115
int rc;
17116
+ vfstraceOnOff(pInfo, VTR_FULLPATH);
1691817117
vfstrace_printf(pInfo, "%s.xFullPathname(\"%s\")",
1691917118
pInfo->zVfsName, zPath);
1692017119
rc = pRoot->xFullPathname(pRoot, zPath, nOut, zOut);
1692117120
vfstrace_print_errcode(pInfo, " -> %s", rc);
1692217121
vfstrace_printf(pInfo, ", out=\"%.*s\"\n", nOut, zOut);
@@ -16927,10 +17126,11 @@
1692717126
** Open the dynamic library located at zPath and return a handle.
1692817127
*/
1692917128
static void *vfstraceDlOpen(sqlite3_vfs *pVfs, const char *zPath){
1693017129
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
1693117130
sqlite3_vfs *pRoot = pInfo->pRootVfs;
17131
+ vfstraceOnOff(pInfo, VTR_DLOPEN);
1693217132
vfstrace_printf(pInfo, "%s.xDlOpen(\"%s\")\n", pInfo->zVfsName, zPath);
1693317133
return pRoot->xDlOpen(pRoot, zPath);
1693417134
}
1693517135
1693617136
/*
@@ -16939,10 +17139,11 @@
1693917139
** with dynamic libraries.
1694017140
*/
1694117141
static void vfstraceDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
1694217142
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
1694317143
sqlite3_vfs *pRoot = pInfo->pRootVfs;
17144
+ vfstraceOnOff(pInfo, VTR_DLERR);
1694417145
vfstrace_printf(pInfo, "%s.xDlError(%d)", pInfo->zVfsName, nByte);
1694517146
pRoot->xDlError(pRoot, nByte, zErrMsg);
1694617147
vfstrace_printf(pInfo, " -> \"%s\"", zErrMsg);
1694717148
}
1694817149
@@ -16960,10 +17161,11 @@
1696017161
** Close the dynamic library handle pHandle.
1696117162
*/
1696217163
static void vfstraceDlClose(sqlite3_vfs *pVfs, void *pHandle){
1696317164
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
1696417165
sqlite3_vfs *pRoot = pInfo->pRootVfs;
17166
+ vfstraceOnOff(pInfo, VTR_DLCLOSE);
1696517167
vfstrace_printf(pInfo, "%s.xDlOpen()\n", pInfo->zVfsName);
1696617168
pRoot->xDlClose(pRoot, pHandle);
1696717169
}
1696817170
1696917171
/*
@@ -16971,10 +17173,11 @@
1697117173
** random data.
1697217174
*/
1697317175
static int vfstraceRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
1697417176
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
1697517177
sqlite3_vfs *pRoot = pInfo->pRootVfs;
17178
+ vfstraceOnOff(pInfo, VTR_RAND);
1697617179
vfstrace_printf(pInfo, "%s.xRandomness(%d)\n", pInfo->zVfsName, nByte);
1697717180
return pRoot->xRandomness(pRoot, nByte, zBufOut);
1697817181
}
1697917182
1698017183
/*
@@ -16982,34 +17185,52 @@
1698217185
** actually slept.
1698317186
*/
1698417187
static int vfstraceSleep(sqlite3_vfs *pVfs, int nMicro){
1698517188
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
1698617189
sqlite3_vfs *pRoot = pInfo->pRootVfs;
17190
+ vfstraceOnOff(pInfo, VTR_SLEEP);
17191
+ vfstrace_printf(pInfo, "%s.xSleep(%d)\n", pInfo->zVfsName, nMicro);
1698717192
return pRoot->xSleep(pRoot, nMicro);
1698817193
}
1698917194
1699017195
/*
1699117196
** Return the current time as a Julian Day number in *pTimeOut.
1699217197
*/
1699317198
static int vfstraceCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
1699417199
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
1699517200
sqlite3_vfs *pRoot = pInfo->pRootVfs;
16996
- return pRoot->xCurrentTime(pRoot, pTimeOut);
17201
+ int rc;
17202
+ vfstraceOnOff(pInfo, VTR_CURTIME);
17203
+ vfstrace_printf(pInfo, "%s.xCurrentTime()", pInfo->zVfsName);
17204
+ rc = pRoot->xCurrentTime(pRoot, pTimeOut);
17205
+ vfstrace_printf(pInfo, " -> %.17g\n", *pTimeOut);
17206
+ return rc;
1699717207
}
1699817208
static int vfstraceCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){
1699917209
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
1700017210
sqlite3_vfs *pRoot = pInfo->pRootVfs;
17001
- return pRoot->xCurrentTimeInt64(pRoot, pTimeOut);
17211
+ int rc;
17212
+ vfstraceOnOff(pInfo, VTR_CURTIME);
17213
+ vfstrace_printf(pInfo, "%s.xCurrentTimeInt64()", pInfo->zVfsName);
17214
+ rc = pRoot->xCurrentTimeInt64(pRoot, pTimeOut);
17215
+ vfstrace_printf(pInfo, " -> %lld\n", *pTimeOut);
17216
+ return rc;
1700217217
}
1700317218
1700417219
/*
17005
-** Return th3 most recent error code and message
17220
+** Return the most recent error code and message
1700617221
*/
17007
-static int vfstraceGetLastError(sqlite3_vfs *pVfs, int iErr, char *zErr){
17222
+static int vfstraceGetLastError(sqlite3_vfs *pVfs, int nErr, char *zErr){
1700817223
vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
1700917224
sqlite3_vfs *pRoot = pInfo->pRootVfs;
17010
- return pRoot->xGetLastError(pRoot, iErr, zErr);
17225
+ int rc;
17226
+ vfstraceOnOff(pInfo, VTR_LASTERR);
17227
+ vfstrace_printf(pInfo, "%s.xGetLastError(%d,zBuf)", pInfo->zVfsName, nErr);
17228
+ if( nErr ) zErr[0] = 0;
17229
+ rc = pRoot->xGetLastError(pRoot, nErr, zErr);
17230
+ vfstrace_printf(pInfo, " -> zBuf[] = \"%s\", rc = %d\n", nErr?zErr:"", rc);
17231
+ return rc;
1701117232
}
1701217233
1701317234
/*
1701417235
** Override system calls.
1701517236
*/
@@ -17099,10 +17320,12 @@
1709917320
pInfo->pRootVfs = pRoot;
1710017321
pInfo->xOut = xOut;
1710117322
pInfo->pOutArg = pOutArg;
1710217323
pInfo->zVfsName = pNew->zName;
1710317324
pInfo->pTraceVfs = pNew;
17325
+ pInfo->mTrace = 0xffffffff;
17326
+ pInfo->bOn = 1;
1710417327
vfstrace_printf(pInfo, "%s.enabled_for(\"%s\")\n",
1710517328
pInfo->zVfsName, pRoot->zName);
1710617329
return sqlite3_vfs_register(pNew, makeDefault);
1710717330
}
1710817331
@@ -20228,10 +20451,12 @@
2022820451
apVal[iField] = sqlite3_value_dup( pVal );
2022920452
if( apVal[iField]==0 ){
2023020453
recoverError(p, SQLITE_NOMEM, 0);
2023120454
}
2023220455
p1->nVal = iField+1;
20456
+ }else if( pTab->nCol==0 ){
20457
+ p1->nVal = pTab->nCol;
2023320458
}
2023420459
p1->iPrevCell = iCell;
2023520460
p1->iPrevPage = iPage;
2023620461
}
2023720462
}else{
@@ -21935,12 +22160,12 @@
2193522160
const char *zSkipValidUtf8(const char *z, int nAccept, long ccm){
2193622161
int ng = (nAccept<0)? -nAccept : 0;
2193722162
const char *pcLimit = (nAccept>=0)? z+nAccept : 0;
2193822163
assert(z!=0);
2193922164
while( (pcLimit)? (z<pcLimit) : (ng-- != 0) ){
21940
- char c = *z;
21941
- if( (c & 0x80) == 0 ){
22165
+ unsigned char c = *(u8*)z;
22166
+ if( c<0x7f ){
2194222167
if( ccm != 0L && c < 0x20 && ((1L<<c) & ccm) != 0 ) return z;
2194322168
++z; /* ASCII */
2194422169
}else if( (c & 0xC0) != 0xC0 ) return z; /* not a lead byte */
2194522170
else{
2194622171
const char *zt = z+1; /* Got lead byte, look at trail bytes.*/
@@ -22004,14 +22229,14 @@
2200422229
}
2200522230
sqlite3_fputs(zq, out);
2200622231
}
2200722232
2200822233
/*
22009
-** Output the given string as a quoted according to JSON quoting rules.
22234
+** Output the given string as quoted according to JSON quoting rules.
2201022235
*/
2201122236
static void output_json_string(FILE *out, const char *z, i64 n){
22012
- char c;
22237
+ unsigned char c;
2201322238
static const char *zq = "\"";
2201422239
static long ctrlMask = ~0L;
2201522240
static const char *zDQBS = "\"\\";
2201622241
const char *pcLimit;
2201722242
char ace[3] = "\\?";
@@ -22027,11 +22252,11 @@
2202722252
if( pcEnd > z ){
2202822253
sqlite3_fprintf(out, "%.*s", (int)(pcEnd-z), z);
2202922254
z = pcEnd;
2203022255
}
2203122256
if( z >= pcLimit ) break;
22032
- c = *(z++);
22257
+ c = (unsigned char)*(z++);
2203322258
switch( c ){
2203422259
case '"': case '\\':
2203522260
cbsSay = (char)c;
2203622261
break;
2203722262
case '\b': cbsSay = 'b'; break;
@@ -22042,12 +22267,12 @@
2204222267
default: cbsSay = 0; break;
2204322268
}
2204422269
if( cbsSay ){
2204522270
ace[1] = cbsSay;
2204622271
sqlite3_fputs(ace, out);
22047
- }else if( c<=0x1f ){
22048
- sqlite3_fprintf(out, "u%04x", c);
22272
+ }else if( c<=0x1f || c>=0x7f ){
22273
+ sqlite3_fprintf(out, "\\u%04x", c);
2204922274
}else{
2205022275
ace[1] = (char)c;
2205122276
sqlite3_fputs(ace+1, out);
2205222277
}
2205322278
}
@@ -24784,13 +25009,13 @@
2478425009
if( rc ){
2478525010
sqlite3_fprintf(p->out, "/****** ERROR: %s ******/\n", zErr);
2478625011
}else{
2478725012
rc = SQLITE_CORRUPT;
2478825013
}
24789
- sqlite3_free(zErr);
2479025014
free(zQ2);
2479125015
}
25016
+ sqlite3_free(zErr);
2479225017
return rc;
2479325018
}
2479425019
2479525020
/*
2479625021
** Text of help messages.
@@ -24849,10 +25074,11 @@
2484925074
".databases List names and files of attached databases",
2485025075
".dbconfig ?op? ?val? List or change sqlite3_db_config() options",
2485125076
#if SQLITE_SHELL_HAVE_RECOVER
2485225077
".dbinfo ?DB? Show status information about the database",
2485325078
#endif
25079
+ ".dbtotxt Hex dump of the database file",
2485425080
".dump ?OBJECTS? Render database content as SQL",
2485525081
" Options:",
2485625082
" --data-only Output only INSERT statements",
2485725083
" --newlines Allow unescaped newline characters in output",
2485825084
" --nosys Omit system tables (ex: \"sqlite_stat1\")",
@@ -25654,11 +25880,12 @@
2565425880
sqlite3_fprintf(stderr,
2565525881
"Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db));
2565625882
}
2565725883
}
2565825884
25659
-#if HAVE_READLINE || HAVE_EDITLINE
25885
+#if (HAVE_READLINE || HAVE_EDITLINE) \
25886
+ && !defined(SQLITE_OMIT_READLINE_COMPLETION)
2566025887
/*
2566125888
** Readline completion callbacks
2566225889
*/
2566325890
static char *readline_completion_generator(const char *text, int state){
2566425891
static sqlite3_stmt *pStmt = 0;
@@ -25692,19 +25919,26 @@
2569225919
#elif HAVE_LINENOISE
2569325920
/*
2569425921
** Linenoise completion callback. Note that the 3rd argument is from
2569525922
** the "msteveb" version of linenoise, not the "antirez" version.
2569625923
*/
25697
-static void linenoise_completion(const char *zLine, linenoiseCompletions *lc,
25698
- void *pUserData){
25924
+static void linenoise_completion(
25925
+ const char *zLine,
25926
+ linenoiseCompletions *lc
25927
+#if HAVE_LINENOISE==2
25928
+ ,void *pUserData
25929
+#endif
25930
+){
2569925931
i64 nLine = strlen(zLine);
2570025932
i64 i, iStart;
2570125933
sqlite3_stmt *pStmt = 0;
2570225934
char *zSql;
2570325935
char zBuf[1000];
2570425936
25937
+#if HAVE_LINENOISE==2
2570525938
UNUSED_PARAMETER(pUserData);
25939
+#endif
2570625940
if( nLine>(i64)sizeof(zBuf)-30 ) return;
2570725941
if( zLine[0]=='.' || zLine[0]=='#') return;
2570825942
for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){}
2570925943
if( i==nLine-1 ) return;
2571025944
iStart = i+1;
@@ -26390,18 +26624,24 @@
2639026624
#endif
2639126625
2639226626
/*
2639326627
** Run an SQL command and return the single integer result.
2639426628
*/
26395
-static int db_int(sqlite3 *db, const char *zSql){
26629
+static int db_int(sqlite3 *db, const char *zSql, ...){
2639626630
sqlite3_stmt *pStmt;
2639726631
int res = 0;
26398
- sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
26632
+ char *z;
26633
+ va_list ap;
26634
+ va_start(ap, zSql);
26635
+ z = sqlite3_vmprintf(zSql, ap);
26636
+ va_end(ap);
26637
+ sqlite3_prepare_v2(db, z, -1, &pStmt, 0);
2639926638
if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){
2640026639
res = sqlite3_column_int(pStmt,0);
2640126640
}
2640226641
sqlite3_finalize(pStmt);
26642
+ sqlite3_free(z);
2640326643
return res;
2640426644
}
2640526645
2640626646
#if SQLITE_SHELL_HAVE_RECOVER
2640726647
/*
@@ -26500,21 +26740,112 @@
2650026740
zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_schema");
2650126741
}else{
2650226742
zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_schema", zDb);
2650326743
}
2650426744
for(i=0; i<ArraySize(aQuery); i++){
26505
- char *zSql = sqlite3_mprintf(aQuery[i].zSql, zSchemaTab);
26506
- int val = db_int(p->db, zSql);
26507
- sqlite3_free(zSql);
26745
+ int val = db_int(p->db, aQuery[i].zSql, zSchemaTab);
2650826746
sqlite3_fprintf(p->out, "%-20s %d\n", aQuery[i].zName, val);
2650926747
}
2651026748
sqlite3_free(zSchemaTab);
2651126749
sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion);
2651226750
sqlite3_fprintf(p->out, "%-20s %u\n", "data version", iDataVersion);
2651326751
return 0;
2651426752
}
2651526753
#endif /* SQLITE_SHELL_HAVE_RECOVER */
26754
+
26755
+/*
26756
+** Implementation of the ".dbtotxt" command.
26757
+**
26758
+** Return 1 on error, 2 to exit, and 0 otherwise.
26759
+*/
26760
+static int shell_dbtotxt_command(ShellState *p, int nArg, char **azArg){
26761
+ sqlite3_stmt *pStmt = 0;
26762
+ sqlite3_int64 nPage = 0;
26763
+ int pgSz = 0;
26764
+ const char *zTail;
26765
+ char *zName = 0;
26766
+ int rc, i, j;
26767
+ unsigned char bShow[256]; /* Characters ok to display */
26768
+
26769
+ UNUSED_PARAMETER(nArg);
26770
+ UNUSED_PARAMETER(azArg);
26771
+ memset(bShow, '.', sizeof(bShow));
26772
+ for(i=' '; i<='~'; i++){
26773
+ if( i!='{' && i!='}' && i!='"' && i!='\\' ) bShow[i] = (unsigned char)i;
26774
+ }
26775
+ rc = sqlite3_prepare_v2(p->db, "PRAGMA page_size", -1, &pStmt, 0);
26776
+ if( rc ) goto dbtotxt_error;
26777
+ rc = 0;
26778
+ if( sqlite3_step(pStmt)!=SQLITE_ROW ) goto dbtotxt_error;
26779
+ pgSz = sqlite3_column_int(pStmt, 0);
26780
+ sqlite3_finalize(pStmt);
26781
+ pStmt = 0;
26782
+ if( pgSz<512 || pgSz>65536 || (pgSz&(pgSz-1))!=0 ) goto dbtotxt_error;
26783
+ rc = sqlite3_prepare_v2(p->db, "PRAGMA page_count", -1, &pStmt, 0);
26784
+ if( rc ) goto dbtotxt_error;
26785
+ rc = 0;
26786
+ if( sqlite3_step(pStmt)!=SQLITE_ROW ) goto dbtotxt_error;
26787
+ nPage = sqlite3_column_int64(pStmt, 0);
26788
+ sqlite3_finalize(pStmt);
26789
+ pStmt = 0;
26790
+ if( nPage<1 ) goto dbtotxt_error;
26791
+ rc = sqlite3_prepare_v2(p->db, "PRAGMA databases", -1, &pStmt, 0);
26792
+ if( rc ) goto dbtotxt_error;
26793
+ if( sqlite3_step(pStmt)!=SQLITE_ROW ){
26794
+ zTail = "unk.db";
26795
+ }else{
26796
+ const char *zFilename = (const char*)sqlite3_column_text(pStmt, 2);
26797
+ if( zFilename==0 || zFilename[0]==0 ) zFilename = "unk.db";
26798
+ zTail = strrchr(zFilename, '/');
26799
+#if defined(_WIN32)
26800
+ if( zTail==0 ) zTail = strrchr(zFilename, '\\');
26801
+#endif
26802
+ }
26803
+ zName = strdup(zTail);
26804
+ shell_check_oom(zName);
26805
+ sqlite3_fprintf(p->out, "| size %lld pagesize %d filename %s\n",
26806
+ nPage*pgSz, pgSz, zName);
26807
+ sqlite3_finalize(pStmt);
26808
+ pStmt = 0;
26809
+ rc = sqlite3_prepare_v2(p->db,
26810
+ "SELECT pgno, data FROM sqlite_dbpage ORDER BY pgno", -1, &pStmt, 0);
26811
+ if( rc ) goto dbtotxt_error;
26812
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
26813
+ sqlite3_int64 pgno = sqlite3_column_int64(pStmt, 0);
26814
+ const u8 *aData = sqlite3_column_blob(pStmt, 1);
26815
+ int seenPageLabel = 0;
26816
+ for(i=0; i<pgSz; i+=16){
26817
+ const u8 *aLine = aData+i;
26818
+ for(j=0; j<16 && aLine[j]==0; j++){}
26819
+ if( j==16 ) continue;
26820
+ if( !seenPageLabel ){
26821
+ sqlite3_fprintf(p->out, "| page %lld offset %lld\n", pgno, pgno*pgSz);
26822
+ seenPageLabel = 1;
26823
+ }
26824
+ sqlite3_fprintf(p->out, "| %5d:", i);
26825
+ for(j=0; j<16; j++) sqlite3_fprintf(p->out, " %02x", aLine[j]);
26826
+ sqlite3_fprintf(p->out, " ");
26827
+ for(j=0; j<16; j++){
26828
+ unsigned char c = (unsigned char)aLine[j];
26829
+ sqlite3_fprintf(p->out, "%c", bShow[c]);
26830
+ }
26831
+ sqlite3_fprintf(p->out, "\n");
26832
+ }
26833
+ }
26834
+ sqlite3_finalize(pStmt);
26835
+ sqlite3_fprintf(p->out, "| end %s\n", zName);
26836
+ free(zName);
26837
+ return 0;
26838
+
26839
+dbtotxt_error:
26840
+ if( rc ){
26841
+ sqlite3_fprintf(stderr, "ERROR: %s\n", sqlite3_errmsg(p->db));
26842
+ }
26843
+ sqlite3_finalize(pStmt);
26844
+ free(zName);
26845
+ return 1;
26846
+}
2651626847
2651726848
/*
2651826849
** Print the given string as an error message.
2651926850
*/
2652026851
static void shellEmitError(const char *zErr){
@@ -28067,12 +28398,12 @@
2806728398
}else if( *pDb==0 ){
2806828399
return 0;
2806928400
}else{
2807028401
/* Formulate the columns spec, close the DB, zero *pDb. */
2807128402
char *zColsSpec = 0;
28072
- int hasDupes = db_int(*pDb, zHasDupes);
28073
- int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
28403
+ int hasDupes = db_int(*pDb, "%s", zHasDupes);
28404
+ int nDigits = (hasDupes)? db_int(*pDb, "%s", zColDigits) : 0;
2807428405
if( hasDupes ){
2807528406
#ifdef SHELL_COLUMN_RENAME_CLEAN
2807628407
rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
2807728408
rc_err_oom_die(rc);
2807828409
#endif
@@ -28083,11 +28414,11 @@
2808328414
sqlite3_bind_int(pStmt, 1, nDigits);
2808428415
rc = sqlite3_step(pStmt);
2808528416
sqlite3_finalize(pStmt);
2808628417
if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM);
2808728418
}
28088
- assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */
28419
+ assert(db_int(*pDb, "%s", zHasDupes)==0); /* Consider: remove this */
2808928420
rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
2809028421
rc_err_oom_die(rc);
2809128422
rc = sqlite3_step(pStmt);
2809228423
if( rc==SQLITE_ROW ){
2809328424
zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
@@ -28688,10 +29019,14 @@
2868829019
}else{
2868929020
eputz("Usage: .echo on|off\n");
2869029021
rc = 1;
2869129022
}
2869229023
}else
29024
+
29025
+ if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbtotxt", n)==0 ){
29026
+ rc = shell_dbtotxt_command(p, nArg, azArg);
29027
+ }else
2869329028
2869429029
if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){
2869529030
if( nArg==2 ){
2869629031
p->autoEQPtest = 0;
2869729032
if( p->autoEQPtrace ){
@@ -29129,11 +29464,15 @@
2912929464
/* Below, resources must be freed before exit. */
2913029465
while( (nSkip--)>0 ){
2913129466
while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
2913229467
}
2913329468
import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */
29134
- if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) ){
29469
+ if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0)
29470
+ && 0==db_int(p->db, "SELECT count(*) FROM \"%w\".sqlite_schema"
29471
+ " WHERE name=%Q AND type='view'",
29472
+ zSchema ? zSchema : "main", zTable)
29473
+ ){
2913529474
/* Table does not exist. Create it. */
2913629475
sqlite3 *dbCols = 0;
2913729476
char *zRenames = 0;
2913829477
char *zColDefs;
2913929478
zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"",
@@ -30201,11 +30540,14 @@
3020130540
}
3020230541
close_db(pSrc);
3020330542
}else
3020430543
#endif /* !defined(SQLITE_SHELL_FIDDLE) */
3020530544
30206
- if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){
30545
+ if( c=='s' &&
30546
+ (cli_strncmp(azArg[0], "scanstats", n)==0 ||
30547
+ cli_strncmp(azArg[0], "scanstatus", n)==0)
30548
+ ){
3020730549
if( nArg==2 ){
3020830550
if( cli_strcmp(azArg[1], "vm")==0 ){
3020930551
p->scanstatsOn = 3;
3021030552
}else
3021130553
if( cli_strcmp(azArg[1], "est")==0 ){
@@ -31256,11 +31598,13 @@
3125631598
{ 0x10000000, 1, "OrderBySubq" },
3125731599
{ 0xffffffff, 0, "All" },
3125831600
};
3125931601
unsigned int curOpt;
3126031602
unsigned int newOpt;
31603
+ unsigned int m;
3126131604
int ii;
31605
+ int nOff;
3126231606
sqlite3_test_control(SQLITE_TESTCTRL_GETOPT, p->db, &curOpt);
3126331607
newOpt = curOpt;
3126431608
for(ii=2; ii<nArg; ii++){
3126531609
const char *z = azArg[ii];
3126631610
int useLabel = 0;
@@ -31297,28 +31641,32 @@
3129731641
}
3129831642
}
3129931643
}
3130031644
if( curOpt!=newOpt ){
3130131645
sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,p->db,newOpt);
31302
- }else if( nArg<3 ){
31303
- curOpt = ~newOpt;
31646
+ }
31647
+ for(ii=nOff=0, m=1; ii<32; ii++, m <<= 1){
31648
+ if( m & newOpt ) nOff++;
3130431649
}
31305
- if( newOpt==0 ){
31306
- sqlite3_fputs("+All\n", p->out);
31307
- }else if( newOpt==0xffffffff ){
31308
- sqlite3_fputs("-All\n", p->out);
31650
+ if( nOff<12 ){
31651
+ sqlite3_fputs("+All", p->out);
31652
+ for(ii=0; ii<ArraySize(aLabel); ii++){
31653
+ if( !aLabel[ii].bDsply ) continue;
31654
+ if( (newOpt & aLabel[ii].mask)!=0 ){
31655
+ sqlite3_fprintf(p->out, " -%s", aLabel[ii].zLabel);
31656
+ }
31657
+ }
3130931658
}else{
31310
- int jj;
31311
- for(jj=0; jj<ArraySize(aLabel); jj++){
31312
- unsigned int m = aLabel[jj].mask;
31313
- if( !aLabel[jj].bDsply ) continue;
31314
- if( (curOpt&m)!=(newOpt&m) ){
31315
- sqlite3_fprintf(p->out, "%c%s\n", (newOpt & m)==0 ? '+' : '-',
31316
- aLabel[jj].zLabel);
31659
+ sqlite3_fputs("-All", p->out);
31660
+ for(ii=0; ii<ArraySize(aLabel); ii++){
31661
+ if( !aLabel[ii].bDsply ) continue;
31662
+ if( (newOpt & aLabel[ii].mask)==0 ){
31663
+ sqlite3_fprintf(p->out, " +%s", aLabel[ii].zLabel);
3131731664
}
3131831665
}
3131931666
}
31667
+ sqlite3_fputs("\n", p->out);
3132031668
rc2 = isOk = 3;
3132131669
break;
3132231670
}
3132331671
3132431672
/* sqlite3_test_control(int, db, int) */
@@ -31652,73 +32000,10 @@
3165232000
}
3165332001
}
3165432002
}else
3165532003
#endif
3165632004
31657
-#if SQLITE_USER_AUTHENTICATION
31658
- if( c=='u' && cli_strncmp(azArg[0], "user", n)==0 ){
31659
- if( nArg<2 ){
31660
- eputz("Usage: .user SUBCOMMAND ...\n");
31661
- rc = 1;
31662
- goto meta_command_exit;
31663
- }
31664
- open_db(p, 0);
31665
- if( cli_strcmp(azArg[1],"login")==0 ){
31666
- if( nArg!=4 ){
31667
- eputz("Usage: .user login USER PASSWORD\n");
31668
- rc = 1;
31669
- goto meta_command_exit;
31670
- }
31671
- rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3],
31672
- strlen30(azArg[3]));
31673
- if( rc ){
31674
- sqlite3_fprintf(stderr,"Authentication failed for user %s\n", azArg[2]);
31675
- rc = 1;
31676
- }
31677
- }else if( cli_strcmp(azArg[1],"add")==0 ){
31678
- if( nArg!=5 ){
31679
- eputz("Usage: .user add USER PASSWORD ISADMIN\n");
31680
- rc = 1;
31681
- goto meta_command_exit;
31682
- }
31683
- rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
31684
- booleanValue(azArg[4]));
31685
- if( rc ){
31686
- sqlite3_fprintf(stderr,"User-Add failed: %d\n", rc);
31687
- rc = 1;
31688
- }
31689
- }else if( cli_strcmp(azArg[1],"edit")==0 ){
31690
- if( nArg!=5 ){
31691
- eputz("Usage: .user edit USER PASSWORD ISADMIN\n");
31692
- rc = 1;
31693
- goto meta_command_exit;
31694
- }
31695
- rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
31696
- booleanValue(azArg[4]));
31697
- if( rc ){
31698
- sqlite3_fprintf(stderr,"User-Edit failed: %d\n", rc);
31699
- rc = 1;
31700
- }
31701
- }else if( cli_strcmp(azArg[1],"delete")==0 ){
31702
- if( nArg!=3 ){
31703
- eputz("Usage: .user delete USER\n");
31704
- rc = 1;
31705
- goto meta_command_exit;
31706
- }
31707
- rc = sqlite3_user_delete(p->db, azArg[2]);
31708
- if( rc ){
31709
- sqlite3_fprintf(stderr,"User-Delete failed: %d\n", rc);
31710
- rc = 1;
31711
- }
31712
- }else{
31713
- eputz("Usage: .user login|add|edit|delete ...\n");
31714
- rc = 1;
31715
- goto meta_command_exit;
31716
- }
31717
- }else
31718
-#endif /* SQLITE_USER_AUTHENTICATION */
31719
-
3172032005
if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){
3172132006
char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit";
3172232007
sqlite3_fprintf(p->out, "SQLite %s %s\n" /*extra-version-info*/,
3172332008
sqlite3_libversion(), sqlite3_sourceid());
3172432009
#if SQLITE_HAVE_ZLIB
@@ -31838,11 +32123,10 @@
3183832123
SCAN_TRACKER_REFTYPE pst){
3183932124
char cin;
3184032125
char cWait = (char)qss; /* intentional narrowing loss */
3184132126
if( cWait==0 ){
3184232127
PlainScan:
31843
- assert( cWait==0 );
3184432128
while( (cin = *zLine++)!=0 ){
3184532129
if( IsSpace(cin) )
3184632130
continue;
3184732131
switch (cin){
3184832132
case '-':
@@ -31890,11 +32174,10 @@
3189032174
switch( cWait ){
3189132175
case '*':
3189232176
if( *zLine != '/' )
3189332177
continue;
3189432178
++zLine;
31895
- cWait = 0;
3189632179
CONTINUE_PROMPT_AWAITC(pst, 0);
3189732180
qss = QSS_SETV(qss, 0);
3189832181
goto PlainScan;
3189932182
case '`': case '\'': case '"':
3190032183
if(*zLine==cWait){
@@ -31902,11 +32185,10 @@
3190232185
++zLine;
3190332186
continue;
3190432187
}
3190532188
deliberate_fall_through;
3190632189
case ']':
31907
- cWait = 0;
3190832190
CONTINUE_PROMPT_AWAITC(pst, 0);
3190932191
qss = QSS_SETV(qss, 0);
3191032192
goto PlainScan;
3191132193
default: assert(0);
3191232194
}
@@ -32090,11 +32372,14 @@
3209032372
if( doAutoDetectRestore(p, zSql) ) return 1;
3209132373
return 0;
3209232374
}
3209332375
3209432376
static void echo_group_input(ShellState *p, const char *zDo){
32095
- if( ShellHasFlag(p, SHFLG_Echo) ) sqlite3_fprintf(p->out, "%s\n", zDo);
32377
+ if( ShellHasFlag(p, SHFLG_Echo) ){
32378
+ sqlite3_fprintf(p->out, "%s\n", zDo);
32379
+ fflush(p->out);
32380
+ }
3209632381
}
3209732382
3209832383
#ifdef SQLITE_SHELL_FIDDLE
3209932384
/*
3210032385
** Alternate one_input_line() impl for wasm mode. This is not in the primary
@@ -32550,10 +32835,19 @@
3255032835
}
3255132836
3255232837
static void sayAbnormalExit(void){
3255332838
if( seenInterrupt ) eputz("Program interrupted.\n");
3255432839
}
32840
+
32841
+/* Routine to output from vfstrace
32842
+*/
32843
+static int vfstraceOut(const char *z, void *pArg){
32844
+ ShellState *p = (ShellState*)pArg;
32845
+ sqlite3_fputs(z, p->out);
32846
+ fflush(p->out);
32847
+ return 1;
32848
+}
3255532849
3255632850
#ifndef SQLITE_SHELL_IS_UTF8
3255732851
# if (defined(_WIN32) || defined(WIN32)) \
3255832852
&& (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__)))
3255932853
# define SQLITE_SHELL_IS_UTF8 (0)
@@ -32787,12 +33081,10 @@
3278733081
case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break;
3278833082
case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break;
3278933083
default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break;
3279033084
}
3279133085
}else if( cli_strcmp(z,"-vfstrace")==0 ){
32792
- vfstrace_register("trace",0,(int(*)(const char*,void*))sqlite3_fputs,
32793
- stderr,1);
3279433086
bEnableVfstrace = 1;
3279533087
#ifdef SQLITE_ENABLE_MULTIPLEX
3279633088
}else if( cli_strcmp(z,"-multiplex")==0 ){
3279733089
extern int sqlite3_multiplex_initialize(const char*,int);
3279833090
sqlite3_multiplex_initialize(0, 1);
@@ -32885,10 +33177,13 @@
3288533177
"%s: Error: no database filename specified\n", Argv0);
3288633178
return 1;
3288733179
#endif
3288833180
}
3288933181
data.out = stdout;
33182
+ if( bEnableVfstrace ){
33183
+ vfstrace_register("trace",0,vfstraceOut, &data, 1);
33184
+ }
3289033185
#ifndef SQLITE_SHELL_FIDDLE
3289133186
sqlite3_appendvfs_init(0,0,0);
3289233187
#endif
3289333188
3289433189
/* Go ahead and open the database file if it already exists. If the
@@ -33129,19 +33424,14 @@
3312933424
*/
3313033425
if( stdin_is_interactive ){
3313133426
char *zHome;
3313233427
char *zHistory;
3313333428
int nHistory;
33134
-#if CIO_WIN_WC_XLATE
33135
-# define SHELL_CIO_CHAR_SET (stdout_is_console? " (UTF-16 console I/O)" : "")
33136
-#else
33137
-# define SHELL_CIO_CHAR_SET ""
33138
-#endif
3313933429
sqlite3_fprintf(stdout,
33140
- "SQLite version %s %.19s%s\n" /*extra-version-info*/
33430
+ "SQLite version %s %.19s\n" /*extra-version-info*/
3314133431
"Enter \".help\" for usage hints.\n",
33142
- sqlite3_libversion(), sqlite3_sourceid(), SHELL_CIO_CHAR_SET);
33432
+ sqlite3_libversion(), sqlite3_sourceid());
3314333433
if( warnInmemoryDb ){
3314433434
sputz(stdout, "Connected to a ");
3314533435
printBold("transient in-memory database");
3314633436
sputz(stdout, ".\nUse \".open FILENAME\" to reopen on a"
3314733437
" persistent database.\n");
@@ -33154,13 +33444,15 @@
3315433444
if( (zHistory = malloc(nHistory))!=0 ){
3315533445
sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
3315633446
}
3315733447
}
3315833448
if( zHistory ){ shell_read_history(zHistory); }
33159
-#if HAVE_READLINE || HAVE_EDITLINE
33449
+#if (HAVE_READLINE || HAVE_EDITLINE) && !defined(SQLITE_OMIT_READLINE_COMPLETION)
3316033450
rl_attempted_completion_function = readline_completion;
33161
-#elif HAVE_LINENOISE
33451
+#elif HAVE_LINENOISE==1
33452
+ linenoiseSetCompletionCallback(linenoise_completion);
33453
+#elif HAVE_LINENOISE==2
3316233454
linenoiseSetCompletionCallback(linenoise_completion, NULL);
3316333455
#endif
3316433456
data.in = 0;
3316533457
rc = process_input(&data);
3316633458
if( zHistory ){
3316733459
--- extsrc/shell.c
+++ extsrc/shell.c
@@ -120,13 +120,10 @@
120 #include <math.h>
121 #include "sqlite3.h"
122 typedef sqlite3_int64 i64;
123 typedef sqlite3_uint64 u64;
124 typedef unsigned char u8;
125 #if SQLITE_USER_AUTHENTICATION
126 # include "sqlite3userauth.h"
127 #endif
128 #include <ctype.h>
129 #include <stdarg.h>
130
131 #if !defined(_WIN32) && !defined(WIN32)
132 # include <signal.h>
@@ -408,21 +405,21 @@
408 wchar_t *b1, *b2;
409 int sz1, sz2;
410
411 sz1 = (int)strlen(zFilename);
412 sz2 = (int)strlen(zMode);
413 b1 = malloc( (sz1+1)*sizeof(b1[0]) );
414 b2 = malloc( (sz2+1)*sizeof(b1[0]) );
415 if( b1 && b2 ){
416 sz1 = MultiByteToWideChar(CP_UTF8, 0, zFilename, sz1, b1, sz1);
417 b1[sz1] = 0;
418 sz2 = MultiByteToWideChar(CP_UTF8, 0, zMode, sz2, b2, sz2);
419 b2[sz2] = 0;
420 fp = _wfopen(b1, b2);
421 }
422 free(b1);
423 free(b2);
424 simBinaryOther = 0;
425 return fp;
426 }
427
428
@@ -434,21 +431,21 @@
434 wchar_t *b1, *b2;
435 int sz1, sz2;
436
437 sz1 = (int)strlen(zCommand);
438 sz2 = (int)strlen(zMode);
439 b1 = malloc( (sz1+1)*sizeof(b1[0]) );
440 b2 = malloc( (sz2+1)*sizeof(b1[0]) );
441 if( b1 && b2 ){
442 sz1 = MultiByteToWideChar(CP_UTF8, 0, zCommand, sz1, b1, sz1);
443 b1[sz1] = 0;
444 sz2 = MultiByteToWideChar(CP_UTF8, 0, zMode, sz2, b2, sz2);
445 b2[sz2] = 0;
446 fp = _wpopen(b1, b2);
447 }
448 free(b1);
449 free(b2);
450 return fp;
451 }
452
453 /*
454 ** Work-alike for fgets() from the standard C library.
@@ -458,16 +455,26 @@
458 /* When reading from the command-prompt in Windows, it is necessary
459 ** to use _O_WTEXT input mode to read UTF-16 characters, then translate
460 ** that into UTF-8. Otherwise, non-ASCII characters all get translated
461 ** into '?'.
462 */
463 wchar_t *b1 = malloc( sz*sizeof(wchar_t) );
464 if( b1==0 ) return 0;
465 _setmode(_fileno(in), IsConsole(in) ? _O_WTEXT : _O_U8TEXT);
466 if( fgetws(b1, sz/4, in)==0 ){
467 sqlite3_free(b1);
468 return 0;
 
 
 
 
 
 
 
 
 
 
469 }
470 WideCharToMultiByte(CP_UTF8, 0, b1, -1, buf, sz, 0, 0);
471 sqlite3_free(b1);
472 return buf;
473 }else{
@@ -519,24 +526,37 @@
519 if( !UseWtextForOutput(out) ){
520 /* Writing to a file or other destination, just write bytes without
521 ** any translation. */
522 return fputs(z, out);
523 }else{
524 /* When writing to the command-prompt in Windows, it is necessary
525 ** to use O_U8TEXT to render Unicode U+0080 and greater. Go ahead
526 ** use O_U8TEXT for everything in text mode.
527 */
528 int sz = (int)strlen(z);
529 wchar_t *b1 = malloc( (sz+1)*sizeof(wchar_t) );
530 if( b1==0 ) return 0;
531 sz = MultiByteToWideChar(CP_UTF8, 0, z, sz, b1, sz);
532 b1[sz] = 0;
533 _setmode(_fileno(out), _O_U8TEXT);
534 if( UseBinaryWText(out) ){
535 piecemealOutput(b1, sz, out);
536 }else{
537 fputws(b1, out);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
538 }
539 sqlite3_free(b1);
540 return 0;
541 }
542 }
@@ -2347,11 +2367,11 @@
2347 **
2348 ******************************************************************************
2349 **
2350 ** This SQLite extension implements functions that compute SHA3 hashes
2351 ** in the way described by the (U.S.) NIST FIPS 202 SHA-3 Standard.
2352 ** Two SQL functions are implemented:
2353 **
2354 ** sha3(X,SIZE)
2355 ** sha3_agg(Y,SIZE)
2356 ** sha3_query(Z,SIZE)
2357 **
@@ -5070,14 +5090,14 @@
5070 char **pzErrMsg,
5071 const sqlite3_api_routines *pApi
5072 ){
5073 int rc = SQLITE_OK;
5074 unsigned int i;
5075 #if defined(SQLITE3_H) || defined(SQLITE_STATIC_PERCENTILE)
5076 (void)pApi; /* Unused parameter */
5077 #else
5078 SQLITE_EXTENSION_INIT2(pApi);
 
 
5079 #endif
5080 (void)pzErrMsg; /* Unused parameter */
5081 for(i=0; i<sizeof(aPercentFunc)/sizeof(aPercentFunc[0]); i++){
5082 rc = sqlite3_create_window_function(db,
5083 aPercentFunc[i].zName,
@@ -6831,35 +6851,41 @@
6831 idxNum |= 0x40;
6832 }
6833 continue;
6834 }
6835 if( pConstraint->iColumn<SERIES_COLUMN_START ){
6836 if( pConstraint->iColumn==SERIES_COLUMN_VALUE ){
6837 switch( op ){
6838 case SQLITE_INDEX_CONSTRAINT_EQ:
6839 case SQLITE_INDEX_CONSTRAINT_IS: {
6840 idxNum |= 0x0080;
6841 idxNum &= ~0x3300;
6842 aIdx[5] = i;
6843 aIdx[6] = -1;
 
6844 bStartSeen = 1;
 
6845 break;
6846 }
6847 case SQLITE_INDEX_CONSTRAINT_GE: {
6848 if( idxNum & 0x0080 ) break;
6849 idxNum |= 0x0100;
6850 idxNum &= ~0x0200;
6851 aIdx[5] = i;
 
6852 bStartSeen = 1;
 
6853 break;
6854 }
6855 case SQLITE_INDEX_CONSTRAINT_GT: {
6856 if( idxNum & 0x0080 ) break;
6857 idxNum |= 0x0200;
6858 idxNum &= ~0x0100;
6859 aIdx[5] = i;
 
6860 bStartSeen = 1;
 
6861 break;
6862 }
6863 case SQLITE_INDEX_CONSTRAINT_LE: {
6864 if( idxNum & 0x0080 ) break;
6865 idxNum |= 0x1000;
@@ -14167,11 +14193,11 @@
14167 /* A view. Or a trigger on a view. */
14168 if( zSql ) rc = expertSchemaSql(p->dbv, zSql, pzErrmsg);
14169 }else{
14170 IdxTable *pTab;
14171 rc = idxGetTableInfo(p->db, zName, &pTab, pzErrmsg);
14172 if( rc==SQLITE_OK ){
14173 int i;
14174 char *zInner = 0;
14175 char *zOuter = 0;
14176 pTab->pNext = p->pTable;
14177 p->pTable = pTab;
@@ -16235,11 +16261,31 @@
16235 **
16236 ** Similar compiler commands will work on different systems. The key
16237 ** invariants are (1) you must have -DSQLITE_ENABLE_VFSTRACE so that
16238 ** the shell.c source file will know to include the -vfstrace command-line
16239 ** option and (2) you must compile and link the three source files
16240 ** shell,c, test_vfstrace.c, and sqlite3.c.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16241 */
16242 #include <stdlib.h>
16243 #include <string.h>
16244 /* #include "sqlite3.h" */
16245
@@ -16249,10 +16295,12 @@
16249 */
16250 typedef struct vfstrace_info vfstrace_info;
16251 struct vfstrace_info {
16252 sqlite3_vfs *pRootVfs; /* The underlying real VFS */
16253 int (*xOut)(const char*, void*); /* Send output here */
 
 
16254 void *pOutArg; /* First argument to xOut */
16255 const char *zVfsName; /* Name of this trace-VFS */
16256 sqlite3_vfs *pTraceVfs; /* Pointer back to the trace VFS */
16257 };
16258
@@ -16265,10 +16313,42 @@
16265 vfstrace_info *pInfo; /* The trace-VFS to which this file belongs */
16266 const char *zFName; /* Base name of the file */
16267 sqlite3_file *pReal; /* The real underlying file */
16268 };
16269
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16270 /*
16271 ** Method declarations for vfstrace_file.
16272 */
16273 static int vfstraceClose(sqlite3_file*);
16274 static int vfstraceRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
@@ -16329,15 +16409,17 @@
16329 const char *zFormat,
16330 ...
16331 ){
16332 va_list ap;
16333 char *zMsg;
16334 va_start(ap, zFormat);
16335 zMsg = sqlite3_vmprintf(zFormat, ap);
16336 va_end(ap);
16337 pInfo->xOut(zMsg, pInfo->pOutArg);
16338 sqlite3_free(zMsg);
 
 
16339 }
16340
16341 /*
16342 ** Try to convert an error code into a symbolic name for that error code.
16343 */
@@ -16431,18 +16513,26 @@
16431 int i = *pI;
16432 while( zAppend[0] ){ z[i++] = *(zAppend++); }
16433 z[i] = 0;
16434 *pI = i;
16435 }
 
 
 
 
 
 
 
16436
16437 /*
16438 ** Close an vfstrace-file.
16439 */
16440 static int vfstraceClose(sqlite3_file *pFile){
16441 vfstrace_file *p = (vfstrace_file *)pFile;
16442 vfstrace_info *pInfo = p->pInfo;
16443 int rc;
 
16444 vfstrace_printf(pInfo, "%s.xClose(%s)", pInfo->zVfsName, p->zFName);
16445 rc = p->pReal->pMethods->xClose(p->pReal);
16446 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16447 if( rc==SQLITE_OK ){
16448 sqlite3_free((void*)p->base.pMethods);
@@ -16461,10 +16551,11 @@
16461 sqlite_int64 iOfst
16462 ){
16463 vfstrace_file *p = (vfstrace_file *)pFile;
16464 vfstrace_info *pInfo = p->pInfo;
16465 int rc;
 
16466 vfstrace_printf(pInfo, "%s.xRead(%s,n=%d,ofst=%lld)",
16467 pInfo->zVfsName, p->zFName, iAmt, iOfst);
16468 rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst);
16469 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16470 return rc;
@@ -16480,10 +16571,11 @@
16480 sqlite_int64 iOfst
16481 ){
16482 vfstrace_file *p = (vfstrace_file *)pFile;
16483 vfstrace_info *pInfo = p->pInfo;
16484 int rc;
 
16485 vfstrace_printf(pInfo, "%s.xWrite(%s,n=%d,ofst=%lld)",
16486 pInfo->zVfsName, p->zFName, iAmt, iOfst);
16487 rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst);
16488 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16489 return rc;
@@ -16494,10 +16586,11 @@
16494 */
16495 static int vfstraceTruncate(sqlite3_file *pFile, sqlite_int64 size){
16496 vfstrace_file *p = (vfstrace_file *)pFile;
16497 vfstrace_info *pInfo = p->pInfo;
16498 int rc;
 
16499 vfstrace_printf(pInfo, "%s.xTruncate(%s,%lld)", pInfo->zVfsName, p->zFName,
16500 size);
16501 rc = p->pReal->pMethods->xTruncate(p->pReal, size);
16502 vfstrace_printf(pInfo, " -> %d\n", rc);
16503 return rc;
@@ -16518,10 +16611,11 @@
16518 else if( flags & SQLITE_SYNC_NORMAL ) strappend(zBuf, &i, "|NORMAL");
16519 if( flags & SQLITE_SYNC_DATAONLY ) strappend(zBuf, &i, "|DATAONLY");
16520 if( flags & ~(SQLITE_SYNC_FULL|SQLITE_SYNC_DATAONLY) ){
16521 sqlite3_snprintf(sizeof(zBuf)-i, &zBuf[i], "|0x%x", flags);
16522 }
 
16523 vfstrace_printf(pInfo, "%s.xSync(%s,%s)", pInfo->zVfsName, p->zFName,
16524 &zBuf[1]);
16525 rc = p->pReal->pMethods->xSync(p->pReal, flags);
16526 vfstrace_printf(pInfo, " -> %d\n", rc);
16527 return rc;
@@ -16532,10 +16626,11 @@
16532 */
16533 static int vfstraceFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
16534 vfstrace_file *p = (vfstrace_file *)pFile;
16535 vfstrace_info *pInfo = p->pInfo;
16536 int rc;
 
16537 vfstrace_printf(pInfo, "%s.xFileSize(%s)", pInfo->zVfsName, p->zFName);
16538 rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
16539 vfstrace_print_errcode(pInfo, " -> %s,", rc);
16540 vfstrace_printf(pInfo, " size=%lld\n", *pSize);
16541 return rc;
@@ -16560,10 +16655,11 @@
16560 */
16561 static int vfstraceLock(sqlite3_file *pFile, int eLock){
16562 vfstrace_file *p = (vfstrace_file *)pFile;
16563 vfstrace_info *pInfo = p->pInfo;
16564 int rc;
 
16565 vfstrace_printf(pInfo, "%s.xLock(%s,%s)", pInfo->zVfsName, p->zFName,
16566 lockName(eLock));
16567 rc = p->pReal->pMethods->xLock(p->pReal, eLock);
16568 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16569 return rc;
@@ -16574,10 +16670,11 @@
16574 */
16575 static int vfstraceUnlock(sqlite3_file *pFile, int eLock){
16576 vfstrace_file *p = (vfstrace_file *)pFile;
16577 vfstrace_info *pInfo = p->pInfo;
16578 int rc;
 
16579 vfstrace_printf(pInfo, "%s.xUnlock(%s,%s)", pInfo->zVfsName, p->zFName,
16580 lockName(eLock));
16581 rc = p->pReal->pMethods->xUnlock(p->pReal, eLock);
16582 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16583 return rc;
@@ -16588,10 +16685,11 @@
16588 */
16589 static int vfstraceCheckReservedLock(sqlite3_file *pFile, int *pResOut){
16590 vfstrace_file *p = (vfstrace_file *)pFile;
16591 vfstrace_info *pInfo = p->pInfo;
16592 int rc;
 
16593 vfstrace_printf(pInfo, "%s.xCheckReservedLock(%s,%d)",
16594 pInfo->zVfsName, p->zFName);
16595 rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut);
16596 vfstrace_print_errcode(pInfo, " -> %s", rc);
16597 vfstrace_printf(pInfo, ", out=%d\n", *pResOut);
@@ -16607,10 +16705,11 @@
16607 int rc;
16608 char zBuf[100];
16609 char zBuf2[100];
16610 char *zOp;
16611 char *zRVal = 0;
 
16612 switch( op ){
16613 case SQLITE_FCNTL_LOCKSTATE: zOp = "LOCKSTATE"; break;
16614 case SQLITE_GET_LOCKPROXYFILE: zOp = "GET_LOCKPROXYFILE"; break;
16615 case SQLITE_SET_LOCKPROXYFILE: zOp = "SET_LOCKPROXYFILE"; break;
16616 case SQLITE_LAST_ERRNO: zOp = "LAST_ERRNO"; break;
@@ -16635,10 +16734,83 @@
16635 case SQLITE_FCNTL_OVERWRITE: zOp = "OVERWRITE"; break;
16636 case SQLITE_FCNTL_VFSNAME: zOp = "VFSNAME"; break;
16637 case SQLITE_FCNTL_POWERSAFE_OVERWRITE: zOp = "POWERSAFE_OVERWRITE"; break;
16638 case SQLITE_FCNTL_PRAGMA: {
16639 const char *const* a = (const char*const*)pArg;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16640 sqlite3_snprintf(sizeof(zBuf), zBuf, "PRAGMA,[%s,%s]",a[1],a[2]);
16641 zOp = zBuf;
16642 break;
16643 }
16644 case SQLITE_FCNTL_BUSYHANDLER: zOp = "BUSYHANDLER"; break;
@@ -16730,10 +16902,11 @@
16730 */
16731 static int vfstraceSectorSize(sqlite3_file *pFile){
16732 vfstrace_file *p = (vfstrace_file *)pFile;
16733 vfstrace_info *pInfo = p->pInfo;
16734 int rc;
 
16735 vfstrace_printf(pInfo, "%s.xSectorSize(%s)", pInfo->zVfsName, p->zFName);
16736 rc = p->pReal->pMethods->xSectorSize(p->pReal);
16737 vfstrace_printf(pInfo, " -> %d\n", rc);
16738 return rc;
16739 }
@@ -16743,10 +16916,11 @@
16743 */
16744 static int vfstraceDeviceCharacteristics(sqlite3_file *pFile){
16745 vfstrace_file *p = (vfstrace_file *)pFile;
16746 vfstrace_info *pInfo = p->pInfo;
16747 int rc;
 
16748 vfstrace_printf(pInfo, "%s.xDeviceCharacteristics(%s)",
16749 pInfo->zVfsName, p->zFName);
16750 rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal);
16751 vfstrace_printf(pInfo, " -> 0x%08x\n", rc);
16752 return rc;
@@ -16754,25 +16928,43 @@
16754
16755 /*
16756 ** Shared-memory operations.
16757 */
16758 static int vfstraceShmLock(sqlite3_file *pFile, int ofst, int n, int flags){
 
 
 
 
 
 
 
 
 
 
16759 vfstrace_file *p = (vfstrace_file *)pFile;
16760 vfstrace_info *pInfo = p->pInfo;
16761 int rc;
16762 char zLck[100];
16763 int i = 0;
 
16764 memcpy(zLck, "|0", 3);
16765 if( flags & SQLITE_SHM_UNLOCK ) strappend(zLck, &i, "|UNLOCK");
16766 if( flags & SQLITE_SHM_LOCK ) strappend(zLck, &i, "|LOCK");
16767 if( flags & SQLITE_SHM_SHARED ) strappend(zLck, &i, "|SHARED");
16768 if( flags & SQLITE_SHM_EXCLUSIVE ) strappend(zLck, &i, "|EXCLUSIVE");
16769 if( flags & ~(0xf) ){
16770 sqlite3_snprintf(sizeof(zLck)-i, &zLck[i], "|0x%x", flags);
16771 }
16772 vfstrace_printf(pInfo, "%s.xShmLock(%s,ofst=%d,n=%d,%s)",
16773 pInfo->zVfsName, p->zFName, ofst, n, &zLck[1]);
 
 
 
 
 
 
 
16774 rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags);
16775 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16776 return rc;
16777 }
16778 static int vfstraceShmMap(
@@ -16783,26 +16975,29 @@
16783 void volatile **pp
16784 ){
16785 vfstrace_file *p = (vfstrace_file *)pFile;
16786 vfstrace_info *pInfo = p->pInfo;
16787 int rc;
 
16788 vfstrace_printf(pInfo, "%s.xShmMap(%s,iRegion=%d,szRegion=%d,isWrite=%d,*)",
16789 pInfo->zVfsName, p->zFName, iRegion, szRegion, isWrite);
16790 rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp);
16791 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16792 return rc;
16793 }
16794 static void vfstraceShmBarrier(sqlite3_file *pFile){
16795 vfstrace_file *p = (vfstrace_file *)pFile;
16796 vfstrace_info *pInfo = p->pInfo;
 
16797 vfstrace_printf(pInfo, "%s.xShmBarrier(%s)\n", pInfo->zVfsName, p->zFName);
16798 p->pReal->pMethods->xShmBarrier(p->pReal);
16799 }
16800 static int vfstraceShmUnmap(sqlite3_file *pFile, int delFlag){
16801 vfstrace_file *p = (vfstrace_file *)pFile;
16802 vfstrace_info *pInfo = p->pInfo;
16803 int rc;
 
16804 vfstrace_printf(pInfo, "%s.xShmUnmap(%s,delFlag=%d)",
16805 pInfo->zVfsName, p->zFName, delFlag);
16806 rc = p->pReal->pMethods->xShmUnmap(p->pReal, delFlag);
16807 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16808 return rc;
@@ -16826,10 +17021,11 @@
16826 sqlite3_vfs *pRoot = pInfo->pRootVfs;
16827 p->pInfo = pInfo;
16828 p->zFName = zName ? fileTail(zName) : "<temp>";
16829 p->pReal = (sqlite3_file *)&p[1];
16830 rc = pRoot->xOpen(pRoot, zName, p->pReal, flags, pOutFlags);
 
16831 vfstrace_printf(pInfo, "%s.xOpen(%s,flags=0x%x)",
16832 pInfo->zVfsName, p->zFName, flags);
16833 if( p->pReal->pMethods ){
16834 sqlite3_io_methods *pNew = sqlite3_malloc( sizeof(*pNew) );
16835 const sqlite3_io_methods *pSub = p->pReal->pMethods;
@@ -16871,10 +17067,11 @@
16871 */
16872 static int vfstraceDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
16873 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
16874 sqlite3_vfs *pRoot = pInfo->pRootVfs;
16875 int rc;
 
16876 vfstrace_printf(pInfo, "%s.xDelete(\"%s\",%d)",
16877 pInfo->zVfsName, zPath, dirSync);
16878 rc = pRoot->xDelete(pRoot, zPath, dirSync);
16879 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16880 return rc;
@@ -16891,10 +17088,11 @@
16891 int *pResOut
16892 ){
16893 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
16894 sqlite3_vfs *pRoot = pInfo->pRootVfs;
16895 int rc;
 
16896 vfstrace_printf(pInfo, "%s.xAccess(\"%s\",%d)",
16897 pInfo->zVfsName, zPath, flags);
16898 rc = pRoot->xAccess(pRoot, zPath, flags, pResOut);
16899 vfstrace_print_errcode(pInfo, " -> %s", rc);
16900 vfstrace_printf(pInfo, ", out=%d\n", *pResOut);
@@ -16913,10 +17111,11 @@
16913 char *zOut
16914 ){
16915 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
16916 sqlite3_vfs *pRoot = pInfo->pRootVfs;
16917 int rc;
 
16918 vfstrace_printf(pInfo, "%s.xFullPathname(\"%s\")",
16919 pInfo->zVfsName, zPath);
16920 rc = pRoot->xFullPathname(pRoot, zPath, nOut, zOut);
16921 vfstrace_print_errcode(pInfo, " -> %s", rc);
16922 vfstrace_printf(pInfo, ", out=\"%.*s\"\n", nOut, zOut);
@@ -16927,10 +17126,11 @@
16927 ** Open the dynamic library located at zPath and return a handle.
16928 */
16929 static void *vfstraceDlOpen(sqlite3_vfs *pVfs, const char *zPath){
16930 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
16931 sqlite3_vfs *pRoot = pInfo->pRootVfs;
 
16932 vfstrace_printf(pInfo, "%s.xDlOpen(\"%s\")\n", pInfo->zVfsName, zPath);
16933 return pRoot->xDlOpen(pRoot, zPath);
16934 }
16935
16936 /*
@@ -16939,10 +17139,11 @@
16939 ** with dynamic libraries.
16940 */
16941 static void vfstraceDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
16942 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
16943 sqlite3_vfs *pRoot = pInfo->pRootVfs;
 
16944 vfstrace_printf(pInfo, "%s.xDlError(%d)", pInfo->zVfsName, nByte);
16945 pRoot->xDlError(pRoot, nByte, zErrMsg);
16946 vfstrace_printf(pInfo, " -> \"%s\"", zErrMsg);
16947 }
16948
@@ -16960,10 +17161,11 @@
16960 ** Close the dynamic library handle pHandle.
16961 */
16962 static void vfstraceDlClose(sqlite3_vfs *pVfs, void *pHandle){
16963 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
16964 sqlite3_vfs *pRoot = pInfo->pRootVfs;
 
16965 vfstrace_printf(pInfo, "%s.xDlOpen()\n", pInfo->zVfsName);
16966 pRoot->xDlClose(pRoot, pHandle);
16967 }
16968
16969 /*
@@ -16971,10 +17173,11 @@
16971 ** random data.
16972 */
16973 static int vfstraceRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
16974 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
16975 sqlite3_vfs *pRoot = pInfo->pRootVfs;
 
16976 vfstrace_printf(pInfo, "%s.xRandomness(%d)\n", pInfo->zVfsName, nByte);
16977 return pRoot->xRandomness(pRoot, nByte, zBufOut);
16978 }
16979
16980 /*
@@ -16982,34 +17185,52 @@
16982 ** actually slept.
16983 */
16984 static int vfstraceSleep(sqlite3_vfs *pVfs, int nMicro){
16985 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
16986 sqlite3_vfs *pRoot = pInfo->pRootVfs;
 
 
16987 return pRoot->xSleep(pRoot, nMicro);
16988 }
16989
16990 /*
16991 ** Return the current time as a Julian Day number in *pTimeOut.
16992 */
16993 static int vfstraceCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
16994 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
16995 sqlite3_vfs *pRoot = pInfo->pRootVfs;
16996 return pRoot->xCurrentTime(pRoot, pTimeOut);
 
 
 
 
 
16997 }
16998 static int vfstraceCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){
16999 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
17000 sqlite3_vfs *pRoot = pInfo->pRootVfs;
17001 return pRoot->xCurrentTimeInt64(pRoot, pTimeOut);
 
 
 
 
 
17002 }
17003
17004 /*
17005 ** Return th3 most recent error code and message
17006 */
17007 static int vfstraceGetLastError(sqlite3_vfs *pVfs, int iErr, char *zErr){
17008 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
17009 sqlite3_vfs *pRoot = pInfo->pRootVfs;
17010 return pRoot->xGetLastError(pRoot, iErr, zErr);
 
 
 
 
 
 
17011 }
17012
17013 /*
17014 ** Override system calls.
17015 */
@@ -17099,10 +17320,12 @@
17099 pInfo->pRootVfs = pRoot;
17100 pInfo->xOut = xOut;
17101 pInfo->pOutArg = pOutArg;
17102 pInfo->zVfsName = pNew->zName;
17103 pInfo->pTraceVfs = pNew;
 
 
17104 vfstrace_printf(pInfo, "%s.enabled_for(\"%s\")\n",
17105 pInfo->zVfsName, pRoot->zName);
17106 return sqlite3_vfs_register(pNew, makeDefault);
17107 }
17108
@@ -20228,10 +20451,12 @@
20228 apVal[iField] = sqlite3_value_dup( pVal );
20229 if( apVal[iField]==0 ){
20230 recoverError(p, SQLITE_NOMEM, 0);
20231 }
20232 p1->nVal = iField+1;
 
 
20233 }
20234 p1->iPrevCell = iCell;
20235 p1->iPrevPage = iPage;
20236 }
20237 }else{
@@ -21935,12 +22160,12 @@
21935 const char *zSkipValidUtf8(const char *z, int nAccept, long ccm){
21936 int ng = (nAccept<0)? -nAccept : 0;
21937 const char *pcLimit = (nAccept>=0)? z+nAccept : 0;
21938 assert(z!=0);
21939 while( (pcLimit)? (z<pcLimit) : (ng-- != 0) ){
21940 char c = *z;
21941 if( (c & 0x80) == 0 ){
21942 if( ccm != 0L && c < 0x20 && ((1L<<c) & ccm) != 0 ) return z;
21943 ++z; /* ASCII */
21944 }else if( (c & 0xC0) != 0xC0 ) return z; /* not a lead byte */
21945 else{
21946 const char *zt = z+1; /* Got lead byte, look at trail bytes.*/
@@ -22004,14 +22229,14 @@
22004 }
22005 sqlite3_fputs(zq, out);
22006 }
22007
22008 /*
22009 ** Output the given string as a quoted according to JSON quoting rules.
22010 */
22011 static void output_json_string(FILE *out, const char *z, i64 n){
22012 char c;
22013 static const char *zq = "\"";
22014 static long ctrlMask = ~0L;
22015 static const char *zDQBS = "\"\\";
22016 const char *pcLimit;
22017 char ace[3] = "\\?";
@@ -22027,11 +22252,11 @@
22027 if( pcEnd > z ){
22028 sqlite3_fprintf(out, "%.*s", (int)(pcEnd-z), z);
22029 z = pcEnd;
22030 }
22031 if( z >= pcLimit ) break;
22032 c = *(z++);
22033 switch( c ){
22034 case '"': case '\\':
22035 cbsSay = (char)c;
22036 break;
22037 case '\b': cbsSay = 'b'; break;
@@ -22042,12 +22267,12 @@
22042 default: cbsSay = 0; break;
22043 }
22044 if( cbsSay ){
22045 ace[1] = cbsSay;
22046 sqlite3_fputs(ace, out);
22047 }else if( c<=0x1f ){
22048 sqlite3_fprintf(out, "u%04x", c);
22049 }else{
22050 ace[1] = (char)c;
22051 sqlite3_fputs(ace+1, out);
22052 }
22053 }
@@ -24784,13 +25009,13 @@
24784 if( rc ){
24785 sqlite3_fprintf(p->out, "/****** ERROR: %s ******/\n", zErr);
24786 }else{
24787 rc = SQLITE_CORRUPT;
24788 }
24789 sqlite3_free(zErr);
24790 free(zQ2);
24791 }
 
24792 return rc;
24793 }
24794
24795 /*
24796 ** Text of help messages.
@@ -24849,10 +25074,11 @@
24849 ".databases List names and files of attached databases",
24850 ".dbconfig ?op? ?val? List or change sqlite3_db_config() options",
24851 #if SQLITE_SHELL_HAVE_RECOVER
24852 ".dbinfo ?DB? Show status information about the database",
24853 #endif
 
24854 ".dump ?OBJECTS? Render database content as SQL",
24855 " Options:",
24856 " --data-only Output only INSERT statements",
24857 " --newlines Allow unescaped newline characters in output",
24858 " --nosys Omit system tables (ex: \"sqlite_stat1\")",
@@ -25654,11 +25880,12 @@
25654 sqlite3_fprintf(stderr,
25655 "Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db));
25656 }
25657 }
25658
25659 #if HAVE_READLINE || HAVE_EDITLINE
 
25660 /*
25661 ** Readline completion callbacks
25662 */
25663 static char *readline_completion_generator(const char *text, int state){
25664 static sqlite3_stmt *pStmt = 0;
@@ -25692,19 +25919,26 @@
25692 #elif HAVE_LINENOISE
25693 /*
25694 ** Linenoise completion callback. Note that the 3rd argument is from
25695 ** the "msteveb" version of linenoise, not the "antirez" version.
25696 */
25697 static void linenoise_completion(const char *zLine, linenoiseCompletions *lc,
25698 void *pUserData){
 
 
 
 
 
25699 i64 nLine = strlen(zLine);
25700 i64 i, iStart;
25701 sqlite3_stmt *pStmt = 0;
25702 char *zSql;
25703 char zBuf[1000];
25704
 
25705 UNUSED_PARAMETER(pUserData);
 
25706 if( nLine>(i64)sizeof(zBuf)-30 ) return;
25707 if( zLine[0]=='.' || zLine[0]=='#') return;
25708 for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){}
25709 if( i==nLine-1 ) return;
25710 iStart = i+1;
@@ -26390,18 +26624,24 @@
26390 #endif
26391
26392 /*
26393 ** Run an SQL command and return the single integer result.
26394 */
26395 static int db_int(sqlite3 *db, const char *zSql){
26396 sqlite3_stmt *pStmt;
26397 int res = 0;
26398 sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
 
 
 
 
 
26399 if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){
26400 res = sqlite3_column_int(pStmt,0);
26401 }
26402 sqlite3_finalize(pStmt);
 
26403 return res;
26404 }
26405
26406 #if SQLITE_SHELL_HAVE_RECOVER
26407 /*
@@ -26500,21 +26740,112 @@
26500 zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_schema");
26501 }else{
26502 zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_schema", zDb);
26503 }
26504 for(i=0; i<ArraySize(aQuery); i++){
26505 char *zSql = sqlite3_mprintf(aQuery[i].zSql, zSchemaTab);
26506 int val = db_int(p->db, zSql);
26507 sqlite3_free(zSql);
26508 sqlite3_fprintf(p->out, "%-20s %d\n", aQuery[i].zName, val);
26509 }
26510 sqlite3_free(zSchemaTab);
26511 sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion);
26512 sqlite3_fprintf(p->out, "%-20s %u\n", "data version", iDataVersion);
26513 return 0;
26514 }
26515 #endif /* SQLITE_SHELL_HAVE_RECOVER */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26516
26517 /*
26518 ** Print the given string as an error message.
26519 */
26520 static void shellEmitError(const char *zErr){
@@ -28067,12 +28398,12 @@
28067 }else if( *pDb==0 ){
28068 return 0;
28069 }else{
28070 /* Formulate the columns spec, close the DB, zero *pDb. */
28071 char *zColsSpec = 0;
28072 int hasDupes = db_int(*pDb, zHasDupes);
28073 int nDigits = (hasDupes)? db_int(*pDb, zColDigits) : 0;
28074 if( hasDupes ){
28075 #ifdef SHELL_COLUMN_RENAME_CLEAN
28076 rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
28077 rc_err_oom_die(rc);
28078 #endif
@@ -28083,11 +28414,11 @@
28083 sqlite3_bind_int(pStmt, 1, nDigits);
28084 rc = sqlite3_step(pStmt);
28085 sqlite3_finalize(pStmt);
28086 if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM);
28087 }
28088 assert(db_int(*pDb, zHasDupes)==0); /* Consider: remove this */
28089 rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
28090 rc_err_oom_die(rc);
28091 rc = sqlite3_step(pStmt);
28092 if( rc==SQLITE_ROW ){
28093 zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
@@ -28688,10 +29019,14 @@
28688 }else{
28689 eputz("Usage: .echo on|off\n");
28690 rc = 1;
28691 }
28692 }else
 
 
 
 
28693
28694 if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){
28695 if( nArg==2 ){
28696 p->autoEQPtest = 0;
28697 if( p->autoEQPtrace ){
@@ -29129,11 +29464,15 @@
29129 /* Below, resources must be freed before exit. */
29130 while( (nSkip--)>0 ){
29131 while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
29132 }
29133 import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */
29134 if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0) ){
 
 
 
 
29135 /* Table does not exist. Create it. */
29136 sqlite3 *dbCols = 0;
29137 char *zRenames = 0;
29138 char *zColDefs;
29139 zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"",
@@ -30201,11 +30540,14 @@
30201 }
30202 close_db(pSrc);
30203 }else
30204 #endif /* !defined(SQLITE_SHELL_FIDDLE) */
30205
30206 if( c=='s' && cli_strncmp(azArg[0], "scanstats", n)==0 ){
 
 
 
30207 if( nArg==2 ){
30208 if( cli_strcmp(azArg[1], "vm")==0 ){
30209 p->scanstatsOn = 3;
30210 }else
30211 if( cli_strcmp(azArg[1], "est")==0 ){
@@ -31256,11 +31598,13 @@
31256 { 0x10000000, 1, "OrderBySubq" },
31257 { 0xffffffff, 0, "All" },
31258 };
31259 unsigned int curOpt;
31260 unsigned int newOpt;
 
31261 int ii;
 
31262 sqlite3_test_control(SQLITE_TESTCTRL_GETOPT, p->db, &curOpt);
31263 newOpt = curOpt;
31264 for(ii=2; ii<nArg; ii++){
31265 const char *z = azArg[ii];
31266 int useLabel = 0;
@@ -31297,28 +31641,32 @@
31297 }
31298 }
31299 }
31300 if( curOpt!=newOpt ){
31301 sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,p->db,newOpt);
31302 }else if( nArg<3 ){
31303 curOpt = ~newOpt;
 
31304 }
31305 if( newOpt==0 ){
31306 sqlite3_fputs("+All\n", p->out);
31307 }else if( newOpt==0xffffffff ){
31308 sqlite3_fputs("-All\n", p->out);
 
 
 
 
31309 }else{
31310 int jj;
31311 for(jj=0; jj<ArraySize(aLabel); jj++){
31312 unsigned int m = aLabel[jj].mask;
31313 if( !aLabel[jj].bDsply ) continue;
31314 if( (curOpt&m)!=(newOpt&m) ){
31315 sqlite3_fprintf(p->out, "%c%s\n", (newOpt & m)==0 ? '+' : '-',
31316 aLabel[jj].zLabel);
31317 }
31318 }
31319 }
 
31320 rc2 = isOk = 3;
31321 break;
31322 }
31323
31324 /* sqlite3_test_control(int, db, int) */
@@ -31652,73 +32000,10 @@
31652 }
31653 }
31654 }else
31655 #endif
31656
31657 #if SQLITE_USER_AUTHENTICATION
31658 if( c=='u' && cli_strncmp(azArg[0], "user", n)==0 ){
31659 if( nArg<2 ){
31660 eputz("Usage: .user SUBCOMMAND ...\n");
31661 rc = 1;
31662 goto meta_command_exit;
31663 }
31664 open_db(p, 0);
31665 if( cli_strcmp(azArg[1],"login")==0 ){
31666 if( nArg!=4 ){
31667 eputz("Usage: .user login USER PASSWORD\n");
31668 rc = 1;
31669 goto meta_command_exit;
31670 }
31671 rc = sqlite3_user_authenticate(p->db, azArg[2], azArg[3],
31672 strlen30(azArg[3]));
31673 if( rc ){
31674 sqlite3_fprintf(stderr,"Authentication failed for user %s\n", azArg[2]);
31675 rc = 1;
31676 }
31677 }else if( cli_strcmp(azArg[1],"add")==0 ){
31678 if( nArg!=5 ){
31679 eputz("Usage: .user add USER PASSWORD ISADMIN\n");
31680 rc = 1;
31681 goto meta_command_exit;
31682 }
31683 rc = sqlite3_user_add(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
31684 booleanValue(azArg[4]));
31685 if( rc ){
31686 sqlite3_fprintf(stderr,"User-Add failed: %d\n", rc);
31687 rc = 1;
31688 }
31689 }else if( cli_strcmp(azArg[1],"edit")==0 ){
31690 if( nArg!=5 ){
31691 eputz("Usage: .user edit USER PASSWORD ISADMIN\n");
31692 rc = 1;
31693 goto meta_command_exit;
31694 }
31695 rc = sqlite3_user_change(p->db, azArg[2], azArg[3], strlen30(azArg[3]),
31696 booleanValue(azArg[4]));
31697 if( rc ){
31698 sqlite3_fprintf(stderr,"User-Edit failed: %d\n", rc);
31699 rc = 1;
31700 }
31701 }else if( cli_strcmp(azArg[1],"delete")==0 ){
31702 if( nArg!=3 ){
31703 eputz("Usage: .user delete USER\n");
31704 rc = 1;
31705 goto meta_command_exit;
31706 }
31707 rc = sqlite3_user_delete(p->db, azArg[2]);
31708 if( rc ){
31709 sqlite3_fprintf(stderr,"User-Delete failed: %d\n", rc);
31710 rc = 1;
31711 }
31712 }else{
31713 eputz("Usage: .user login|add|edit|delete ...\n");
31714 rc = 1;
31715 goto meta_command_exit;
31716 }
31717 }else
31718 #endif /* SQLITE_USER_AUTHENTICATION */
31719
31720 if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){
31721 char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit";
31722 sqlite3_fprintf(p->out, "SQLite %s %s\n" /*extra-version-info*/,
31723 sqlite3_libversion(), sqlite3_sourceid());
31724 #if SQLITE_HAVE_ZLIB
@@ -31838,11 +32123,10 @@
31838 SCAN_TRACKER_REFTYPE pst){
31839 char cin;
31840 char cWait = (char)qss; /* intentional narrowing loss */
31841 if( cWait==0 ){
31842 PlainScan:
31843 assert( cWait==0 );
31844 while( (cin = *zLine++)!=0 ){
31845 if( IsSpace(cin) )
31846 continue;
31847 switch (cin){
31848 case '-':
@@ -31890,11 +32174,10 @@
31890 switch( cWait ){
31891 case '*':
31892 if( *zLine != '/' )
31893 continue;
31894 ++zLine;
31895 cWait = 0;
31896 CONTINUE_PROMPT_AWAITC(pst, 0);
31897 qss = QSS_SETV(qss, 0);
31898 goto PlainScan;
31899 case '`': case '\'': case '"':
31900 if(*zLine==cWait){
@@ -31902,11 +32185,10 @@
31902 ++zLine;
31903 continue;
31904 }
31905 deliberate_fall_through;
31906 case ']':
31907 cWait = 0;
31908 CONTINUE_PROMPT_AWAITC(pst, 0);
31909 qss = QSS_SETV(qss, 0);
31910 goto PlainScan;
31911 default: assert(0);
31912 }
@@ -32090,11 +32372,14 @@
32090 if( doAutoDetectRestore(p, zSql) ) return 1;
32091 return 0;
32092 }
32093
32094 static void echo_group_input(ShellState *p, const char *zDo){
32095 if( ShellHasFlag(p, SHFLG_Echo) ) sqlite3_fprintf(p->out, "%s\n", zDo);
 
 
 
32096 }
32097
32098 #ifdef SQLITE_SHELL_FIDDLE
32099 /*
32100 ** Alternate one_input_line() impl for wasm mode. This is not in the primary
@@ -32550,10 +32835,19 @@
32550 }
32551
32552 static void sayAbnormalExit(void){
32553 if( seenInterrupt ) eputz("Program interrupted.\n");
32554 }
 
 
 
 
 
 
 
 
 
32555
32556 #ifndef SQLITE_SHELL_IS_UTF8
32557 # if (defined(_WIN32) || defined(WIN32)) \
32558 && (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__)))
32559 # define SQLITE_SHELL_IS_UTF8 (0)
@@ -32787,12 +33081,10 @@
32787 case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break;
32788 case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break;
32789 default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break;
32790 }
32791 }else if( cli_strcmp(z,"-vfstrace")==0 ){
32792 vfstrace_register("trace",0,(int(*)(const char*,void*))sqlite3_fputs,
32793 stderr,1);
32794 bEnableVfstrace = 1;
32795 #ifdef SQLITE_ENABLE_MULTIPLEX
32796 }else if( cli_strcmp(z,"-multiplex")==0 ){
32797 extern int sqlite3_multiplex_initialize(const char*,int);
32798 sqlite3_multiplex_initialize(0, 1);
@@ -32885,10 +33177,13 @@
32885 "%s: Error: no database filename specified\n", Argv0);
32886 return 1;
32887 #endif
32888 }
32889 data.out = stdout;
 
 
 
32890 #ifndef SQLITE_SHELL_FIDDLE
32891 sqlite3_appendvfs_init(0,0,0);
32892 #endif
32893
32894 /* Go ahead and open the database file if it already exists. If the
@@ -33129,19 +33424,14 @@
33129 */
33130 if( stdin_is_interactive ){
33131 char *zHome;
33132 char *zHistory;
33133 int nHistory;
33134 #if CIO_WIN_WC_XLATE
33135 # define SHELL_CIO_CHAR_SET (stdout_is_console? " (UTF-16 console I/O)" : "")
33136 #else
33137 # define SHELL_CIO_CHAR_SET ""
33138 #endif
33139 sqlite3_fprintf(stdout,
33140 "SQLite version %s %.19s%s\n" /*extra-version-info*/
33141 "Enter \".help\" for usage hints.\n",
33142 sqlite3_libversion(), sqlite3_sourceid(), SHELL_CIO_CHAR_SET);
33143 if( warnInmemoryDb ){
33144 sputz(stdout, "Connected to a ");
33145 printBold("transient in-memory database");
33146 sputz(stdout, ".\nUse \".open FILENAME\" to reopen on a"
33147 " persistent database.\n");
@@ -33154,13 +33444,15 @@
33154 if( (zHistory = malloc(nHistory))!=0 ){
33155 sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
33156 }
33157 }
33158 if( zHistory ){ shell_read_history(zHistory); }
33159 #if HAVE_READLINE || HAVE_EDITLINE
33160 rl_attempted_completion_function = readline_completion;
33161 #elif HAVE_LINENOISE
 
 
33162 linenoiseSetCompletionCallback(linenoise_completion, NULL);
33163 #endif
33164 data.in = 0;
33165 rc = process_input(&data);
33166 if( zHistory ){
33167
--- extsrc/shell.c
+++ extsrc/shell.c
@@ -120,13 +120,10 @@
120 #include <math.h>
121 #include "sqlite3.h"
122 typedef sqlite3_int64 i64;
123 typedef sqlite3_uint64 u64;
124 typedef unsigned char u8;
 
 
 
125 #include <ctype.h>
126 #include <stdarg.h>
127
128 #if !defined(_WIN32) && !defined(WIN32)
129 # include <signal.h>
@@ -408,21 +405,21 @@
405 wchar_t *b1, *b2;
406 int sz1, sz2;
407
408 sz1 = (int)strlen(zFilename);
409 sz2 = (int)strlen(zMode);
410 b1 = sqlite3_malloc( (sz1+1)*sizeof(b1[0]) );
411 b2 = sqlite3_malloc( (sz2+1)*sizeof(b1[0]) );
412 if( b1 && b2 ){
413 sz1 = MultiByteToWideChar(CP_UTF8, 0, zFilename, sz1, b1, sz1);
414 b1[sz1] = 0;
415 sz2 = MultiByteToWideChar(CP_UTF8, 0, zMode, sz2, b2, sz2);
416 b2[sz2] = 0;
417 fp = _wfopen(b1, b2);
418 }
419 sqlite3_free(b1);
420 sqlite3_free(b2);
421 simBinaryOther = 0;
422 return fp;
423 }
424
425
@@ -434,21 +431,21 @@
431 wchar_t *b1, *b2;
432 int sz1, sz2;
433
434 sz1 = (int)strlen(zCommand);
435 sz2 = (int)strlen(zMode);
436 b1 = sqlite3_malloc( (sz1+1)*sizeof(b1[0]) );
437 b2 = sqlite3_malloc( (sz2+1)*sizeof(b1[0]) );
438 if( b1 && b2 ){
439 sz1 = MultiByteToWideChar(CP_UTF8, 0, zCommand, sz1, b1, sz1);
440 b1[sz1] = 0;
441 sz2 = MultiByteToWideChar(CP_UTF8, 0, zMode, sz2, b2, sz2);
442 b2[sz2] = 0;
443 fp = _wpopen(b1, b2);
444 }
445 sqlite3_free(b1);
446 sqlite3_free(b2);
447 return fp;
448 }
449
450 /*
451 ** Work-alike for fgets() from the standard C library.
@@ -458,16 +455,26 @@
455 /* When reading from the command-prompt in Windows, it is necessary
456 ** to use _O_WTEXT input mode to read UTF-16 characters, then translate
457 ** that into UTF-8. Otherwise, non-ASCII characters all get translated
458 ** into '?'.
459 */
460 wchar_t *b1 = sqlite3_malloc( sz*sizeof(wchar_t) );
461 if( b1==0 ) return 0;
462 #ifndef SQLITE_USE_STDIO_FOR_CONSOLE
463 DWORD nRead = 0;
464 if( IsConsole(in)
465 && ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), b1, sz, &nRead, 0)
466 ){
467 b1[nRead] = 0;
468 }else
469 #endif
470 {
471 _setmode(_fileno(in), IsConsole(in) ? _O_WTEXT : _O_U8TEXT);
472 if( fgetws(b1, sz/4, in)==0 ){
473 sqlite3_free(b1);
474 return 0;
475 }
476 }
477 WideCharToMultiByte(CP_UTF8, 0, b1, -1, buf, sz, 0, 0);
478 sqlite3_free(b1);
479 return buf;
480 }else{
@@ -519,24 +526,37 @@
526 if( !UseWtextForOutput(out) ){
527 /* Writing to a file or other destination, just write bytes without
528 ** any translation. */
529 return fputs(z, out);
530 }else{
531 /* One must use UTF16 in order to get unicode support when writing
532 ** to the console on Windows.
 
533 */
534 int sz = (int)strlen(z);
535 wchar_t *b1 = sqlite3_malloc( (sz+1)*sizeof(wchar_t) );
536 if( b1==0 ) return 0;
537 sz = MultiByteToWideChar(CP_UTF8, 0, z, sz, b1, sz);
538 b1[sz] = 0;
539
540 #ifndef SQLITE_STDIO_FOR_CONSOLE
541 DWORD nWr = 0;
542 if( IsConsole(out)
543 && WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE),b1,sz,&nWr,0)
544 ){
545 /* If writing to the console, then the WriteConsoleW() is all we
546 ** need to do. */
547 }else
548 #endif
549 {
550 /* For non-console I/O, or if SQLITE_USE_STDIO_FOR_CONSOLE is defined
551 ** then write using the standard library. */
552 _setmode(_fileno(out), _O_U8TEXT);
553 if( UseBinaryWText(out) ){
554 piecemealOutput(b1, sz, out);
555 }else{
556 fputws(b1, out);
557 }
558 }
559 sqlite3_free(b1);
560 return 0;
561 }
562 }
@@ -2347,11 +2367,11 @@
2367 **
2368 ******************************************************************************
2369 **
2370 ** This SQLite extension implements functions that compute SHA3 hashes
2371 ** in the way described by the (U.S.) NIST FIPS 202 SHA-3 Standard.
2372 ** Three SQL functions are implemented:
2373 **
2374 ** sha3(X,SIZE)
2375 ** sha3_agg(Y,SIZE)
2376 ** sha3_query(Z,SIZE)
2377 **
@@ -5070,14 +5090,14 @@
5090 char **pzErrMsg,
5091 const sqlite3_api_routines *pApi
5092 ){
5093 int rc = SQLITE_OK;
5094 unsigned int i;
5095 #ifdef SQLITE3EXT_H
 
 
5096 SQLITE_EXTENSION_INIT2(pApi);
5097 #else
5098 (void)pApi; /* Unused parameter */
5099 #endif
5100 (void)pzErrMsg; /* Unused parameter */
5101 for(i=0; i<sizeof(aPercentFunc)/sizeof(aPercentFunc[0]); i++){
5102 rc = sqlite3_create_window_function(db,
5103 aPercentFunc[i].zName,
@@ -6831,35 +6851,41 @@
6851 idxNum |= 0x40;
6852 }
6853 continue;
6854 }
6855 if( pConstraint->iColumn<SERIES_COLUMN_START ){
6856 if( pConstraint->iColumn==SERIES_COLUMN_VALUE && pConstraint->usable ){
6857 switch( op ){
6858 case SQLITE_INDEX_CONSTRAINT_EQ:
6859 case SQLITE_INDEX_CONSTRAINT_IS: {
6860 idxNum |= 0x0080;
6861 idxNum &= ~0x3300;
6862 aIdx[5] = i;
6863 aIdx[6] = -1;
6864 #ifndef ZERO_ARGUMENT_GENERATE_SERIES
6865 bStartSeen = 1;
6866 #endif
6867 break;
6868 }
6869 case SQLITE_INDEX_CONSTRAINT_GE: {
6870 if( idxNum & 0x0080 ) break;
6871 idxNum |= 0x0100;
6872 idxNum &= ~0x0200;
6873 aIdx[5] = i;
6874 #ifndef ZERO_ARGUMENT_GENERATE_SERIES
6875 bStartSeen = 1;
6876 #endif
6877 break;
6878 }
6879 case SQLITE_INDEX_CONSTRAINT_GT: {
6880 if( idxNum & 0x0080 ) break;
6881 idxNum |= 0x0200;
6882 idxNum &= ~0x0100;
6883 aIdx[5] = i;
6884 #ifndef ZERO_ARGUMENT_GENERATE_SERIES
6885 bStartSeen = 1;
6886 #endif
6887 break;
6888 }
6889 case SQLITE_INDEX_CONSTRAINT_LE: {
6890 if( idxNum & 0x0080 ) break;
6891 idxNum |= 0x1000;
@@ -14167,11 +14193,11 @@
14193 /* A view. Or a trigger on a view. */
14194 if( zSql ) rc = expertSchemaSql(p->dbv, zSql, pzErrmsg);
14195 }else{
14196 IdxTable *pTab;
14197 rc = idxGetTableInfo(p->db, zName, &pTab, pzErrmsg);
14198 if( rc==SQLITE_OK && ALWAYS(pTab!=0) ){
14199 int i;
14200 char *zInner = 0;
14201 char *zOuter = 0;
14202 pTab->pNext = p->pTable;
14203 p->pTable = pTab;
@@ -16235,11 +16261,31 @@
16261 **
16262 ** Similar compiler commands will work on different systems. The key
16263 ** invariants are (1) you must have -DSQLITE_ENABLE_VFSTRACE so that
16264 ** the shell.c source file will know to include the -vfstrace command-line
16265 ** option and (2) you must compile and link the three source files
16266 ** shell,c, test_vfstrace.c, and sqlite3.c.
16267 **
16268 ** RUNTIME CONTROL OF VFSTRACE OUTPUT
16269 **
16270 ** The application can use the "vfstrace" pragma to control which VFS
16271 ** APIs are traced. To disable all output:
16272 **
16273 ** PRAGMA vfstrace('-all');
16274 **
16275 ** To enable all output (which is the default setting):
16276 **
16277 ** PRAGMA vfstrace('+all');
16278 **
16279 ** Individual APIs can be enabled or disabled by name, with or without
16280 ** the initial "x" character. For example, to set up for tracing lock
16281 ** primatives only:
16282 **
16283 ** PRAGMA vfstrace('-all, +Lock,Unlock,ShmLock');
16284 **
16285 ** The argument to the vfstrace pragma ignores capitalization and any
16286 ** characters other than alphabetics, '+', and '-'.
16287 */
16288 #include <stdlib.h>
16289 #include <string.h>
16290 /* #include "sqlite3.h" */
16291
@@ -16249,10 +16295,12 @@
16295 */
16296 typedef struct vfstrace_info vfstrace_info;
16297 struct vfstrace_info {
16298 sqlite3_vfs *pRootVfs; /* The underlying real VFS */
16299 int (*xOut)(const char*, void*); /* Send output here */
16300 unsigned int mTrace; /* Mask of interfaces to trace */
16301 u8 bOn; /* Tracing on/off */
16302 void *pOutArg; /* First argument to xOut */
16303 const char *zVfsName; /* Name of this trace-VFS */
16304 sqlite3_vfs *pTraceVfs; /* Pointer back to the trace VFS */
16305 };
16306
@@ -16265,10 +16313,42 @@
16313 vfstrace_info *pInfo; /* The trace-VFS to which this file belongs */
16314 const char *zFName; /* Base name of the file */
16315 sqlite3_file *pReal; /* The real underlying file */
16316 };
16317
16318 /*
16319 ** Bit values for vfstrace_info.mTrace.
16320 */
16321 #define VTR_CLOSE 0x00000001
16322 #define VTR_READ 0x00000002
16323 #define VTR_WRITE 0x00000004
16324 #define VTR_TRUNC 0x00000008
16325 #define VTR_SYNC 0x00000010
16326 #define VTR_FSIZE 0x00000020
16327 #define VTR_LOCK 0x00000040
16328 #define VTR_UNLOCK 0x00000080
16329 #define VTR_CRL 0x00000100
16330 #define VTR_FCTRL 0x00000200
16331 #define VTR_SECSZ 0x00000400
16332 #define VTR_DEVCHAR 0x00000800
16333 #define VTR_SHMLOCK 0x00001000
16334 #define VTR_SHMMAP 0x00002000
16335 #define VTR_SHMBAR 0x00004000
16336 #define VTR_SHMUNMAP 0x00008000
16337 #define VTR_OPEN 0x00010000
16338 #define VTR_DELETE 0x00020000
16339 #define VTR_ACCESS 0x00040000
16340 #define VTR_FULLPATH 0x00080000
16341 #define VTR_DLOPEN 0x00100000
16342 #define VTR_DLERR 0x00200000
16343 #define VTR_DLSYM 0x00400000
16344 #define VTR_DLCLOSE 0x00800000
16345 #define VTR_RAND 0x01000000
16346 #define VTR_SLEEP 0x02000000
16347 #define VTR_CURTIME 0x04000000
16348 #define VTR_LASTERR 0x08000000
16349
16350 /*
16351 ** Method declarations for vfstrace_file.
16352 */
16353 static int vfstraceClose(sqlite3_file*);
16354 static int vfstraceRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
@@ -16329,15 +16409,17 @@
16409 const char *zFormat,
16410 ...
16411 ){
16412 va_list ap;
16413 char *zMsg;
16414 if( pInfo->bOn ){
16415 va_start(ap, zFormat);
16416 zMsg = sqlite3_vmprintf(zFormat, ap);
16417 va_end(ap);
16418 pInfo->xOut(zMsg, pInfo->pOutArg);
16419 sqlite3_free(zMsg);
16420 }
16421 }
16422
16423 /*
16424 ** Try to convert an error code into a symbolic name for that error code.
16425 */
@@ -16431,18 +16513,26 @@
16513 int i = *pI;
16514 while( zAppend[0] ){ z[i++] = *(zAppend++); }
16515 z[i] = 0;
16516 *pI = i;
16517 }
16518
16519 /*
16520 ** Turn tracing output on or off according to mMask.
16521 */
16522 static void vfstraceOnOff(vfstrace_info *pInfo, unsigned int mMask){
16523 pInfo->bOn = (pInfo->mTrace & mMask)!=0;
16524 }
16525
16526 /*
16527 ** Close an vfstrace-file.
16528 */
16529 static int vfstraceClose(sqlite3_file *pFile){
16530 vfstrace_file *p = (vfstrace_file *)pFile;
16531 vfstrace_info *pInfo = p->pInfo;
16532 int rc;
16533 vfstraceOnOff(pInfo, VTR_CLOSE);
16534 vfstrace_printf(pInfo, "%s.xClose(%s)", pInfo->zVfsName, p->zFName);
16535 rc = p->pReal->pMethods->xClose(p->pReal);
16536 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16537 if( rc==SQLITE_OK ){
16538 sqlite3_free((void*)p->base.pMethods);
@@ -16461,10 +16551,11 @@
16551 sqlite_int64 iOfst
16552 ){
16553 vfstrace_file *p = (vfstrace_file *)pFile;
16554 vfstrace_info *pInfo = p->pInfo;
16555 int rc;
16556 vfstraceOnOff(pInfo, VTR_READ);
16557 vfstrace_printf(pInfo, "%s.xRead(%s,n=%d,ofst=%lld)",
16558 pInfo->zVfsName, p->zFName, iAmt, iOfst);
16559 rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst);
16560 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16561 return rc;
@@ -16480,10 +16571,11 @@
16571 sqlite_int64 iOfst
16572 ){
16573 vfstrace_file *p = (vfstrace_file *)pFile;
16574 vfstrace_info *pInfo = p->pInfo;
16575 int rc;
16576 vfstraceOnOff(pInfo, VTR_WRITE);
16577 vfstrace_printf(pInfo, "%s.xWrite(%s,n=%d,ofst=%lld)",
16578 pInfo->zVfsName, p->zFName, iAmt, iOfst);
16579 rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst);
16580 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16581 return rc;
@@ -16494,10 +16586,11 @@
16586 */
16587 static int vfstraceTruncate(sqlite3_file *pFile, sqlite_int64 size){
16588 vfstrace_file *p = (vfstrace_file *)pFile;
16589 vfstrace_info *pInfo = p->pInfo;
16590 int rc;
16591 vfstraceOnOff(pInfo, VTR_TRUNC);
16592 vfstrace_printf(pInfo, "%s.xTruncate(%s,%lld)", pInfo->zVfsName, p->zFName,
16593 size);
16594 rc = p->pReal->pMethods->xTruncate(p->pReal, size);
16595 vfstrace_printf(pInfo, " -> %d\n", rc);
16596 return rc;
@@ -16518,10 +16611,11 @@
16611 else if( flags & SQLITE_SYNC_NORMAL ) strappend(zBuf, &i, "|NORMAL");
16612 if( flags & SQLITE_SYNC_DATAONLY ) strappend(zBuf, &i, "|DATAONLY");
16613 if( flags & ~(SQLITE_SYNC_FULL|SQLITE_SYNC_DATAONLY) ){
16614 sqlite3_snprintf(sizeof(zBuf)-i, &zBuf[i], "|0x%x", flags);
16615 }
16616 vfstraceOnOff(pInfo, VTR_SYNC);
16617 vfstrace_printf(pInfo, "%s.xSync(%s,%s)", pInfo->zVfsName, p->zFName,
16618 &zBuf[1]);
16619 rc = p->pReal->pMethods->xSync(p->pReal, flags);
16620 vfstrace_printf(pInfo, " -> %d\n", rc);
16621 return rc;
@@ -16532,10 +16626,11 @@
16626 */
16627 static int vfstraceFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
16628 vfstrace_file *p = (vfstrace_file *)pFile;
16629 vfstrace_info *pInfo = p->pInfo;
16630 int rc;
16631 vfstraceOnOff(pInfo, VTR_FSIZE);
16632 vfstrace_printf(pInfo, "%s.xFileSize(%s)", pInfo->zVfsName, p->zFName);
16633 rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
16634 vfstrace_print_errcode(pInfo, " -> %s,", rc);
16635 vfstrace_printf(pInfo, " size=%lld\n", *pSize);
16636 return rc;
@@ -16560,10 +16655,11 @@
16655 */
16656 static int vfstraceLock(sqlite3_file *pFile, int eLock){
16657 vfstrace_file *p = (vfstrace_file *)pFile;
16658 vfstrace_info *pInfo = p->pInfo;
16659 int rc;
16660 vfstraceOnOff(pInfo, VTR_LOCK);
16661 vfstrace_printf(pInfo, "%s.xLock(%s,%s)", pInfo->zVfsName, p->zFName,
16662 lockName(eLock));
16663 rc = p->pReal->pMethods->xLock(p->pReal, eLock);
16664 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16665 return rc;
@@ -16574,10 +16670,11 @@
16670 */
16671 static int vfstraceUnlock(sqlite3_file *pFile, int eLock){
16672 vfstrace_file *p = (vfstrace_file *)pFile;
16673 vfstrace_info *pInfo = p->pInfo;
16674 int rc;
16675 vfstraceOnOff(pInfo, VTR_UNLOCK);
16676 vfstrace_printf(pInfo, "%s.xUnlock(%s,%s)", pInfo->zVfsName, p->zFName,
16677 lockName(eLock));
16678 rc = p->pReal->pMethods->xUnlock(p->pReal, eLock);
16679 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16680 return rc;
@@ -16588,10 +16685,11 @@
16685 */
16686 static int vfstraceCheckReservedLock(sqlite3_file *pFile, int *pResOut){
16687 vfstrace_file *p = (vfstrace_file *)pFile;
16688 vfstrace_info *pInfo = p->pInfo;
16689 int rc;
16690 vfstraceOnOff(pInfo, VTR_CRL);
16691 vfstrace_printf(pInfo, "%s.xCheckReservedLock(%s,%d)",
16692 pInfo->zVfsName, p->zFName);
16693 rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut);
16694 vfstrace_print_errcode(pInfo, " -> %s", rc);
16695 vfstrace_printf(pInfo, ", out=%d\n", *pResOut);
@@ -16607,10 +16705,11 @@
16705 int rc;
16706 char zBuf[100];
16707 char zBuf2[100];
16708 char *zOp;
16709 char *zRVal = 0;
16710 vfstraceOnOff(pInfo, VTR_FCTRL);
16711 switch( op ){
16712 case SQLITE_FCNTL_LOCKSTATE: zOp = "LOCKSTATE"; break;
16713 case SQLITE_GET_LOCKPROXYFILE: zOp = "GET_LOCKPROXYFILE"; break;
16714 case SQLITE_SET_LOCKPROXYFILE: zOp = "SET_LOCKPROXYFILE"; break;
16715 case SQLITE_LAST_ERRNO: zOp = "LAST_ERRNO"; break;
@@ -16635,10 +16734,83 @@
16734 case SQLITE_FCNTL_OVERWRITE: zOp = "OVERWRITE"; break;
16735 case SQLITE_FCNTL_VFSNAME: zOp = "VFSNAME"; break;
16736 case SQLITE_FCNTL_POWERSAFE_OVERWRITE: zOp = "POWERSAFE_OVERWRITE"; break;
16737 case SQLITE_FCNTL_PRAGMA: {
16738 const char *const* a = (const char*const*)pArg;
16739 if( a[1] && strcmp(a[1],"vfstrace")==0 && a[2] ){
16740 const u8 *zArg = (const u8*)a[2];
16741 if( zArg[0]>='0' && zArg[0]<=9 ){
16742 pInfo->mTrace = (sqlite3_uint64)strtoll(a[2], 0, 0);
16743 }else{
16744 static const struct {
16745 const char *z;
16746 unsigned int m;
16747 } aKw[] = {
16748 { "all", 0xffffffff },
16749 { "close", VTR_CLOSE },
16750 { "read", VTR_READ },
16751 { "write", VTR_WRITE },
16752 { "truncate", VTR_TRUNC },
16753 { "sync", VTR_SYNC },
16754 { "filesize", VTR_FSIZE },
16755 { "lock", VTR_LOCK },
16756 { "unlock", VTR_UNLOCK },
16757 { "checkreservedlock", VTR_CRL },
16758 { "filecontrol", VTR_FCTRL },
16759 { "sectorsize", VTR_SECSZ },
16760 { "devicecharacteristics", VTR_DEVCHAR },
16761 { "shmlock", VTR_SHMLOCK },
16762 { "shmmap", VTR_SHMMAP },
16763 { "shmummap", VTR_SHMUNMAP },
16764 { "shmbarrier", VTR_SHMBAR },
16765 { "open", VTR_OPEN },
16766 { "delete", VTR_DELETE },
16767 { "access", VTR_ACCESS },
16768 { "fullpathname", VTR_FULLPATH },
16769 { "dlopen", VTR_DLOPEN },
16770 { "dlerror", VTR_DLERR },
16771 { "dlsym", VTR_DLSYM },
16772 { "dlclose", VTR_DLCLOSE },
16773 { "randomness", VTR_RAND },
16774 { "sleep", VTR_SLEEP },
16775 { "currenttime", VTR_CURTIME },
16776 { "currenttimeint64", VTR_CURTIME },
16777 { "getlasterror", VTR_LASTERR },
16778 };
16779 int onOff = 1;
16780 while( zArg[0] ){
16781 int jj, n;
16782 while( zArg[0]!=0 && zArg[0]!='-' && zArg[0]!='+'
16783 && !isalpha(zArg[0]) ) zArg++;
16784 if( zArg[0]==0 ) break;
16785 if( zArg[0]=='-' ){
16786 onOff = 0;
16787 zArg++;
16788 }else if( zArg[0]=='+' ){
16789 onOff = 1;
16790 zArg++;
16791 }
16792 while( !isalpha(zArg[0]) ){
16793 if( zArg[0]==0 ) break;
16794 zArg++;
16795 }
16796 if( zArg[0]=='x' && isalpha(zArg[1]) ) zArg++;
16797 for(n=0; isalpha(zArg[n]); n++){}
16798 for(jj=0; jj<(int)(sizeof(aKw)/sizeof(aKw[0])); jj++){
16799 if( sqlite3_strnicmp(aKw[jj].z,(const char*)zArg,n)==0 ){
16800 if( onOff ){
16801 pInfo->mTrace |= aKw[jj].m;
16802 }else{
16803 pInfo->mTrace &= ~aKw[jj].m;
16804 }
16805 break;
16806 }
16807 }
16808 zArg += n;
16809 }
16810 }
16811 }
16812 sqlite3_snprintf(sizeof(zBuf), zBuf, "PRAGMA,[%s,%s]",a[1],a[2]);
16813 zOp = zBuf;
16814 break;
16815 }
16816 case SQLITE_FCNTL_BUSYHANDLER: zOp = "BUSYHANDLER"; break;
@@ -16730,10 +16902,11 @@
16902 */
16903 static int vfstraceSectorSize(sqlite3_file *pFile){
16904 vfstrace_file *p = (vfstrace_file *)pFile;
16905 vfstrace_info *pInfo = p->pInfo;
16906 int rc;
16907 vfstraceOnOff(pInfo, VTR_SECSZ);
16908 vfstrace_printf(pInfo, "%s.xSectorSize(%s)", pInfo->zVfsName, p->zFName);
16909 rc = p->pReal->pMethods->xSectorSize(p->pReal);
16910 vfstrace_printf(pInfo, " -> %d\n", rc);
16911 return rc;
16912 }
@@ -16743,10 +16916,11 @@
16916 */
16917 static int vfstraceDeviceCharacteristics(sqlite3_file *pFile){
16918 vfstrace_file *p = (vfstrace_file *)pFile;
16919 vfstrace_info *pInfo = p->pInfo;
16920 int rc;
16921 vfstraceOnOff(pInfo, VTR_DEVCHAR);
16922 vfstrace_printf(pInfo, "%s.xDeviceCharacteristics(%s)",
16923 pInfo->zVfsName, p->zFName);
16924 rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal);
16925 vfstrace_printf(pInfo, " -> 0x%08x\n", rc);
16926 return rc;
@@ -16754,25 +16928,43 @@
16928
16929 /*
16930 ** Shared-memory operations.
16931 */
16932 static int vfstraceShmLock(sqlite3_file *pFile, int ofst, int n, int flags){
16933 static const char *azLockName[] = {
16934 "WRITE",
16935 "CKPT",
16936 "RECOVER",
16937 "READ0",
16938 "READ1",
16939 "READ2",
16940 "READ3",
16941 "READ4",
16942 };
16943 vfstrace_file *p = (vfstrace_file *)pFile;
16944 vfstrace_info *pInfo = p->pInfo;
16945 int rc;
16946 char zLck[100];
16947 int i = 0;
16948 vfstraceOnOff(pInfo, VTR_SHMLOCK);
16949 memcpy(zLck, "|0", 3);
16950 if( flags & SQLITE_SHM_UNLOCK ) strappend(zLck, &i, "|UNLOCK");
16951 if( flags & SQLITE_SHM_LOCK ) strappend(zLck, &i, "|LOCK");
16952 if( flags & SQLITE_SHM_SHARED ) strappend(zLck, &i, "|SHARED");
16953 if( flags & SQLITE_SHM_EXCLUSIVE ) strappend(zLck, &i, "|EXCLUSIVE");
16954 if( flags & ~(0xf) ){
16955 sqlite3_snprintf(sizeof(zLck)-i, &zLck[i], "|0x%x", flags);
16956 }
16957 if( ofst>=0 && ofst<(int)(sizeof(azLockName)/sizeof(azLockName[0])) ){
16958 vfstrace_printf(pInfo, "%s.xShmLock(%s,ofst=%d(%s),n=%d,%s)",
16959 pInfo->zVfsName, p->zFName, ofst, azLockName[ofst],
16960 n, &zLck[1]);
16961 }else{
16962 vfstrace_printf(pInfo, "%s.xShmLock(%s,ofst=5d,n=%d,%s)",
16963 pInfo->zVfsName, p->zFName, ofst,
16964 n, &zLck[1]);
16965 }
16966 rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags);
16967 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16968 return rc;
16969 }
16970 static int vfstraceShmMap(
@@ -16783,26 +16975,29 @@
16975 void volatile **pp
16976 ){
16977 vfstrace_file *p = (vfstrace_file *)pFile;
16978 vfstrace_info *pInfo = p->pInfo;
16979 int rc;
16980 vfstraceOnOff(pInfo, VTR_SHMMAP);
16981 vfstrace_printf(pInfo, "%s.xShmMap(%s,iRegion=%d,szRegion=%d,isWrite=%d,*)",
16982 pInfo->zVfsName, p->zFName, iRegion, szRegion, isWrite);
16983 rc = p->pReal->pMethods->xShmMap(p->pReal, iRegion, szRegion, isWrite, pp);
16984 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
16985 return rc;
16986 }
16987 static void vfstraceShmBarrier(sqlite3_file *pFile){
16988 vfstrace_file *p = (vfstrace_file *)pFile;
16989 vfstrace_info *pInfo = p->pInfo;
16990 vfstraceOnOff(pInfo, VTR_SHMBAR);
16991 vfstrace_printf(pInfo, "%s.xShmBarrier(%s)\n", pInfo->zVfsName, p->zFName);
16992 p->pReal->pMethods->xShmBarrier(p->pReal);
16993 }
16994 static int vfstraceShmUnmap(sqlite3_file *pFile, int delFlag){
16995 vfstrace_file *p = (vfstrace_file *)pFile;
16996 vfstrace_info *pInfo = p->pInfo;
16997 int rc;
16998 vfstraceOnOff(pInfo, VTR_SHMUNMAP);
16999 vfstrace_printf(pInfo, "%s.xShmUnmap(%s,delFlag=%d)",
17000 pInfo->zVfsName, p->zFName, delFlag);
17001 rc = p->pReal->pMethods->xShmUnmap(p->pReal, delFlag);
17002 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
17003 return rc;
@@ -16826,10 +17021,11 @@
17021 sqlite3_vfs *pRoot = pInfo->pRootVfs;
17022 p->pInfo = pInfo;
17023 p->zFName = zName ? fileTail(zName) : "<temp>";
17024 p->pReal = (sqlite3_file *)&p[1];
17025 rc = pRoot->xOpen(pRoot, zName, p->pReal, flags, pOutFlags);
17026 vfstraceOnOff(pInfo, VTR_OPEN);
17027 vfstrace_printf(pInfo, "%s.xOpen(%s,flags=0x%x)",
17028 pInfo->zVfsName, p->zFName, flags);
17029 if( p->pReal->pMethods ){
17030 sqlite3_io_methods *pNew = sqlite3_malloc( sizeof(*pNew) );
17031 const sqlite3_io_methods *pSub = p->pReal->pMethods;
@@ -16871,10 +17067,11 @@
17067 */
17068 static int vfstraceDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
17069 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
17070 sqlite3_vfs *pRoot = pInfo->pRootVfs;
17071 int rc;
17072 vfstraceOnOff(pInfo, VTR_DELETE);
17073 vfstrace_printf(pInfo, "%s.xDelete(\"%s\",%d)",
17074 pInfo->zVfsName, zPath, dirSync);
17075 rc = pRoot->xDelete(pRoot, zPath, dirSync);
17076 vfstrace_print_errcode(pInfo, " -> %s\n", rc);
17077 return rc;
@@ -16891,10 +17088,11 @@
17088 int *pResOut
17089 ){
17090 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
17091 sqlite3_vfs *pRoot = pInfo->pRootVfs;
17092 int rc;
17093 vfstraceOnOff(pInfo, VTR_ACCESS);
17094 vfstrace_printf(pInfo, "%s.xAccess(\"%s\",%d)",
17095 pInfo->zVfsName, zPath, flags);
17096 rc = pRoot->xAccess(pRoot, zPath, flags, pResOut);
17097 vfstrace_print_errcode(pInfo, " -> %s", rc);
17098 vfstrace_printf(pInfo, ", out=%d\n", *pResOut);
@@ -16913,10 +17111,11 @@
17111 char *zOut
17112 ){
17113 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
17114 sqlite3_vfs *pRoot = pInfo->pRootVfs;
17115 int rc;
17116 vfstraceOnOff(pInfo, VTR_FULLPATH);
17117 vfstrace_printf(pInfo, "%s.xFullPathname(\"%s\")",
17118 pInfo->zVfsName, zPath);
17119 rc = pRoot->xFullPathname(pRoot, zPath, nOut, zOut);
17120 vfstrace_print_errcode(pInfo, " -> %s", rc);
17121 vfstrace_printf(pInfo, ", out=\"%.*s\"\n", nOut, zOut);
@@ -16927,10 +17126,11 @@
17126 ** Open the dynamic library located at zPath and return a handle.
17127 */
17128 static void *vfstraceDlOpen(sqlite3_vfs *pVfs, const char *zPath){
17129 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
17130 sqlite3_vfs *pRoot = pInfo->pRootVfs;
17131 vfstraceOnOff(pInfo, VTR_DLOPEN);
17132 vfstrace_printf(pInfo, "%s.xDlOpen(\"%s\")\n", pInfo->zVfsName, zPath);
17133 return pRoot->xDlOpen(pRoot, zPath);
17134 }
17135
17136 /*
@@ -16939,10 +17139,11 @@
17139 ** with dynamic libraries.
17140 */
17141 static void vfstraceDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
17142 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
17143 sqlite3_vfs *pRoot = pInfo->pRootVfs;
17144 vfstraceOnOff(pInfo, VTR_DLERR);
17145 vfstrace_printf(pInfo, "%s.xDlError(%d)", pInfo->zVfsName, nByte);
17146 pRoot->xDlError(pRoot, nByte, zErrMsg);
17147 vfstrace_printf(pInfo, " -> \"%s\"", zErrMsg);
17148 }
17149
@@ -16960,10 +17161,11 @@
17161 ** Close the dynamic library handle pHandle.
17162 */
17163 static void vfstraceDlClose(sqlite3_vfs *pVfs, void *pHandle){
17164 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
17165 sqlite3_vfs *pRoot = pInfo->pRootVfs;
17166 vfstraceOnOff(pInfo, VTR_DLCLOSE);
17167 vfstrace_printf(pInfo, "%s.xDlOpen()\n", pInfo->zVfsName);
17168 pRoot->xDlClose(pRoot, pHandle);
17169 }
17170
17171 /*
@@ -16971,10 +17173,11 @@
17173 ** random data.
17174 */
17175 static int vfstraceRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
17176 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
17177 sqlite3_vfs *pRoot = pInfo->pRootVfs;
17178 vfstraceOnOff(pInfo, VTR_RAND);
17179 vfstrace_printf(pInfo, "%s.xRandomness(%d)\n", pInfo->zVfsName, nByte);
17180 return pRoot->xRandomness(pRoot, nByte, zBufOut);
17181 }
17182
17183 /*
@@ -16982,34 +17185,52 @@
17185 ** actually slept.
17186 */
17187 static int vfstraceSleep(sqlite3_vfs *pVfs, int nMicro){
17188 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
17189 sqlite3_vfs *pRoot = pInfo->pRootVfs;
17190 vfstraceOnOff(pInfo, VTR_SLEEP);
17191 vfstrace_printf(pInfo, "%s.xSleep(%d)\n", pInfo->zVfsName, nMicro);
17192 return pRoot->xSleep(pRoot, nMicro);
17193 }
17194
17195 /*
17196 ** Return the current time as a Julian Day number in *pTimeOut.
17197 */
17198 static int vfstraceCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
17199 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
17200 sqlite3_vfs *pRoot = pInfo->pRootVfs;
17201 int rc;
17202 vfstraceOnOff(pInfo, VTR_CURTIME);
17203 vfstrace_printf(pInfo, "%s.xCurrentTime()", pInfo->zVfsName);
17204 rc = pRoot->xCurrentTime(pRoot, pTimeOut);
17205 vfstrace_printf(pInfo, " -> %.17g\n", *pTimeOut);
17206 return rc;
17207 }
17208 static int vfstraceCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){
17209 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
17210 sqlite3_vfs *pRoot = pInfo->pRootVfs;
17211 int rc;
17212 vfstraceOnOff(pInfo, VTR_CURTIME);
17213 vfstrace_printf(pInfo, "%s.xCurrentTimeInt64()", pInfo->zVfsName);
17214 rc = pRoot->xCurrentTimeInt64(pRoot, pTimeOut);
17215 vfstrace_printf(pInfo, " -> %lld\n", *pTimeOut);
17216 return rc;
17217 }
17218
17219 /*
17220 ** Return the most recent error code and message
17221 */
17222 static int vfstraceGetLastError(sqlite3_vfs *pVfs, int nErr, char *zErr){
17223 vfstrace_info *pInfo = (vfstrace_info*)pVfs->pAppData;
17224 sqlite3_vfs *pRoot = pInfo->pRootVfs;
17225 int rc;
17226 vfstraceOnOff(pInfo, VTR_LASTERR);
17227 vfstrace_printf(pInfo, "%s.xGetLastError(%d,zBuf)", pInfo->zVfsName, nErr);
17228 if( nErr ) zErr[0] = 0;
17229 rc = pRoot->xGetLastError(pRoot, nErr, zErr);
17230 vfstrace_printf(pInfo, " -> zBuf[] = \"%s\", rc = %d\n", nErr?zErr:"", rc);
17231 return rc;
17232 }
17233
17234 /*
17235 ** Override system calls.
17236 */
@@ -17099,10 +17320,12 @@
17320 pInfo->pRootVfs = pRoot;
17321 pInfo->xOut = xOut;
17322 pInfo->pOutArg = pOutArg;
17323 pInfo->zVfsName = pNew->zName;
17324 pInfo->pTraceVfs = pNew;
17325 pInfo->mTrace = 0xffffffff;
17326 pInfo->bOn = 1;
17327 vfstrace_printf(pInfo, "%s.enabled_for(\"%s\")\n",
17328 pInfo->zVfsName, pRoot->zName);
17329 return sqlite3_vfs_register(pNew, makeDefault);
17330 }
17331
@@ -20228,10 +20451,12 @@
20451 apVal[iField] = sqlite3_value_dup( pVal );
20452 if( apVal[iField]==0 ){
20453 recoverError(p, SQLITE_NOMEM, 0);
20454 }
20455 p1->nVal = iField+1;
20456 }else if( pTab->nCol==0 ){
20457 p1->nVal = pTab->nCol;
20458 }
20459 p1->iPrevCell = iCell;
20460 p1->iPrevPage = iPage;
20461 }
20462 }else{
@@ -21935,12 +22160,12 @@
22160 const char *zSkipValidUtf8(const char *z, int nAccept, long ccm){
22161 int ng = (nAccept<0)? -nAccept : 0;
22162 const char *pcLimit = (nAccept>=0)? z+nAccept : 0;
22163 assert(z!=0);
22164 while( (pcLimit)? (z<pcLimit) : (ng-- != 0) ){
22165 unsigned char c = *(u8*)z;
22166 if( c<0x7f ){
22167 if( ccm != 0L && c < 0x20 && ((1L<<c) & ccm) != 0 ) return z;
22168 ++z; /* ASCII */
22169 }else if( (c & 0xC0) != 0xC0 ) return z; /* not a lead byte */
22170 else{
22171 const char *zt = z+1; /* Got lead byte, look at trail bytes.*/
@@ -22004,14 +22229,14 @@
22229 }
22230 sqlite3_fputs(zq, out);
22231 }
22232
22233 /*
22234 ** Output the given string as quoted according to JSON quoting rules.
22235 */
22236 static void output_json_string(FILE *out, const char *z, i64 n){
22237 unsigned char c;
22238 static const char *zq = "\"";
22239 static long ctrlMask = ~0L;
22240 static const char *zDQBS = "\"\\";
22241 const char *pcLimit;
22242 char ace[3] = "\\?";
@@ -22027,11 +22252,11 @@
22252 if( pcEnd > z ){
22253 sqlite3_fprintf(out, "%.*s", (int)(pcEnd-z), z);
22254 z = pcEnd;
22255 }
22256 if( z >= pcLimit ) break;
22257 c = (unsigned char)*(z++);
22258 switch( c ){
22259 case '"': case '\\':
22260 cbsSay = (char)c;
22261 break;
22262 case '\b': cbsSay = 'b'; break;
@@ -22042,12 +22267,12 @@
22267 default: cbsSay = 0; break;
22268 }
22269 if( cbsSay ){
22270 ace[1] = cbsSay;
22271 sqlite3_fputs(ace, out);
22272 }else if( c<=0x1f || c>=0x7f ){
22273 sqlite3_fprintf(out, "\\u%04x", c);
22274 }else{
22275 ace[1] = (char)c;
22276 sqlite3_fputs(ace+1, out);
22277 }
22278 }
@@ -24784,13 +25009,13 @@
25009 if( rc ){
25010 sqlite3_fprintf(p->out, "/****** ERROR: %s ******/\n", zErr);
25011 }else{
25012 rc = SQLITE_CORRUPT;
25013 }
 
25014 free(zQ2);
25015 }
25016 sqlite3_free(zErr);
25017 return rc;
25018 }
25019
25020 /*
25021 ** Text of help messages.
@@ -24849,10 +25074,11 @@
25074 ".databases List names and files of attached databases",
25075 ".dbconfig ?op? ?val? List or change sqlite3_db_config() options",
25076 #if SQLITE_SHELL_HAVE_RECOVER
25077 ".dbinfo ?DB? Show status information about the database",
25078 #endif
25079 ".dbtotxt Hex dump of the database file",
25080 ".dump ?OBJECTS? Render database content as SQL",
25081 " Options:",
25082 " --data-only Output only INSERT statements",
25083 " --newlines Allow unescaped newline characters in output",
25084 " --nosys Omit system tables (ex: \"sqlite_stat1\")",
@@ -25654,11 +25880,12 @@
25880 sqlite3_fprintf(stderr,
25881 "Error: sqlite3_close() returns %d: %s\n", rc, sqlite3_errmsg(db));
25882 }
25883 }
25884
25885 #if (HAVE_READLINE || HAVE_EDITLINE) \
25886 && !defined(SQLITE_OMIT_READLINE_COMPLETION)
25887 /*
25888 ** Readline completion callbacks
25889 */
25890 static char *readline_completion_generator(const char *text, int state){
25891 static sqlite3_stmt *pStmt = 0;
@@ -25692,19 +25919,26 @@
25919 #elif HAVE_LINENOISE
25920 /*
25921 ** Linenoise completion callback. Note that the 3rd argument is from
25922 ** the "msteveb" version of linenoise, not the "antirez" version.
25923 */
25924 static void linenoise_completion(
25925 const char *zLine,
25926 linenoiseCompletions *lc
25927 #if HAVE_LINENOISE==2
25928 ,void *pUserData
25929 #endif
25930 ){
25931 i64 nLine = strlen(zLine);
25932 i64 i, iStart;
25933 sqlite3_stmt *pStmt = 0;
25934 char *zSql;
25935 char zBuf[1000];
25936
25937 #if HAVE_LINENOISE==2
25938 UNUSED_PARAMETER(pUserData);
25939 #endif
25940 if( nLine>(i64)sizeof(zBuf)-30 ) return;
25941 if( zLine[0]=='.' || zLine[0]=='#') return;
25942 for(i=nLine-1; i>=0 && (isalnum(zLine[i]) || zLine[i]=='_'); i--){}
25943 if( i==nLine-1 ) return;
25944 iStart = i+1;
@@ -26390,18 +26624,24 @@
26624 #endif
26625
26626 /*
26627 ** Run an SQL command and return the single integer result.
26628 */
26629 static int db_int(sqlite3 *db, const char *zSql, ...){
26630 sqlite3_stmt *pStmt;
26631 int res = 0;
26632 char *z;
26633 va_list ap;
26634 va_start(ap, zSql);
26635 z = sqlite3_vmprintf(zSql, ap);
26636 va_end(ap);
26637 sqlite3_prepare_v2(db, z, -1, &pStmt, 0);
26638 if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){
26639 res = sqlite3_column_int(pStmt,0);
26640 }
26641 sqlite3_finalize(pStmt);
26642 sqlite3_free(z);
26643 return res;
26644 }
26645
26646 #if SQLITE_SHELL_HAVE_RECOVER
26647 /*
@@ -26500,21 +26740,112 @@
26740 zSchemaTab = sqlite3_mprintf("%s", "sqlite_temp_schema");
26741 }else{
26742 zSchemaTab = sqlite3_mprintf("\"%w\".sqlite_schema", zDb);
26743 }
26744 for(i=0; i<ArraySize(aQuery); i++){
26745 int val = db_int(p->db, aQuery[i].zSql, zSchemaTab);
 
 
26746 sqlite3_fprintf(p->out, "%-20s %d\n", aQuery[i].zName, val);
26747 }
26748 sqlite3_free(zSchemaTab);
26749 sqlite3_file_control(p->db, zDb, SQLITE_FCNTL_DATA_VERSION, &iDataVersion);
26750 sqlite3_fprintf(p->out, "%-20s %u\n", "data version", iDataVersion);
26751 return 0;
26752 }
26753 #endif /* SQLITE_SHELL_HAVE_RECOVER */
26754
26755 /*
26756 ** Implementation of the ".dbtotxt" command.
26757 **
26758 ** Return 1 on error, 2 to exit, and 0 otherwise.
26759 */
26760 static int shell_dbtotxt_command(ShellState *p, int nArg, char **azArg){
26761 sqlite3_stmt *pStmt = 0;
26762 sqlite3_int64 nPage = 0;
26763 int pgSz = 0;
26764 const char *zTail;
26765 char *zName = 0;
26766 int rc, i, j;
26767 unsigned char bShow[256]; /* Characters ok to display */
26768
26769 UNUSED_PARAMETER(nArg);
26770 UNUSED_PARAMETER(azArg);
26771 memset(bShow, '.', sizeof(bShow));
26772 for(i=' '; i<='~'; i++){
26773 if( i!='{' && i!='}' && i!='"' && i!='\\' ) bShow[i] = (unsigned char)i;
26774 }
26775 rc = sqlite3_prepare_v2(p->db, "PRAGMA page_size", -1, &pStmt, 0);
26776 if( rc ) goto dbtotxt_error;
26777 rc = 0;
26778 if( sqlite3_step(pStmt)!=SQLITE_ROW ) goto dbtotxt_error;
26779 pgSz = sqlite3_column_int(pStmt, 0);
26780 sqlite3_finalize(pStmt);
26781 pStmt = 0;
26782 if( pgSz<512 || pgSz>65536 || (pgSz&(pgSz-1))!=0 ) goto dbtotxt_error;
26783 rc = sqlite3_prepare_v2(p->db, "PRAGMA page_count", -1, &pStmt, 0);
26784 if( rc ) goto dbtotxt_error;
26785 rc = 0;
26786 if( sqlite3_step(pStmt)!=SQLITE_ROW ) goto dbtotxt_error;
26787 nPage = sqlite3_column_int64(pStmt, 0);
26788 sqlite3_finalize(pStmt);
26789 pStmt = 0;
26790 if( nPage<1 ) goto dbtotxt_error;
26791 rc = sqlite3_prepare_v2(p->db, "PRAGMA databases", -1, &pStmt, 0);
26792 if( rc ) goto dbtotxt_error;
26793 if( sqlite3_step(pStmt)!=SQLITE_ROW ){
26794 zTail = "unk.db";
26795 }else{
26796 const char *zFilename = (const char*)sqlite3_column_text(pStmt, 2);
26797 if( zFilename==0 || zFilename[0]==0 ) zFilename = "unk.db";
26798 zTail = strrchr(zFilename, '/');
26799 #if defined(_WIN32)
26800 if( zTail==0 ) zTail = strrchr(zFilename, '\\');
26801 #endif
26802 }
26803 zName = strdup(zTail);
26804 shell_check_oom(zName);
26805 sqlite3_fprintf(p->out, "| size %lld pagesize %d filename %s\n",
26806 nPage*pgSz, pgSz, zName);
26807 sqlite3_finalize(pStmt);
26808 pStmt = 0;
26809 rc = sqlite3_prepare_v2(p->db,
26810 "SELECT pgno, data FROM sqlite_dbpage ORDER BY pgno", -1, &pStmt, 0);
26811 if( rc ) goto dbtotxt_error;
26812 while( sqlite3_step(pStmt)==SQLITE_ROW ){
26813 sqlite3_int64 pgno = sqlite3_column_int64(pStmt, 0);
26814 const u8 *aData = sqlite3_column_blob(pStmt, 1);
26815 int seenPageLabel = 0;
26816 for(i=0; i<pgSz; i+=16){
26817 const u8 *aLine = aData+i;
26818 for(j=0; j<16 && aLine[j]==0; j++){}
26819 if( j==16 ) continue;
26820 if( !seenPageLabel ){
26821 sqlite3_fprintf(p->out, "| page %lld offset %lld\n", pgno, pgno*pgSz);
26822 seenPageLabel = 1;
26823 }
26824 sqlite3_fprintf(p->out, "| %5d:", i);
26825 for(j=0; j<16; j++) sqlite3_fprintf(p->out, " %02x", aLine[j]);
26826 sqlite3_fprintf(p->out, " ");
26827 for(j=0; j<16; j++){
26828 unsigned char c = (unsigned char)aLine[j];
26829 sqlite3_fprintf(p->out, "%c", bShow[c]);
26830 }
26831 sqlite3_fprintf(p->out, "\n");
26832 }
26833 }
26834 sqlite3_finalize(pStmt);
26835 sqlite3_fprintf(p->out, "| end %s\n", zName);
26836 free(zName);
26837 return 0;
26838
26839 dbtotxt_error:
26840 if( rc ){
26841 sqlite3_fprintf(stderr, "ERROR: %s\n", sqlite3_errmsg(p->db));
26842 }
26843 sqlite3_finalize(pStmt);
26844 free(zName);
26845 return 1;
26846 }
26847
26848 /*
26849 ** Print the given string as an error message.
26850 */
26851 static void shellEmitError(const char *zErr){
@@ -28067,12 +28398,12 @@
28398 }else if( *pDb==0 ){
28399 return 0;
28400 }else{
28401 /* Formulate the columns spec, close the DB, zero *pDb. */
28402 char *zColsSpec = 0;
28403 int hasDupes = db_int(*pDb, "%s", zHasDupes);
28404 int nDigits = (hasDupes)? db_int(*pDb, "%s", zColDigits) : 0;
28405 if( hasDupes ){
28406 #ifdef SHELL_COLUMN_RENAME_CLEAN
28407 rc = sqlite3_exec(*pDb, zDedoctor, 0, 0, 0);
28408 rc_err_oom_die(rc);
28409 #endif
@@ -28083,11 +28414,11 @@
28414 sqlite3_bind_int(pStmt, 1, nDigits);
28415 rc = sqlite3_step(pStmt);
28416 sqlite3_finalize(pStmt);
28417 if( rc!=SQLITE_DONE ) rc_err_oom_die(SQLITE_NOMEM);
28418 }
28419 assert(db_int(*pDb, "%s", zHasDupes)==0); /* Consider: remove this */
28420 rc = sqlite3_prepare_v2(*pDb, zCollectVar, -1, &pStmt, 0);
28421 rc_err_oom_die(rc);
28422 rc = sqlite3_step(pStmt);
28423 if( rc==SQLITE_ROW ){
28424 zColsSpec = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 0));
@@ -28688,10 +29019,14 @@
29019 }else{
29020 eputz("Usage: .echo on|off\n");
29021 rc = 1;
29022 }
29023 }else
29024
29025 if( c=='d' && n>=3 && cli_strncmp(azArg[0], "dbtotxt", n)==0 ){
29026 rc = shell_dbtotxt_command(p, nArg, azArg);
29027 }else
29028
29029 if( c=='e' && cli_strncmp(azArg[0], "eqp", n)==0 ){
29030 if( nArg==2 ){
29031 p->autoEQPtest = 0;
29032 if( p->autoEQPtrace ){
@@ -29129,11 +29464,15 @@
29464 /* Below, resources must be freed before exit. */
29465 while( (nSkip--)>0 ){
29466 while( xRead(&sCtx) && sCtx.cTerm==sCtx.cColSep ){}
29467 }
29468 import_append_char(&sCtx, 0); /* To ensure sCtx.z is allocated */
29469 if( sqlite3_table_column_metadata(p->db, zSchema, zTable,0,0,0,0,0,0)
29470 && 0==db_int(p->db, "SELECT count(*) FROM \"%w\".sqlite_schema"
29471 " WHERE name=%Q AND type='view'",
29472 zSchema ? zSchema : "main", zTable)
29473 ){
29474 /* Table does not exist. Create it. */
29475 sqlite3 *dbCols = 0;
29476 char *zRenames = 0;
29477 char *zColDefs;
29478 zCreate = sqlite3_mprintf("CREATE TABLE \"%w\".\"%w\"",
@@ -30201,11 +30540,14 @@
30540 }
30541 close_db(pSrc);
30542 }else
30543 #endif /* !defined(SQLITE_SHELL_FIDDLE) */
30544
30545 if( c=='s' &&
30546 (cli_strncmp(azArg[0], "scanstats", n)==0 ||
30547 cli_strncmp(azArg[0], "scanstatus", n)==0)
30548 ){
30549 if( nArg==2 ){
30550 if( cli_strcmp(azArg[1], "vm")==0 ){
30551 p->scanstatsOn = 3;
30552 }else
30553 if( cli_strcmp(azArg[1], "est")==0 ){
@@ -31256,11 +31598,13 @@
31598 { 0x10000000, 1, "OrderBySubq" },
31599 { 0xffffffff, 0, "All" },
31600 };
31601 unsigned int curOpt;
31602 unsigned int newOpt;
31603 unsigned int m;
31604 int ii;
31605 int nOff;
31606 sqlite3_test_control(SQLITE_TESTCTRL_GETOPT, p->db, &curOpt);
31607 newOpt = curOpt;
31608 for(ii=2; ii<nArg; ii++){
31609 const char *z = azArg[ii];
31610 int useLabel = 0;
@@ -31297,28 +31641,32 @@
31641 }
31642 }
31643 }
31644 if( curOpt!=newOpt ){
31645 sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,p->db,newOpt);
31646 }
31647 for(ii=nOff=0, m=1; ii<32; ii++, m <<= 1){
31648 if( m & newOpt ) nOff++;
31649 }
31650 if( nOff<12 ){
31651 sqlite3_fputs("+All", p->out);
31652 for(ii=0; ii<ArraySize(aLabel); ii++){
31653 if( !aLabel[ii].bDsply ) continue;
31654 if( (newOpt & aLabel[ii].mask)!=0 ){
31655 sqlite3_fprintf(p->out, " -%s", aLabel[ii].zLabel);
31656 }
31657 }
31658 }else{
31659 sqlite3_fputs("-All", p->out);
31660 for(ii=0; ii<ArraySize(aLabel); ii++){
31661 if( !aLabel[ii].bDsply ) continue;
31662 if( (newOpt & aLabel[ii].mask)==0 ){
31663 sqlite3_fprintf(p->out, " +%s", aLabel[ii].zLabel);
 
 
31664 }
31665 }
31666 }
31667 sqlite3_fputs("\n", p->out);
31668 rc2 = isOk = 3;
31669 break;
31670 }
31671
31672 /* sqlite3_test_control(int, db, int) */
@@ -31652,73 +32000,10 @@
32000 }
32001 }
32002 }else
32003 #endif
32004
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32005 if( c=='v' && cli_strncmp(azArg[0], "version", n)==0 ){
32006 char *zPtrSz = sizeof(void*)==8 ? "64-bit" : "32-bit";
32007 sqlite3_fprintf(p->out, "SQLite %s %s\n" /*extra-version-info*/,
32008 sqlite3_libversion(), sqlite3_sourceid());
32009 #if SQLITE_HAVE_ZLIB
@@ -31838,11 +32123,10 @@
32123 SCAN_TRACKER_REFTYPE pst){
32124 char cin;
32125 char cWait = (char)qss; /* intentional narrowing loss */
32126 if( cWait==0 ){
32127 PlainScan:
 
32128 while( (cin = *zLine++)!=0 ){
32129 if( IsSpace(cin) )
32130 continue;
32131 switch (cin){
32132 case '-':
@@ -31890,11 +32174,10 @@
32174 switch( cWait ){
32175 case '*':
32176 if( *zLine != '/' )
32177 continue;
32178 ++zLine;
 
32179 CONTINUE_PROMPT_AWAITC(pst, 0);
32180 qss = QSS_SETV(qss, 0);
32181 goto PlainScan;
32182 case '`': case '\'': case '"':
32183 if(*zLine==cWait){
@@ -31902,11 +32185,10 @@
32185 ++zLine;
32186 continue;
32187 }
32188 deliberate_fall_through;
32189 case ']':
 
32190 CONTINUE_PROMPT_AWAITC(pst, 0);
32191 qss = QSS_SETV(qss, 0);
32192 goto PlainScan;
32193 default: assert(0);
32194 }
@@ -32090,11 +32372,14 @@
32372 if( doAutoDetectRestore(p, zSql) ) return 1;
32373 return 0;
32374 }
32375
32376 static void echo_group_input(ShellState *p, const char *zDo){
32377 if( ShellHasFlag(p, SHFLG_Echo) ){
32378 sqlite3_fprintf(p->out, "%s\n", zDo);
32379 fflush(p->out);
32380 }
32381 }
32382
32383 #ifdef SQLITE_SHELL_FIDDLE
32384 /*
32385 ** Alternate one_input_line() impl for wasm mode. This is not in the primary
@@ -32550,10 +32835,19 @@
32835 }
32836
32837 static void sayAbnormalExit(void){
32838 if( seenInterrupt ) eputz("Program interrupted.\n");
32839 }
32840
32841 /* Routine to output from vfstrace
32842 */
32843 static int vfstraceOut(const char *z, void *pArg){
32844 ShellState *p = (ShellState*)pArg;
32845 sqlite3_fputs(z, p->out);
32846 fflush(p->out);
32847 return 1;
32848 }
32849
32850 #ifndef SQLITE_SHELL_IS_UTF8
32851 # if (defined(_WIN32) || defined(WIN32)) \
32852 && (defined(_MSC_VER) || (defined(UNICODE) && defined(__GNUC__)))
32853 # define SQLITE_SHELL_IS_UTF8 (0)
@@ -32787,12 +33081,10 @@
33081 case 0: sqlite3_config(SQLITE_CONFIG_SINGLETHREAD); break;
33082 case 2: sqlite3_config(SQLITE_CONFIG_MULTITHREAD); break;
33083 default: sqlite3_config(SQLITE_CONFIG_SERIALIZED); break;
33084 }
33085 }else if( cli_strcmp(z,"-vfstrace")==0 ){
 
 
33086 bEnableVfstrace = 1;
33087 #ifdef SQLITE_ENABLE_MULTIPLEX
33088 }else if( cli_strcmp(z,"-multiplex")==0 ){
33089 extern int sqlite3_multiplex_initialize(const char*,int);
33090 sqlite3_multiplex_initialize(0, 1);
@@ -32885,10 +33177,13 @@
33177 "%s: Error: no database filename specified\n", Argv0);
33178 return 1;
33179 #endif
33180 }
33181 data.out = stdout;
33182 if( bEnableVfstrace ){
33183 vfstrace_register("trace",0,vfstraceOut, &data, 1);
33184 }
33185 #ifndef SQLITE_SHELL_FIDDLE
33186 sqlite3_appendvfs_init(0,0,0);
33187 #endif
33188
33189 /* Go ahead and open the database file if it already exists. If the
@@ -33129,19 +33424,14 @@
33424 */
33425 if( stdin_is_interactive ){
33426 char *zHome;
33427 char *zHistory;
33428 int nHistory;
 
 
 
 
 
33429 sqlite3_fprintf(stdout,
33430 "SQLite version %s %.19s\n" /*extra-version-info*/
33431 "Enter \".help\" for usage hints.\n",
33432 sqlite3_libversion(), sqlite3_sourceid());
33433 if( warnInmemoryDb ){
33434 sputz(stdout, "Connected to a ");
33435 printBold("transient in-memory database");
33436 sputz(stdout, ".\nUse \".open FILENAME\" to reopen on a"
33437 " persistent database.\n");
@@ -33154,13 +33444,15 @@
33444 if( (zHistory = malloc(nHistory))!=0 ){
33445 sqlite3_snprintf(nHistory, zHistory,"%s/.sqlite_history", zHome);
33446 }
33447 }
33448 if( zHistory ){ shell_read_history(zHistory); }
33449 #if (HAVE_READLINE || HAVE_EDITLINE) && !defined(SQLITE_OMIT_READLINE_COMPLETION)
33450 rl_attempted_completion_function = readline_completion;
33451 #elif HAVE_LINENOISE==1
33452 linenoiseSetCompletionCallback(linenoise_completion);
33453 #elif HAVE_LINENOISE==2
33454 linenoiseSetCompletionCallback(linenoise_completion, NULL);
33455 #endif
33456 data.in = 0;
33457 rc = process_input(&data);
33458 if( zHistory ){
33459
+1286 -709
--- extsrc/sqlite3.c
+++ extsrc/sqlite3.c
@@ -1,8 +1,8 @@
11
/******************************************************************************
22
** This file is an amalgamation of many separate C source files from SQLite
3
-** version 3.47.0. By combining all the individual C code files into this
3
+** version 3.48.0. By combining all the individual C code files into this
44
** single large file, the entire code can be compiled as a single translation
55
** unit. This allows many compilers to do optimizations that would not be
66
** possible if the files were compiled separately. Performance improvements
77
** of 5% or more are commonly seen when SQLite is compiled as a single
88
** translation unit.
@@ -16,12 +16,15 @@
1616
** if you want a wrapper to interface SQLite with your choice of programming
1717
** language. The code for the "sqlite3" command-line shell is also in a
1818
** separate file. This file contains only code for the core SQLite library.
1919
**
2020
** The content in this amalgamation comes from Fossil check-in
21
-** 03a9703e27c44437c39363d0baf82db4ebc9.
21
+** 2b17bc49655c577029919c2d409de994b0d2 with changes in files:
22
+**
23
+**
2224
*/
25
+#ifndef SQLITE_AMALGAMATION
2326
#define SQLITE_CORE 1
2427
#define SQLITE_AMALGAMATION 1
2528
#ifndef SQLITE_PRIVATE
2629
# define SQLITE_PRIVATE static
2730
#endif
@@ -460,13 +463,13 @@
460463
**
461464
** See also: [sqlite3_libversion()],
462465
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
463466
** [sqlite_version()] and [sqlite_source_id()].
464467
*/
465
-#define SQLITE_VERSION "3.47.0"
466
-#define SQLITE_VERSION_NUMBER 3047000
467
-#define SQLITE_SOURCE_ID "2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f28411c85dda156f82636e"
468
+#define SQLITE_VERSION "3.48.0"
469
+#define SQLITE_VERSION_NUMBER 3048000
470
+#define SQLITE_SOURCE_ID "2024-12-30 21:23:53 2b17bc49655c577029919c2d409de994b0d252f8efb5da1ba0913f2c96bee552"
468471
469472
/*
470473
** CAPI3REF: Run-Time Library Version Numbers
471474
** KEYWORDS: sqlite3_version sqlite3_sourceid
472475
**
@@ -966,10 +969,17 @@
966969
**
967970
** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying
968971
** filesystem supports doing multiple write operations atomically when those
969972
** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and
970973
** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE].
974
+**
975
+** The SQLITE_IOCAP_SUBPAGE_READ property means that it is ok to read
976
+** from the database file in amounts that are not a multiple of the
977
+** page size and that do not begin at a page boundary. Without this
978
+** property, SQLite is careful to only do full-page reads and write
979
+** on aligned pages, with the one exception that it will do a sub-page
980
+** read of the first page to access the database header.
971981
*/
972982
#define SQLITE_IOCAP_ATOMIC 0x00000001
973983
#define SQLITE_IOCAP_ATOMIC512 0x00000002
974984
#define SQLITE_IOCAP_ATOMIC1K 0x00000004
975985
#define SQLITE_IOCAP_ATOMIC2K 0x00000008
@@ -982,10 +992,11 @@
982992
#define SQLITE_IOCAP_SEQUENTIAL 0x00000400
983993
#define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800
984994
#define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000
985995
#define SQLITE_IOCAP_IMMUTABLE 0x00002000
986996
#define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000
997
+#define SQLITE_IOCAP_SUBPAGE_READ 0x00008000
987998
988999
/*
9891000
** CAPI3REF: File Locking Levels
9901001
**
9911002
** SQLite uses one of these integer values as the second
@@ -1128,10 +1139,11 @@
11281139
** <li> [SQLITE_IOCAP_SEQUENTIAL]
11291140
** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN]
11301141
** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE]
11311142
** <li> [SQLITE_IOCAP_IMMUTABLE]
11321143
** <li> [SQLITE_IOCAP_BATCH_ATOMIC]
1144
+** <li> [SQLITE_IOCAP_SUBPAGE_READ]
11331145
** </ul>
11341146
**
11351147
** The SQLITE_IOCAP_ATOMIC property means that all writes of
11361148
** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values
11371149
** mean that writes of blocks that are nnn bytes in size and
@@ -1405,10 +1417,15 @@
14051417
** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This
14061418
** opcode causes the xFileControl method to swap the file handle with the one
14071419
** pointed to by the pArg argument. This capability is used during testing
14081420
** and only needs to be supported when SQLITE_TEST is defined.
14091421
**
1422
+** <li>[[SQLITE_FCNTL_NULL_IO]]
1423
+** The [SQLITE_FCNTL_NULL_IO] opcode sets the low-level file descriptor
1424
+** or file handle for the [sqlite3_file] object such that it will no longer
1425
+** read or write to the database file.
1426
+**
14101427
** <li>[[SQLITE_FCNTL_WAL_BLOCK]]
14111428
** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might
14121429
** be advantageous to block on the next WAL lock if the lock is not immediately
14131430
** available. The WAL subsystem issues this signal during rare
14141431
** circumstances in order to fix a problem with priority inversion.
@@ -1558,10 +1575,11 @@
15581575
#define SQLITE_FCNTL_RESERVE_BYTES 38
15591576
#define SQLITE_FCNTL_CKPT_START 39
15601577
#define SQLITE_FCNTL_EXTERNAL_READER 40
15611578
#define SQLITE_FCNTL_CKSM_FILE 41
15621579
#define SQLITE_FCNTL_RESET_CACHE 42
1580
+#define SQLITE_FCNTL_NULL_IO 43
15631581
15641582
/* deprecated names */
15651583
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
15661584
#define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE
15671585
#define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO
@@ -2936,14 +2954,18 @@
29362954
**
29372955
** ^These functions return the number of rows modified, inserted or
29382956
** deleted by the most recently completed INSERT, UPDATE or DELETE
29392957
** statement on the database connection specified by the only parameter.
29402958
** The two functions are identical except for the type of the return value
2941
-** and that if the number of rows modified by the most recent INSERT, UPDATE
2959
+** and that if the number of rows modified by the most recent INSERT, UPDATE,
29422960
** or DELETE is greater than the maximum value supported by type "int", then
29432961
** the return value of sqlite3_changes() is undefined. ^Executing any other
29442962
** type of SQL statement does not modify the value returned by these functions.
2963
+** For the purposes of this interface, a CREATE TABLE AS SELECT statement
2964
+** does not count as an INSERT, UPDATE or DELETE statement and hence the rows
2965
+** added to the new table by the CREATE TABLE AS SELECT statement are not
2966
+** counted.
29452967
**
29462968
** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are
29472969
** considered - auxiliary changes caused by [CREATE TRIGGER | triggers],
29482970
** [foreign key actions] or [REPLACE] constraint resolution are not counted.
29492971
**
@@ -4499,15 +4521,26 @@
44994521
**
45004522
** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt>
45014523
** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler
45024524
** to return an error (error code SQLITE_ERROR) if the statement uses
45034525
** any virtual tables.
4526
+**
4527
+** [[SQLITE_PREPARE_DONT_LOG]] <dt>SQLITE_PREPARE_DONT_LOG</dt>
4528
+** <dd>The SQLITE_PREPARE_DONT_LOG flag prevents SQL compiler
4529
+** errors from being sent to the error log defined by
4530
+** [SQLITE_CONFIG_LOG]. This can be used, for example, to do test
4531
+** compiles to see if some SQL syntax is well-formed, without generating
4532
+** messages on the global error log when it is not. If the test compile
4533
+** fails, the sqlite3_prepare_v3() call returns the same error indications
4534
+** with or without this flag; it just omits the call to [sqlite3_log()] that
4535
+** logs the error.
45044536
** </dl>
45054537
*/
45064538
#define SQLITE_PREPARE_PERSISTENT 0x01
45074539
#define SQLITE_PREPARE_NORMALIZE 0x02
45084540
#define SQLITE_PREPARE_NO_VTAB 0x04
4541
+#define SQLITE_PREPARE_DONT_LOG 0x10
45094542
45104543
/*
45114544
** CAPI3REF: Compiling An SQL Statement
45124545
** KEYWORDS: {SQL statement compiler}
45134546
** METHOD: sqlite3
@@ -11194,11 +11227,11 @@
1119411227
#endif
1119511228
1119611229
#if 0
1119711230
} /* End of the 'extern "C"' block */
1119811231
#endif
11199
-#endif /* SQLITE3_H */
11232
+/* #endif for SQLITE3_H will be added by mksqlite3.tcl */
1120011233
1120111234
/******** Begin file sqlite3rtree.h *********/
1120211235
/*
1120311236
** 2010 August 30
1120411237
**
@@ -13445,17 +13478,32 @@
1344513478
** This is used to access token iToken of phrase hit iIdx within the
1344613479
** current row. If iIdx is less than zero or greater than or equal to the
1344713480
** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise,
1344813481
** output variable (*ppToken) is set to point to a buffer containing the
1344913482
** matching document token, and (*pnToken) to the size of that buffer in
13450
-** bytes. This API is not available if the specified token matches a
13451
-** prefix query term. In that case both output variables are always set
13452
-** to 0.
13483
+** bytes.
1345313484
**
1345413485
** The output text is not a copy of the document text that was tokenized.
1345513486
** It is the output of the tokenizer module. For tokendata=1 tables, this
1345613487
** includes any embedded 0x00 and trailing data.
13488
+**
13489
+** This API may be slow in some cases if the token identified by parameters
13490
+** iIdx and iToken matched a prefix token in the query. In most cases, the
13491
+** first call to this API for each prefix token in the query is forced
13492
+** to scan the portion of the full-text index that matches the prefix
13493
+** token to collect the extra data required by this API. If the prefix
13494
+** token matches a large number of token instances in the document set,
13495
+** this may be a performance problem.
13496
+**
13497
+** If the user knows in advance that a query may use this API for a
13498
+** prefix token, FTS5 may be configured to collect all required data as part
13499
+** of the initial querying of the full-text index, avoiding the second scan
13500
+** entirely. This also causes prefix queries that do not use this API to
13501
+** run more slowly and use more memory. FTS5 may be configured in this way
13502
+** either on a per-table basis using the [FTS5 insttoken | 'insttoken']
13503
+** option, or on a per-query basis using the
13504
+** [fts5_insttoken | fts5_insttoken()] user function.
1345713505
**
1345813506
** This API can be quite slow if used with an FTS5 table created with the
1345913507
** "detail=none" or "detail=column" option.
1346013508
**
1346113509
** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale)
@@ -13886,10 +13934,11 @@
1388613934
#endif
1388713935
1388813936
#endif /* _FTS5_H */
1388913937
1389013938
/******** End of fts5.h *********/
13939
+#endif /* SQLITE3_H */
1389113940
1389213941
/************** End of sqlite3.h *********************************************/
1389313942
/************** Continuing where we left off in sqliteInt.h ******************/
1389413943
1389513944
/*
@@ -13931,10 +13980,11 @@
1393113980
** to count the size: 2^31-1 or 2147483647.
1393213981
*/
1393313982
#ifndef SQLITE_MAX_LENGTH
1393413983
# define SQLITE_MAX_LENGTH 1000000000
1393513984
#endif
13985
+#define SQLITE_MIN_LENGTH 30 /* Minimum value for the length limit */
1393613986
1393713987
/*
1393813988
** This is the maximum number of
1393913989
**
1394013990
** * Columns in a table
@@ -13996,13 +14046,17 @@
1399614046
# define SQLITE_MAX_VDBE_OP 250000000
1399714047
#endif
1399814048
1399914049
/*
1400014050
** The maximum number of arguments to an SQL function.
14051
+**
14052
+** This value has a hard upper limit of 32767 due to storage
14053
+** constraints (it needs to fit inside a i16). We keep it
14054
+** lower than that to prevent abuse.
1400114055
*/
1400214056
#ifndef SQLITE_MAX_FUNCTION_ARG
14003
-# define SQLITE_MAX_FUNCTION_ARG 127
14057
+# define SQLITE_MAX_FUNCTION_ARG 1000
1400414058
#endif
1400514059
1400614060
/*
1400714061
** The suggested maximum number of in-memory pages to use for
1400814062
** the main database table and for temporary tables.
@@ -16000,10 +16054,26 @@
1600016054
#define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */
1600116055
#define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */
1600216056
#define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */
1600316057
#define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */
1600416058
16059
+#define isWalMode(x) ((x)==PAGER_JOURNALMODE_WAL)
16060
+
16061
+/*
16062
+** The argument to this macro is a file descriptor (type sqlite3_file*).
16063
+** Return 0 if it is not open, or non-zero (but not 1) if it is.
16064
+**
16065
+** This is so that expressions can be written as:
16066
+**
16067
+** if( isOpen(pPager->jfd) ){ ...
16068
+**
16069
+** instead of
16070
+**
16071
+** if( pPager->jfd->pMethods ){ ...
16072
+*/
16073
+#define isOpen(pFd) ((pFd)->pMethods!=0)
16074
+
1600516075
/*
1600616076
** Flags that make up the mask passed to sqlite3PagerGet().
1600716077
*/
1600816078
#define PAGER_GET_NOCONTENT 0x01 /* Do not load data from disk */
1600916079
#define PAGER_GET_READONLY 0x02 /* Read-only page is acceptable */
@@ -17025,11 +17095,11 @@
1702517095
1702617096
/*
1702717097
** Additional non-public SQLITE_PREPARE_* flags
1702817098
*/
1702917099
#define SQLITE_PREPARE_SAVESQL 0x80 /* Preserve SQL text */
17030
-#define SQLITE_PREPARE_MASK 0x0f /* Mask of public flags */
17100
+#define SQLITE_PREPARE_MASK 0x1f /* Mask of public flags */
1703117101
1703217102
/*
1703317103
** Prototypes for the VDBE interface. See comments on the implementation
1703417104
** for a description of what each of these routines does.
1703517105
*/
@@ -17740,51 +17810,15 @@
1774017810
struct FuncDefHash {
1774117811
FuncDef *a[SQLITE_FUNC_HASH_SZ]; /* Hash table for functions */
1774217812
};
1774317813
#define SQLITE_FUNC_HASH(C,L) (((C)+(L))%SQLITE_FUNC_HASH_SZ)
1774417814
17745
-#if defined(SQLITE_USER_AUTHENTICATION)
17746
-# warning "The SQLITE_USER_AUTHENTICATION extension is deprecated. \
17747
- See ext/userauth/user-auth.txt for details."
17748
-#endif
17749
-#ifdef SQLITE_USER_AUTHENTICATION
17750
-/*
17751
-** Information held in the "sqlite3" database connection object and used
17752
-** to manage user authentication.
17753
-*/
17754
-typedef struct sqlite3_userauth sqlite3_userauth;
17755
-struct sqlite3_userauth {
17756
- u8 authLevel; /* Current authentication level */
17757
- int nAuthPW; /* Size of the zAuthPW in bytes */
17758
- char *zAuthPW; /* Password used to authenticate */
17759
- char *zAuthUser; /* User name used to authenticate */
17760
-};
17761
-
17762
-/* Allowed values for sqlite3_userauth.authLevel */
17763
-#define UAUTH_Unknown 0 /* Authentication not yet checked */
17764
-#define UAUTH_Fail 1 /* User authentication failed */
17765
-#define UAUTH_User 2 /* Authenticated as a normal user */
17766
-#define UAUTH_Admin 3 /* Authenticated as an administrator */
17767
-
17768
-/* Functions used only by user authorization logic */
17769
-SQLITE_PRIVATE int sqlite3UserAuthTable(const char*);
17770
-SQLITE_PRIVATE int sqlite3UserAuthCheckLogin(sqlite3*,const char*,u8*);
17771
-SQLITE_PRIVATE void sqlite3UserAuthInit(sqlite3*);
17772
-SQLITE_PRIVATE void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**);
17773
-
17774
-#endif /* SQLITE_USER_AUTHENTICATION */
17775
-
1777617815
/*
1777717816
** typedef for the authorization callback function.
1777817817
*/
17779
-#ifdef SQLITE_USER_AUTHENTICATION
17780
- typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*,
17781
- const char*, const char*);
17782
-#else
17783
- typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*,
17784
- const char*);
17785
-#endif
17818
+typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*,
17819
+ const char*);
1778617820
1778717821
#ifndef SQLITE_OMIT_DEPRECATED
1778817822
/* This is an extra SQLITE_TRACE macro that indicates "legacy" tracing
1778917823
** in the style of sqlite3_trace()
1779017824
*/
@@ -17941,13 +17975,10 @@
1794117975
sqlite3 *pUnlockConnection; /* Connection to watch for unlock */
1794217976
void *pUnlockArg; /* Argument to xUnlockNotify */
1794317977
void (*xUnlockNotify)(void **, int); /* Unlock notify callback */
1794417978
sqlite3 *pNextBlocked; /* Next in list of all blocked connections */
1794517979
#endif
17946
-#ifdef SQLITE_USER_AUTHENTICATION
17947
- sqlite3_userauth auth; /* User authentication information */
17948
-#endif
1794917980
};
1795017981
1795117982
/*
1795217983
** A macro to discover the encoding of a database.
1795317984
*/
@@ -18102,11 +18133,11 @@
1810218133
**
1810318134
** The u.pHash field is used by the global built-ins. The u.pDestructor
1810418135
** field is used by per-connection app-def functions.
1810518136
*/
1810618137
struct FuncDef {
18107
- i8 nArg; /* Number of arguments. -1 means unlimited */
18138
+ i16 nArg; /* Number of arguments. -1 means unlimited */
1810818139
u32 funcFlags; /* Some combination of SQLITE_FUNC_* */
1810918140
void *pUserData; /* User data parameter */
1811018141
FuncDef *pNext; /* Next function with same name */
1811118142
void (*xSFunc)(sqlite3_context*,int,sqlite3_value**); /* func or agg-step */
1811218143
void (*xFinalize)(sqlite3_context*); /* Agg finalizer */
@@ -22850,13 +22881,10 @@
2285022881
"UNLINK_AFTER_CLOSE",
2285122882
#endif
2285222883
#ifdef SQLITE_UNTESTABLE
2285322884
"UNTESTABLE",
2285422885
#endif
22855
-#ifdef SQLITE_USER_AUTHENTICATION
22856
- "USER_AUTHENTICATION",
22857
-#endif
2285822886
#ifdef SQLITE_USE_ALLOCA
2285922887
"USE_ALLOCA",
2286022888
#endif
2286122889
#ifdef SQLITE_USE_FCNTL_TRACE
2286222890
"USE_FCNTL_TRACE",
@@ -23700,11 +23728,11 @@
2370023728
Vdbe *pVdbe; /* The VM that owns this context */
2370123729
int iOp; /* Instruction number of OP_Function */
2370223730
int isError; /* Error code returned by the function. */
2370323731
u8 enc; /* Encoding to use for results */
2370423732
u8 skipFlag; /* Skip accumulator loading if true */
23705
- u8 argc; /* Number of arguments */
23733
+ u16 argc; /* Number of arguments */
2370623734
sqlite3_value *argv[1]; /* Argument set */
2370723735
};
2370823736
2370923737
/* A bitfield type for use inside of structures. Always follow with :N where
2371023738
** N is the number of bits.
@@ -23847,10 +23875,11 @@
2384723875
UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */
2384823876
int iNewReg; /* Register for new.* values */
2384923877
int iBlobWrite; /* Value returned by preupdate_blobwrite() */
2385023878
i64 iKey1; /* First key value passed to hook */
2385123879
i64 iKey2; /* Second key value passed to hook */
23880
+ Mem oldipk; /* Memory cell holding "old" IPK value */
2385223881
Mem *aNew; /* Array of new.* values */
2385323882
Table *pTab; /* Schema object being updated */
2385423883
Index *pPk; /* PK index if pTab is WITHOUT ROWID */
2385523884
sqlite3_value **apDflt; /* Array of default values, if required */
2385623885
};
@@ -32296,10 +32325,11 @@
3229632325
&& (ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0)
3229732326
){
3229832327
pExpr = pExpr->pLeft;
3229932328
}
3230032329
if( pExpr==0 ) return;
32330
+ if( ExprHasProperty(pExpr, EP_FromDDL) ) return;
3230132331
db->errByteOffset = pExpr->w.iOfst;
3230232332
}
3230332333
3230432334
/*
3230532335
** Enlarge the memory allocation on a StrAccum object so that it is
@@ -33025,11 +33055,11 @@
3302533055
}
3302633056
if( pItem->fg.isCte ){
3302733057
sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse);
3302833058
}
3302933059
if( pItem->fg.isOn || (pItem->fg.isUsing==0 && pItem->u3.pOn!=0) ){
33030
- sqlite3_str_appendf(&x, " ON");
33060
+ sqlite3_str_appendf(&x, " isOn");
3303133061
}
3303233062
if( pItem->fg.isTabFunc ) sqlite3_str_appendf(&x, " isTabFunc");
3303333063
if( pItem->fg.isCorrelated ) sqlite3_str_appendf(&x, " isCorrelated");
3303433064
if( pItem->fg.isMaterialized ) sqlite3_str_appendf(&x, " isMaterialized");
3303533065
if( pItem->fg.viaCoroutine ) sqlite3_str_appendf(&x, " viaCoroutine");
@@ -34109,10 +34139,14 @@
3410934139
**
3411034140
** This routines are given external linkage so that they will always be
3411134141
** accessible to the debugging, and to avoid warnings about unused
3411234142
** functions. But these routines only exist in debugging builds, so they
3411334143
** do not contaminate the interface.
34144
+**
34145
+** See Also:
34146
+**
34147
+** sqlite3ShowWhereTerm() in where.c
3411434148
*/
3411534149
SQLITE_PRIVATE void sqlite3ShowExpr(const Expr *p){ sqlite3TreeViewExpr(0,p,0); }
3411634150
SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList *p){ sqlite3TreeViewExprList(0,p,0,0);}
3411734151
SQLITE_PRIVATE void sqlite3ShowIdList(const IdList *p){ sqlite3TreeViewIdList(0,p,0,0); }
3411834152
SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList *p){ sqlite3TreeViewSrcList(0,p); }
@@ -35685,12 +35719,12 @@
3568535719
int esign = 1; /* sign of exponent */
3568635720
int e = 0; /* exponent */
3568735721
int eValid = 1; /* True exponent is either not used or is well-formed */
3568835722
int nDigit = 0; /* Number of digits processed */
3568935723
int eType = 1; /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */
35724
+ u64 s2; /* round-tripped significand */
3569035725
double rr[2];
35691
- u64 s2;
3569235726
3569335727
assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE );
3569435728
*pResult = 0.0; /* Default return value, in case of an error */
3569535729
if( length==0 ) return 0;
3569635730
@@ -35789,25 +35823,36 @@
3578935823
3579035824
/* adjust exponent by d, and update sign */
3579135825
e = (e*esign) + d;
3579235826
3579335827
/* Try to adjust the exponent to make it smaller */
35794
- while( e>0 && s<(LARGEST_UINT64/10) ){
35828
+ while( e>0 && s<((LARGEST_UINT64-0x7ff)/10) ){
3579535829
s *= 10;
3579635830
e--;
3579735831
}
3579835832
while( e<0 && (s%10)==0 ){
3579935833
s /= 10;
3580035834
e++;
3580135835
}
3580235836
3580335837
rr[0] = (double)s;
35804
- s2 = (u64)rr[0];
35805
-#if defined(_MSC_VER) && _MSC_VER<1700
35806
- if( s2==0x8000000000000000LL ){ s2 = 2*(u64)(0.5*rr[0]); }
35838
+ assert( sizeof(s2)==sizeof(rr[0]) );
35839
+#ifdef SQLITE_DEBUG
35840
+ rr[1] = 18446744073709549568.0;
35841
+ memcpy(&s2, &rr[1], sizeof(s2));
35842
+ assert( s2==0x43efffffffffffffLL );
3580735843
#endif
35808
- rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s);
35844
+ /* Largest double that can be safely converted to u64
35845
+ ** vvvvvvvvvvvvvvvvvvvvvv */
35846
+ if( rr[0]<=18446744073709549568.0 ){
35847
+ s2 = (u64)rr[0];
35848
+ rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s);
35849
+ }else{
35850
+ rr[1] = 0.0;
35851
+ }
35852
+ assert( rr[1]<=1.0e-10*rr[0] ); /* Equal only when rr[0]==0.0 */
35853
+
3580935854
if( e>0 ){
3581035855
while( e>=100 ){
3581135856
e -= 100;
3581235857
dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83);
3581335858
}
@@ -38674,11 +38719,11 @@
3867438719
# define F_SETLKW 7
3867538720
# endif
3867638721
# endif
3867738722
#else /* !SQLITE_WASI */
3867838723
# ifndef HAVE_FCHMOD
38679
-# define HAVE_FCHMOD
38724
+# define HAVE_FCHMOD 1
3868038725
# endif
3868138726
#endif /* SQLITE_WASI */
3868238727
3868338728
#ifdef SQLITE_WASI
3868438729
# define osGetpid(X) (pid_t)1
@@ -42448,10 +42493,15 @@
4244842493
int rc = osIoctl(pFile->h, F2FS_IOC_ABORT_VOLATILE_WRITE);
4244942494
return rc ? SQLITE_IOERR_ROLLBACK_ATOMIC : SQLITE_OK;
4245042495
}
4245142496
#endif /* __linux__ && SQLITE_ENABLE_BATCH_ATOMIC_WRITE */
4245242497
42498
+ case SQLITE_FCNTL_NULL_IO: {
42499
+ osClose(pFile->h);
42500
+ pFile->h = -1;
42501
+ return SQLITE_OK;
42502
+ }
4245342503
case SQLITE_FCNTL_LOCKSTATE: {
4245442504
*(int*)pArg = pFile->eFileLock;
4245542505
return SQLITE_OK;
4245642506
}
4245742507
case SQLITE_FCNTL_LAST_ERRNO: {
@@ -42589,10 +42639,11 @@
4258942639
4259042640
/* Set the POWERSAFE_OVERWRITE flag if requested. */
4259142641
if( pFd->ctrlFlags & UNIXFILE_PSOW ){
4259242642
pFd->deviceCharacteristics |= SQLITE_IOCAP_POWERSAFE_OVERWRITE;
4259342643
}
42644
+ pFd->deviceCharacteristics |= SQLITE_IOCAP_SUBPAGE_READ;
4259442645
4259542646
pFd->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE;
4259642647
}
4259742648
}
4259842649
#else
@@ -50328,10 +50379,15 @@
5032850379
OSTRACE(("FCNTL oldFile=%p, newFile=%p, rc=SQLITE_OK\n",
5032950380
hOldFile, pFile->h));
5033050381
return SQLITE_OK;
5033150382
}
5033250383
#endif
50384
+ case SQLITE_FCNTL_NULL_IO: {
50385
+ (void)osCloseHandle(pFile->h);
50386
+ pFile->h = NULL;
50387
+ return SQLITE_OK;
50388
+ }
5033350389
case SQLITE_FCNTL_TEMPFILENAME: {
5033450390
char *zTFile = 0;
5033550391
int rc = winGetTempname(pFile->pVfs, &zTFile);
5033650392
if( rc==SQLITE_OK ){
5033750393
*(char**)pArg = zTFile;
@@ -50389,11 +50445,11 @@
5038950445
/*
5039050446
** Return a vector of device characteristics.
5039150447
*/
5039250448
static int winDeviceCharacteristics(sqlite3_file *id){
5039350449
winFile *p = (winFile*)id;
50394
- return SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN |
50450
+ return SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN | SQLITE_IOCAP_SUBPAGE_READ |
5039550451
((p->ctrlFlags & WINFILE_PSOW)?SQLITE_IOCAP_POWERSAFE_OVERWRITE:0);
5039650452
}
5039750453
5039850454
/*
5039950455
** Windows will only let you create file view mappings
@@ -51777,11 +51833,11 @@
5177751833
*/
5177851834
char *zTmpname = 0; /* For temporary filename, if necessary. */
5177951835
5178051836
int rc = SQLITE_OK; /* Function Return Code */
5178151837
#if !defined(NDEBUG) || SQLITE_OS_WINCE
51782
- int eType = flags&0xFFFFFF00; /* Type of file to open */
51838
+ int eType = flags&0x0FFF00; /* Type of file to open */
5178351839
#endif
5178451840
5178551841
int isExclusive = (flags & SQLITE_OPEN_EXCLUSIVE);
5178651842
int isDelete = (flags & SQLITE_OPEN_DELETEONCLOSE);
5178751843
int isCreate = (flags & SQLITE_OPEN_CREATE);
@@ -57978,43 +58034,37 @@
5797858034
# define USEFETCH(x) ((x)->bUseFetch)
5797958035
#else
5798058036
# define USEFETCH(x) 0
5798158037
#endif
5798258038
57983
-/*
57984
-** The argument to this macro is a file descriptor (type sqlite3_file*).
57985
-** Return 0 if it is not open, or non-zero (but not 1) if it is.
57986
-**
57987
-** This is so that expressions can be written as:
57988
-**
57989
-** if( isOpen(pPager->jfd) ){ ...
57990
-**
57991
-** instead of
57992
-**
57993
-** if( pPager->jfd->pMethods ){ ...
57994
-*/
57995
-#define isOpen(pFd) ((pFd)->pMethods!=0)
57996
-
5799758039
#ifdef SQLITE_DIRECT_OVERFLOW_READ
5799858040
/*
5799958041
** Return true if page pgno can be read directly from the database file
5800058042
** by the b-tree layer. This is the case if:
5800158043
**
58002
-** * the database file is open,
58003
-** * there are no dirty pages in the cache, and
58004
-** * the desired page is not currently in the wal file.
58044
+** (1) the database file is open
58045
+** (2) the VFS for the database is able to do unaligned sub-page reads
58046
+** (3) there are no dirty pages in the cache, and
58047
+** (4) the desired page is not currently in the wal file.
5800558048
*/
5800658049
SQLITE_PRIVATE int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){
58007
- if( pPager->fd->pMethods==0 ) return 0;
58008
- if( sqlite3PCacheIsDirty(pPager->pPCache) ) return 0;
58050
+ assert( pPager!=0 );
58051
+ assert( pPager->fd!=0 );
58052
+ if( pPager->fd->pMethods==0 ) return 0; /* Case (1) */
58053
+ if( sqlite3PCacheIsDirty(pPager->pPCache) ) return 0; /* Failed (3) */
5800958054
#ifndef SQLITE_OMIT_WAL
5801058055
if( pPager->pWal ){
5801158056
u32 iRead = 0;
5801258057
(void)sqlite3WalFindFrame(pPager->pWal, pgno, &iRead);
58013
- return iRead==0;
58058
+ return iRead==0; /* Condition (4) */
5801458059
}
5801558060
#endif
58061
+ assert( pPager->fd->pMethods->xDeviceCharacteristics!=0 );
58062
+ if( (pPager->fd->pMethods->xDeviceCharacteristics(pPager->fd)
58063
+ & SQLITE_IOCAP_SUBPAGE_READ)==0 ){
58064
+ return 0; /* Case (2) */
58065
+ }
5801658066
return 1;
5801758067
}
5801858068
#endif
5801958069
5802058070
#ifndef SQLITE_OMIT_WAL
@@ -59269,11 +59319,11 @@
5926959319
rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags);
5927059320
}
5927159321
}
5927259322
pPager->journalOff = 0;
5927359323
}else if( pPager->journalMode==PAGER_JOURNALMODE_PERSIST
59274
- || (pPager->exclusiveMode && pPager->journalMode!=PAGER_JOURNALMODE_WAL)
59324
+ || (pPager->exclusiveMode && pPager->journalMode<PAGER_JOURNALMODE_WAL)
5927559325
){
5927659326
rc = zeroJournalHdr(pPager, hasSuper||pPager->tempFile);
5927759327
pPager->journalOff = 0;
5927859328
}else{
5927959329
/* This branch may be executed with Pager.journalMode==MEMORY if
@@ -67979,15 +68029,11 @@
6797968029
** so it takes care to hold an exclusive lock on the corresponding
6798068030
** WAL_READ_LOCK() while changing values.
6798168031
*/
6798268032
static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){
6798368033
volatile WalCkptInfo *pInfo; /* Checkpoint information in wal-index */
67984
- u32 mxReadMark; /* Largest aReadMark[] value */
67985
- int mxI; /* Index of largest aReadMark[] value */
67986
- int i; /* Loop counter */
6798768034
int rc = SQLITE_OK; /* Return code */
67988
- u32 mxFrame; /* Wal frame to lock to */
6798968035
#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
6799068036
int nBlockTmout = 0;
6799168037
#endif
6799268038
6799368039
assert( pWal->readLock<0 ); /* Not currently locked */
@@ -68089,145 +68135,151 @@
6808968135
6809068136
assert( pWal->nWiData>0 );
6809168137
assert( pWal->apWiData[0]!=0 );
6809268138
pInfo = walCkptInfo(pWal);
6809368139
SEH_INJECT_FAULT;
68094
- if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame
68095
-#ifdef SQLITE_ENABLE_SNAPSHOT
68096
- && ((pWal->bGetSnapshot==0 && pWal->pSnapshot==0) || pWal->hdr.mxFrame==0)
68097
-#endif
68098
- ){
68099
- /* The WAL has been completely backfilled (or it is empty).
68100
- ** and can be safely ignored.
68101
- */
68102
- rc = walLockShared(pWal, WAL_READ_LOCK(0));
68103
- walShmBarrier(pWal);
68104
- if( rc==SQLITE_OK ){
68105
- if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) ){
68106
- /* It is not safe to allow the reader to continue here if frames
68107
- ** may have been appended to the log before READ_LOCK(0) was obtained.
68108
- ** When holding READ_LOCK(0), the reader ignores the entire log file,
68109
- ** which implies that the database file contains a trustworthy
68110
- ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from
68111
- ** happening, this is usually correct.
68112
- **
68113
- ** However, if frames have been appended to the log (or if the log
68114
- ** is wrapped and written for that matter) before the READ_LOCK(0)
68115
- ** is obtained, that is not necessarily true. A checkpointer may
68116
- ** have started to backfill the appended frames but crashed before
68117
- ** it finished. Leaving a corrupt image in the database file.
68118
- */
68119
- walUnlockShared(pWal, WAL_READ_LOCK(0));
68120
- return WAL_RETRY;
68121
- }
68122
- pWal->readLock = 0;
68123
- return SQLITE_OK;
68124
- }else if( rc!=SQLITE_BUSY ){
68125
- return rc;
68126
- }
68127
- }
68128
-
68129
- /* If we get this far, it means that the reader will want to use
68130
- ** the WAL to get at content from recent commits. The job now is
68131
- ** to select one of the aReadMark[] entries that is closest to
68132
- ** but not exceeding pWal->hdr.mxFrame and lock that entry.
68133
- */
68134
- mxReadMark = 0;
68135
- mxI = 0;
68136
- mxFrame = pWal->hdr.mxFrame;
68137
-#ifdef SQLITE_ENABLE_SNAPSHOT
68138
- if( pWal->pSnapshot && pWal->pSnapshot->mxFrame<mxFrame ){
68139
- mxFrame = pWal->pSnapshot->mxFrame;
68140
- }
68141
-#endif
68142
- for(i=1; i<WAL_NREADER; i++){
68143
- u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT;
68144
- if( mxReadMark<=thisMark && thisMark<=mxFrame ){
68145
- assert( thisMark!=READMARK_NOT_USED );
68146
- mxReadMark = thisMark;
68147
- mxI = i;
68148
- }
68149
- }
68150
- if( (pWal->readOnly & WAL_SHM_RDONLY)==0
68151
- && (mxReadMark<mxFrame || mxI==0)
68152
- ){
68153
- for(i=1; i<WAL_NREADER; i++){
68154
- rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
68155
- if( rc==SQLITE_OK ){
68156
- AtomicStore(pInfo->aReadMark+i,mxFrame);
68157
- mxReadMark = mxFrame;
68158
- mxI = i;
68159
- walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1);
68160
- break;
68161
- }else if( rc!=SQLITE_BUSY ){
68162
- return rc;
68163
- }
68164
- }
68165
- }
68166
- if( mxI==0 ){
68167
- assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 );
68168
- return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT;
68169
- }
68170
-
68171
- (void)walEnableBlockingMs(pWal, nBlockTmout);
68172
- rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
68173
- walDisableBlocking(pWal);
68174
- if( rc ){
68175
-#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
68176
- if( rc==SQLITE_BUSY_TIMEOUT ){
68177
- *pCnt |= WAL_RETRY_BLOCKED_MASK;
68178
- }
68179
-#else
68180
- assert( rc!=SQLITE_BUSY_TIMEOUT );
68181
-#endif
68182
- assert( (rc&0xFF)!=SQLITE_BUSY||rc==SQLITE_BUSY||rc==SQLITE_BUSY_TIMEOUT );
68183
- return (rc&0xFF)==SQLITE_BUSY ? WAL_RETRY : rc;
68184
- }
68185
- /* Now that the read-lock has been obtained, check that neither the
68186
- ** value in the aReadMark[] array or the contents of the wal-index
68187
- ** header have changed.
68188
- **
68189
- ** It is necessary to check that the wal-index header did not change
68190
- ** between the time it was read and when the shared-lock was obtained
68191
- ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility
68192
- ** that the log file may have been wrapped by a writer, or that frames
68193
- ** that occur later in the log than pWal->hdr.mxFrame may have been
68194
- ** copied into the database by a checkpointer. If either of these things
68195
- ** happened, then reading the database with the current value of
68196
- ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry
68197
- ** instead.
68198
- **
68199
- ** Before checking that the live wal-index header has not changed
68200
- ** since it was read, set Wal.minFrame to the first frame in the wal
68201
- ** file that has not yet been checkpointed. This client will not need
68202
- ** to read any frames earlier than minFrame from the wal file - they
68203
- ** can be safely read directly from the database file.
68204
- **
68205
- ** Because a ShmBarrier() call is made between taking the copy of
68206
- ** nBackfill and checking that the wal-header in shared-memory still
68207
- ** matches the one cached in pWal->hdr, it is guaranteed that the
68208
- ** checkpointer that set nBackfill was not working with a wal-index
68209
- ** header newer than that cached in pWal->hdr. If it were, that could
68210
- ** cause a problem. The checkpointer could omit to checkpoint
68211
- ** a version of page X that lies before pWal->minFrame (call that version
68212
- ** A) on the basis that there is a newer version (version B) of the same
68213
- ** page later in the wal file. But if version B happens to like past
68214
- ** frame pWal->hdr.mxFrame - then the client would incorrectly assume
68215
- ** that it can read version A from the database file. However, since
68216
- ** we can guarantee that the checkpointer that set nBackfill could not
68217
- ** see any pages past pWal->hdr.mxFrame, this problem does not come up.
68218
- */
68219
- pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT;
68220
- walShmBarrier(pWal);
68221
- if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark
68222
- || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr))
68223
- ){
68224
- walUnlockShared(pWal, WAL_READ_LOCK(mxI));
68225
- return WAL_RETRY;
68226
- }else{
68227
- assert( mxReadMark<=pWal->hdr.mxFrame );
68228
- pWal->readLock = (i16)mxI;
68140
+ {
68141
+ u32 mxReadMark; /* Largest aReadMark[] value */
68142
+ int mxI; /* Index of largest aReadMark[] value */
68143
+ int i; /* Loop counter */
68144
+ u32 mxFrame; /* Wal frame to lock to */
68145
+ if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame
68146
+#ifdef SQLITE_ENABLE_SNAPSHOT
68147
+ && ((pWal->bGetSnapshot==0 && pWal->pSnapshot==0) || pWal->hdr.mxFrame==0)
68148
+#endif
68149
+ ){
68150
+ /* The WAL has been completely backfilled (or it is empty).
68151
+ ** and can be safely ignored.
68152
+ */
68153
+ rc = walLockShared(pWal, WAL_READ_LOCK(0));
68154
+ walShmBarrier(pWal);
68155
+ if( rc==SQLITE_OK ){
68156
+ if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr,sizeof(WalIndexHdr)) ){
68157
+ /* It is not safe to allow the reader to continue here if frames
68158
+ ** may have been appended to the log before READ_LOCK(0) was obtained.
68159
+ ** When holding READ_LOCK(0), the reader ignores the entire log file,
68160
+ ** which implies that the database file contains a trustworthy
68161
+ ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from
68162
+ ** happening, this is usually correct.
68163
+ **
68164
+ ** However, if frames have been appended to the log (or if the log
68165
+ ** is wrapped and written for that matter) before the READ_LOCK(0)
68166
+ ** is obtained, that is not necessarily true. A checkpointer may
68167
+ ** have started to backfill the appended frames but crashed before
68168
+ ** it finished. Leaving a corrupt image in the database file.
68169
+ */
68170
+ walUnlockShared(pWal, WAL_READ_LOCK(0));
68171
+ return WAL_RETRY;
68172
+ }
68173
+ pWal->readLock = 0;
68174
+ return SQLITE_OK;
68175
+ }else if( rc!=SQLITE_BUSY ){
68176
+ return rc;
68177
+ }
68178
+ }
68179
+
68180
+ /* If we get this far, it means that the reader will want to use
68181
+ ** the WAL to get at content from recent commits. The job now is
68182
+ ** to select one of the aReadMark[] entries that is closest to
68183
+ ** but not exceeding pWal->hdr.mxFrame and lock that entry.
68184
+ */
68185
+ mxReadMark = 0;
68186
+ mxI = 0;
68187
+ mxFrame = pWal->hdr.mxFrame;
68188
+#ifdef SQLITE_ENABLE_SNAPSHOT
68189
+ if( pWal->pSnapshot && pWal->pSnapshot->mxFrame<mxFrame ){
68190
+ mxFrame = pWal->pSnapshot->mxFrame;
68191
+ }
68192
+#endif
68193
+ for(i=1; i<WAL_NREADER; i++){
68194
+ u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT;
68195
+ if( mxReadMark<=thisMark && thisMark<=mxFrame ){
68196
+ assert( thisMark!=READMARK_NOT_USED );
68197
+ mxReadMark = thisMark;
68198
+ mxI = i;
68199
+ }
68200
+ }
68201
+ if( (pWal->readOnly & WAL_SHM_RDONLY)==0
68202
+ && (mxReadMark<mxFrame || mxI==0)
68203
+ ){
68204
+ for(i=1; i<WAL_NREADER; i++){
68205
+ rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
68206
+ if( rc==SQLITE_OK ){
68207
+ AtomicStore(pInfo->aReadMark+i,mxFrame);
68208
+ mxReadMark = mxFrame;
68209
+ mxI = i;
68210
+ walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1);
68211
+ break;
68212
+ }else if( rc!=SQLITE_BUSY ){
68213
+ return rc;
68214
+ }
68215
+ }
68216
+ }
68217
+ if( mxI==0 ){
68218
+ assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 );
68219
+ return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT;
68220
+ }
68221
+
68222
+ (void)walEnableBlockingMs(pWal, nBlockTmout);
68223
+ rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
68224
+ walDisableBlocking(pWal);
68225
+ if( rc ){
68226
+#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
68227
+ if( rc==SQLITE_BUSY_TIMEOUT ){
68228
+ *pCnt |= WAL_RETRY_BLOCKED_MASK;
68229
+ }
68230
+#else
68231
+ assert( rc!=SQLITE_BUSY_TIMEOUT );
68232
+#endif
68233
+ assert((rc&0xFF)!=SQLITE_BUSY||rc==SQLITE_BUSY||rc==SQLITE_BUSY_TIMEOUT);
68234
+ return (rc&0xFF)==SQLITE_BUSY ? WAL_RETRY : rc;
68235
+ }
68236
+ /* Now that the read-lock has been obtained, check that neither the
68237
+ ** value in the aReadMark[] array or the contents of the wal-index
68238
+ ** header have changed.
68239
+ **
68240
+ ** It is necessary to check that the wal-index header did not change
68241
+ ** between the time it was read and when the shared-lock was obtained
68242
+ ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility
68243
+ ** that the log file may have been wrapped by a writer, or that frames
68244
+ ** that occur later in the log than pWal->hdr.mxFrame may have been
68245
+ ** copied into the database by a checkpointer. If either of these things
68246
+ ** happened, then reading the database with the current value of
68247
+ ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry
68248
+ ** instead.
68249
+ **
68250
+ ** Before checking that the live wal-index header has not changed
68251
+ ** since it was read, set Wal.minFrame to the first frame in the wal
68252
+ ** file that has not yet been checkpointed. This client will not need
68253
+ ** to read any frames earlier than minFrame from the wal file - they
68254
+ ** can be safely read directly from the database file.
68255
+ **
68256
+ ** Because a ShmBarrier() call is made between taking the copy of
68257
+ ** nBackfill and checking that the wal-header in shared-memory still
68258
+ ** matches the one cached in pWal->hdr, it is guaranteed that the
68259
+ ** checkpointer that set nBackfill was not working with a wal-index
68260
+ ** header newer than that cached in pWal->hdr. If it were, that could
68261
+ ** cause a problem. The checkpointer could omit to checkpoint
68262
+ ** a version of page X that lies before pWal->minFrame (call that version
68263
+ ** A) on the basis that there is a newer version (version B) of the same
68264
+ ** page later in the wal file. But if version B happens to like past
68265
+ ** frame pWal->hdr.mxFrame - then the client would incorrectly assume
68266
+ ** that it can read version A from the database file. However, since
68267
+ ** we can guarantee that the checkpointer that set nBackfill could not
68268
+ ** see any pages past pWal->hdr.mxFrame, this problem does not come up.
68269
+ */
68270
+ pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT;
68271
+ walShmBarrier(pWal);
68272
+ if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark
68273
+ || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr))
68274
+ ){
68275
+ walUnlockShared(pWal, WAL_READ_LOCK(mxI));
68276
+ return WAL_RETRY;
68277
+ }else{
68278
+ assert( mxReadMark<=pWal->hdr.mxFrame );
68279
+ pWal->readLock = (i16)mxI;
68280
+ }
6822968281
}
6823068282
return rc;
6823168283
}
6823268284
6823368285
#ifdef SQLITE_ENABLE_SNAPSHOT
@@ -87103,10 +87155,11 @@
8710387155
**
8710487156
** All other fields of Mem can safely remain uninitialized for now. They
8710587157
** will be initialized before use.
8710687158
*/
8710787159
static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){
87160
+ assert( db!=0 );
8710887161
if( N>0 ){
8710987162
do{
8711087163
p->flags = flags;
8711187164
p->db = db;
8711287165
p->szMalloc = 0;
@@ -87128,10 +87181,11 @@
8712887181
*/
8712987182
static void releaseMemArray(Mem *p, int N){
8713087183
if( p && N ){
8713187184
Mem *pEnd = &p[N];
8713287185
sqlite3 *db = p->db;
87186
+ assert( db!=0 );
8713387187
if( db->pnBytesFreed ){
8713487188
do{
8713587189
if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc);
8713687190
}while( (++p)<pEnd );
8713787191
return;
@@ -87608,10 +87662,11 @@
8760887662
assert( p!=0 );
8760987663
assert( p->nOp>0 );
8761087664
assert( pParse!=0 );
8761187665
assert( p->eVdbeState==VDBE_INIT_STATE );
8761287666
assert( pParse==p->pParse );
87667
+ assert( pParse->db==p->db );
8761387668
p->pVList = pParse->pVList;
8761487669
pParse->pVList = 0;
8761587670
db = p->db;
8761687671
assert( db->mallocFailed==0 );
8761787672
nVar = pParse->nVar;
@@ -90488,10 +90543,11 @@
9048890543
db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2);
9048990544
db->pPreUpdate = 0;
9049090545
sqlite3DbFree(db, preupdate.aRecord);
9049190546
vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pUnpacked);
9049290547
vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pNewUnpacked);
90548
+ sqlite3VdbeMemRelease(&preupdate.oldipk);
9049390549
if( preupdate.aNew ){
9049490550
int i;
9049590551
for(i=0; i<pCsr->nField; i++){
9049690552
sqlite3VdbeMemRelease(&preupdate.aNew[i]);
9049790553
}
@@ -91844,11 +91900,11 @@
9184491900
**
9184591901
** sqlite3_column_int()
9184691902
** sqlite3_column_int64()
9184791903
** sqlite3_column_text()
9184891904
** sqlite3_column_text16()
91849
-** sqlite3_column_real()
91905
+** sqlite3_column_double()
9185091906
** sqlite3_column_bytes()
9185191907
** sqlite3_column_bytes16()
9185291908
** sqlite3_column_blob()
9185391909
*/
9185491910
static void columnMallocFailure(sqlite3_stmt *pStmt)
@@ -92706,64 +92762,68 @@
9270692762
if( iIdx>=p->pCsr->nField || iIdx<0 ){
9270792763
rc = SQLITE_RANGE;
9270892764
goto preupdate_old_out;
9270992765
}
9271092766
92711
- /* If the old.* record has not yet been loaded into memory, do so now. */
92712
- if( p->pUnpacked==0 ){
92713
- u32 nRec;
92714
- u8 *aRec;
92715
-
92716
- assert( p->pCsr->eCurType==CURTYPE_BTREE );
92717
- nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor);
92718
- aRec = sqlite3DbMallocRaw(db, nRec);
92719
- if( !aRec ) goto preupdate_old_out;
92720
- rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec);
92721
- if( rc==SQLITE_OK ){
92722
- p->pUnpacked = vdbeUnpackRecord(&p->keyinfo, nRec, aRec);
92723
- if( !p->pUnpacked ) rc = SQLITE_NOMEM;
92724
- }
92725
- if( rc!=SQLITE_OK ){
92726
- sqlite3DbFree(db, aRec);
92727
- goto preupdate_old_out;
92728
- }
92729
- p->aRecord = aRec;
92730
- }
92731
-
92732
- pMem = *ppValue = &p->pUnpacked->aMem[iIdx];
9273392767
if( iIdx==p->pTab->iPKey ){
92768
+ *ppValue = pMem = &p->oldipk;
9273492769
sqlite3VdbeMemSetInt64(pMem, p->iKey1);
92735
- }else if( iIdx>=p->pUnpacked->nField ){
92736
- /* This occurs when the table has been extended using ALTER TABLE
92737
- ** ADD COLUMN. The value to return is the default value of the column. */
92738
- Column *pCol = &p->pTab->aCol[iIdx];
92739
- if( pCol->iDflt>0 ){
92740
- if( p->apDflt==0 ){
92741
- int nByte = sizeof(sqlite3_value*)*p->pTab->nCol;
92742
- p->apDflt = (sqlite3_value**)sqlite3DbMallocZero(db, nByte);
92743
- if( p->apDflt==0 ) goto preupdate_old_out;
92744
- }
92745
- if( p->apDflt[iIdx]==0 ){
92746
- sqlite3_value *pVal = 0;
92747
- Expr *pDflt;
92748
- assert( p->pTab!=0 && IsOrdinaryTable(p->pTab) );
92749
- pDflt = p->pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr;
92750
- rc = sqlite3ValueFromExpr(db, pDflt, ENC(db), pCol->affinity, &pVal);
92751
- if( rc==SQLITE_OK && pVal==0 ){
92752
- rc = SQLITE_CORRUPT_BKPT;
92753
- }
92754
- p->apDflt[iIdx] = pVal;
92755
- }
92756
- *ppValue = p->apDflt[iIdx];
92757
- }else{
92758
- *ppValue = (sqlite3_value *)columnNullValue();
92759
- }
92760
- }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){
92761
- if( pMem->flags & (MEM_Int|MEM_IntReal) ){
92762
- testcase( pMem->flags & MEM_Int );
92763
- testcase( pMem->flags & MEM_IntReal );
92764
- sqlite3VdbeMemRealify(pMem);
92770
+ }else{
92771
+
92772
+ /* If the old.* record has not yet been loaded into memory, do so now. */
92773
+ if( p->pUnpacked==0 ){
92774
+ u32 nRec;
92775
+ u8 *aRec;
92776
+
92777
+ assert( p->pCsr->eCurType==CURTYPE_BTREE );
92778
+ nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor);
92779
+ aRec = sqlite3DbMallocRaw(db, nRec);
92780
+ if( !aRec ) goto preupdate_old_out;
92781
+ rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec);
92782
+ if( rc==SQLITE_OK ){
92783
+ p->pUnpacked = vdbeUnpackRecord(&p->keyinfo, nRec, aRec);
92784
+ if( !p->pUnpacked ) rc = SQLITE_NOMEM;
92785
+ }
92786
+ if( rc!=SQLITE_OK ){
92787
+ sqlite3DbFree(db, aRec);
92788
+ goto preupdate_old_out;
92789
+ }
92790
+ p->aRecord = aRec;
92791
+ }
92792
+
92793
+ pMem = *ppValue = &p->pUnpacked->aMem[iIdx];
92794
+ if( iIdx>=p->pUnpacked->nField ){
92795
+ /* This occurs when the table has been extended using ALTER TABLE
92796
+ ** ADD COLUMN. The value to return is the default value of the column. */
92797
+ Column *pCol = &p->pTab->aCol[iIdx];
92798
+ if( pCol->iDflt>0 ){
92799
+ if( p->apDflt==0 ){
92800
+ int nByte = sizeof(sqlite3_value*)*p->pTab->nCol;
92801
+ p->apDflt = (sqlite3_value**)sqlite3DbMallocZero(db, nByte);
92802
+ if( p->apDflt==0 ) goto preupdate_old_out;
92803
+ }
92804
+ if( p->apDflt[iIdx]==0 ){
92805
+ sqlite3_value *pVal = 0;
92806
+ Expr *pDflt;
92807
+ assert( p->pTab!=0 && IsOrdinaryTable(p->pTab) );
92808
+ pDflt = p->pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr;
92809
+ rc = sqlite3ValueFromExpr(db, pDflt, ENC(db), pCol->affinity, &pVal);
92810
+ if( rc==SQLITE_OK && pVal==0 ){
92811
+ rc = SQLITE_CORRUPT_BKPT;
92812
+ }
92813
+ p->apDflt[iIdx] = pVal;
92814
+ }
92815
+ *ppValue = p->apDflt[iIdx];
92816
+ }else{
92817
+ *ppValue = (sqlite3_value *)columnNullValue();
92818
+ }
92819
+ }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){
92820
+ if( pMem->flags & (MEM_Int|MEM_IntReal) ){
92821
+ testcase( pMem->flags & MEM_Int );
92822
+ testcase( pMem->flags & MEM_IntReal );
92823
+ sqlite3VdbeMemRealify(pMem);
92824
+ }
9276592825
}
9276692826
}
9276792827
9276892828
preupdate_old_out:
9276992829
sqlite3Error(db, rc);
@@ -97913,13 +97973,15 @@
9791397973
0, pCx->uc.pCursor);
9791497974
pCx->isTable = 1;
9791597975
}
9791697976
}
9791797977
pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED);
97978
+ assert( p->apCsr[pOp->p1]==pCx );
9791897979
if( rc ){
9791997980
assert( !sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) );
9792097981
sqlite3BtreeClose(pCx->ub.pBtx);
97982
+ p->apCsr[pOp->p1] = 0; /* Not required; helps with static analysis */
9792197983
}else{
9792297984
assert( sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) );
9792397985
}
9792497986
}
9792597987
}
@@ -109845,11 +109907,11 @@
109845109907
p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight);
109846109908
}
109847109909
p5 = binaryCompareP5(pLeft, pRight, jumpIfNull);
109848109910
addr = sqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1,
109849109911
(void*)p4, P4_COLLSEQ);
109850
- sqlite3VdbeChangeP5(pParse->pVdbe, (u8)p5);
109912
+ sqlite3VdbeChangeP5(pParse->pVdbe, (u16)p5);
109851109913
return addr;
109852109914
}
109853109915
109854109916
/*
109855109917
** Return true if expression pExpr is a vector, or false otherwise.
@@ -112012,11 +112074,11 @@
112012112074
**
112013112075
** (4) If pSrc is the right operand of a LEFT JOIN, then...
112014112076
** (4a) pExpr must come from an ON clause..
112015112077
** (4b) and specifically the ON clause associated with the LEFT JOIN.
112016112078
**
112017
-** (5) If pSrc is not the right operand of a LEFT JOIN or the left
112079
+** (5) If pSrc is the right operand of a LEFT JOIN or the left
112018112080
** operand of a RIGHT JOIN, then pExpr must be from the WHERE
112019112081
** clause, not an ON clause.
112020112082
**
112021112083
** (6) Either:
112022112084
**
@@ -115546,35 +115608,41 @@
115546115608
**
115547115609
** Additionally, if pExpr is a simple SQL value and the value is the
115548115610
** same as that currently bound to variable pVar, non-zero is returned.
115549115611
** Otherwise, if the values are not the same or if pExpr is not a simple
115550115612
** SQL value, zero is returned.
115613
+**
115614
+** If the SQLITE_EnableQPSG flag is set on the database connection, then
115615
+** this routine always returns false.
115551115616
*/
115552
-static int exprCompareVariable(
115617
+static SQLITE_NOINLINE int exprCompareVariable(
115553115618
const Parse *pParse,
115554115619
const Expr *pVar,
115555115620
const Expr *pExpr
115556115621
){
115557
- int res = 0;
115622
+ int res = 2;
115558115623
int iVar;
115559115624
sqlite3_value *pL, *pR = 0;
115560115625
115626
+ if( pExpr->op==TK_VARIABLE && pVar->iColumn==pExpr->iColumn ){
115627
+ return 0;
115628
+ }
115629
+ if( (pParse->db->flags & SQLITE_EnableQPSG)!=0 ) return 2;
115561115630
sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR);
115562115631
if( pR ){
115563115632
iVar = pVar->iColumn;
115564115633
sqlite3VdbeSetVarmask(pParse->pVdbe, iVar);
115565115634
pL = sqlite3VdbeGetBoundValue(pParse->pReprepare, iVar, SQLITE_AFF_BLOB);
115566115635
if( pL ){
115567115636
if( sqlite3_value_type(pL)==SQLITE_TEXT ){
115568115637
sqlite3_value_text(pL); /* Make sure the encoding is UTF-8 */
115569115638
}
115570
- res = 0==sqlite3MemCompare(pL, pR, 0);
115639
+ res = sqlite3MemCompare(pL, pR, 0) ? 2 : 0;
115571115640
}
115572115641
sqlite3ValueFree(pR);
115573115642
sqlite3ValueFree(pL);
115574115643
}
115575
-
115576115644
return res;
115577115645
}
115578115646
115579115647
/*
115580115648
** Do a deep comparison of two expression trees. Return 0 if the two
@@ -115596,16 +115664,14 @@
115596115664
** can be sure the expressions are the same. In the places where
115597115665
** this routine is used, it does not hurt to get an extra 2 - that
115598115666
** just might result in some slightly slower code. But returning
115599115667
** an incorrect 0 or 1 could lead to a malfunction.
115600115668
**
115601
-** If pParse is not NULL then TK_VARIABLE terms in pA with bindings in
115602
-** pParse->pReprepare can be matched against literals in pB. The
115603
-** pParse->pVdbe->expmask bitmask is updated for each variable referenced.
115604
-** If pParse is NULL (the normal case) then any TK_VARIABLE term in
115605
-** Argument pParse should normally be NULL. If it is not NULL and pA or
115606
-** pB causes a return value of 2.
115669
+** If pParse is not NULL and SQLITE_EnableQPSG is off then TK_VARIABLE
115670
+** terms in pA with bindings in pParse->pReprepare can be matched against
115671
+** literals in pB. The pParse->pVdbe->expmask bitmask is updated for
115672
+** each variable referenced.
115607115673
*/
115608115674
SQLITE_PRIVATE int sqlite3ExprCompare(
115609115675
const Parse *pParse,
115610115676
const Expr *pA,
115611115677
const Expr *pB,
@@ -115613,12 +115679,12 @@
115613115679
){
115614115680
u32 combinedFlags;
115615115681
if( pA==0 || pB==0 ){
115616115682
return pB==pA ? 0 : 2;
115617115683
}
115618
- if( pParse && pA->op==TK_VARIABLE && exprCompareVariable(pParse, pA, pB) ){
115619
- return 0;
115684
+ if( pParse && pA->op==TK_VARIABLE ){
115685
+ return exprCompareVariable(pParse, pA, pB);
115620115686
}
115621115687
combinedFlags = pA->flags | pB->flags;
115622115688
if( combinedFlags & EP_IntValue ){
115623115689
if( (pA->flags&pB->flags&EP_IntValue)!=0 && pA->u.iValue==pB->u.iValue ){
115624115690
return 0;
@@ -115808,23 +115874,75 @@
115808115874
return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1);
115809115875
}
115810115876
}
115811115877
return 0;
115812115878
}
115879
+
115880
+/*
115881
+** Return true if the boolean value of the expression is always either
115882
+** FALSE or NULL.
115883
+*/
115884
+static int sqlite3ExprIsNotTrue(Expr *pExpr){
115885
+ int v;
115886
+ if( pExpr->op==TK_NULL ) return 1;
115887
+ if( pExpr->op==TK_TRUEFALSE && sqlite3ExprTruthValue(pExpr)==0 ) return 1;
115888
+ v = 1;
115889
+ if( sqlite3ExprIsInteger(pExpr, &v, 0) && v==0 ) return 1;
115890
+ return 0;
115891
+}
115892
+
115893
+/*
115894
+** Return true if the expression is one of the following:
115895
+**
115896
+** CASE WHEN x THEN y END
115897
+** CASE WHEN x THEN y ELSE NULL END
115898
+** CASE WHEN x THEN y ELSE false END
115899
+** iif(x,y)
115900
+** iif(x,y,NULL)
115901
+** iif(x,y,false)
115902
+*/
115903
+static int sqlite3ExprIsIIF(sqlite3 *db, const Expr *pExpr){
115904
+ ExprList *pList;
115905
+ if( pExpr->op==TK_FUNCTION ){
115906
+ const char *z = pExpr->u.zToken;
115907
+ FuncDef *pDef;
115908
+ if( (z[0]!='i' && z[0]!='I') ) return 0;
115909
+ if( pExpr->x.pList==0 ) return 0;
115910
+ pDef = sqlite3FindFunction(db, z, pExpr->x.pList->nExpr, ENC(db), 0);
115911
+#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
115912
+ if( pDef==0 ) return 0;
115913
+#else
115914
+ if( NEVER(pDef==0) ) return 0;
115915
+#endif
115916
+ if( (pDef->funcFlags & SQLITE_FUNC_INLINE)==0 ) return 0;
115917
+ if( SQLITE_PTR_TO_INT(pDef->pUserData)!=INLINEFUNC_iif ) return 0;
115918
+ }else if( pExpr->op==TK_CASE ){
115919
+ if( pExpr->pLeft!=0 ) return 0;
115920
+ }else{
115921
+ return 0;
115922
+ }
115923
+ pList = pExpr->x.pList;
115924
+ assert( pList!=0 );
115925
+ if( pList->nExpr==2 ) return 1;
115926
+ if( pList->nExpr==3 && sqlite3ExprIsNotTrue(pList->a[2].pExpr) ) return 1;
115927
+ return 0;
115928
+}
115813115929
115814115930
/*
115815115931
** Return true if we can prove the pE2 will always be true if pE1 is
115816115932
** true. Return false if we cannot complete the proof or if pE2 might
115817115933
** be false. Examples:
115818115934
**
115819
-** pE1: x==5 pE2: x==5 Result: true
115820
-** pE1: x>0 pE2: x==5 Result: false
115821
-** pE1: x=21 pE2: x=21 OR y=43 Result: true
115822
-** pE1: x!=123 pE2: x IS NOT NULL Result: true
115823
-** pE1: x!=?1 pE2: x IS NOT NULL Result: true
115824
-** pE1: x IS NULL pE2: x IS NOT NULL Result: false
115825
-** pE1: x IS ?2 pE2: x IS NOT NULL Result: false
115935
+** pE1: x==5 pE2: x==5 Result: true
115936
+** pE1: x>0 pE2: x==5 Result: false
115937
+** pE1: x=21 pE2: x=21 OR y=43 Result: true
115938
+** pE1: x!=123 pE2: x IS NOT NULL Result: true
115939
+** pE1: x!=?1 pE2: x IS NOT NULL Result: true
115940
+** pE1: x IS NULL pE2: x IS NOT NULL Result: false
115941
+** pE1: x IS ?2 pE2: x IS NOT NULL Result: false
115942
+** pE1: iif(x,y) pE2: x Result: true
115943
+** PE1: iif(x,y,0) pE2: x Result: true
115826115944
**
115827115945
** When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has
115828115946
** Expr.iTable<0 then assume a table number given by iTab.
115829115947
**
115830115948
** If pParse is not NULL, then the values of bound variables in pE1 are
@@ -115854,10 +115972,13 @@
115854115972
if( pE2->op==TK_NOTNULL
115855115973
&& exprImpliesNotNull(pParse, pE1, pE2->pLeft, iTab, 0)
115856115974
){
115857115975
return 1;
115858115976
}
115977
+ if( sqlite3ExprIsIIF(pParse->db, pE1) ){
115978
+ return sqlite3ExprImpliesExpr(pParse,pE1->x.pList->a[0].pExpr,pE2,iTab);
115979
+ }
115859115980
return 0;
115860115981
}
115861115982
115862115983
/* This is a helper function to impliesNotNullRow(). In this routine,
115863115984
** set pWalker->eCode to one only if *both* of the input expressions
@@ -121265,19 +121386,10 @@
121265121386
rc = sqlite3Init(db, &zErrDyn);
121266121387
}
121267121388
sqlite3BtreeLeaveAll(db);
121268121389
assert( zErrDyn==0 || rc!=SQLITE_OK );
121269121390
}
121270
-#ifdef SQLITE_USER_AUTHENTICATION
121271
- if( rc==SQLITE_OK && !REOPEN_AS_MEMDB(db) ){
121272
- u8 newAuth = 0;
121273
- rc = sqlite3UserAuthCheckLogin(db, zName, &newAuth);
121274
- if( newAuth<db->auth.authLevel ){
121275
- rc = SQLITE_AUTH_USER;
121276
- }
121277
- }
121278
-#endif
121279121391
if( rc ){
121280121392
if( ALWAYS(!REOPEN_AS_MEMDB(db)) ){
121281121393
int iDb = db->nDb - 1;
121282121394
assert( iDb>=2 );
121283121395
if( db->aDb[iDb].pBt ){
@@ -121771,15 +121883,11 @@
121771121883
sqlite3 *db = pParse->db; /* Database handle */
121772121884
char *zDb = db->aDb[iDb].zDbSName; /* Schema name of attached database */
121773121885
int rc; /* Auth callback return code */
121774121886
121775121887
if( db->init.busy ) return SQLITE_OK;
121776
- rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext
121777
-#ifdef SQLITE_USER_AUTHENTICATION
121778
- ,db->auth.zAuthUser
121779
-#endif
121780
- );
121888
+ rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext);
121781121889
if( rc==SQLITE_DENY ){
121782121890
char *z = sqlite3_mprintf("%s.%s", zTab, zCol);
121783121891
if( db->nDb>2 || iDb!=0 ) z = sqlite3_mprintf("%s.%z", zDb, z);
121784121892
sqlite3ErrorMsg(pParse, "access to %z is prohibited", z);
121785121893
pParse->rc = SQLITE_AUTH;
@@ -121882,15 +121990,11 @@
121882121990
testcase( zArg1==0 );
121883121991
testcase( zArg2==0 );
121884121992
testcase( zArg3==0 );
121885121993
testcase( pParse->zAuthContext==0 );
121886121994
121887
- rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext
121888
-#ifdef SQLITE_USER_AUTHENTICATION
121889
- ,db->auth.zAuthUser
121890
-#endif
121891
- );
121995
+ rc = db->xAuth(db->pAuthArg,code,zArg1,zArg2,zArg3,pParse->zAuthContext);
121892121996
if( rc==SQLITE_DENY ){
121893121997
sqlite3ErrorMsg(pParse, "not authorized");
121894121998
pParse->rc = SQLITE_AUTH;
121895121999
}else if( rc!=SQLITE_OK && rc!=SQLITE_IGNORE ){
121896122000
rc = SQLITE_DENY;
@@ -122119,21 +122223,10 @@
122119122223
sqlite3VdbeJumpHere(v, addrRewind);
122120122224
}
122121122225
}
122122122226
sqlite3VdbeAddOp0(v, OP_Halt);
122123122227
122124
-#if SQLITE_USER_AUTHENTICATION && !defined(SQLITE_OMIT_SHARED_CACHE)
122125
- if( pParse->nTableLock>0 && db->init.busy==0 ){
122126
- sqlite3UserAuthInit(db);
122127
- if( db->auth.authLevel<UAUTH_User ){
122128
- sqlite3ErrorMsg(pParse, "user not authenticated");
122129
- pParse->rc = SQLITE_AUTH_USER;
122130
- return;
122131
- }
122132
- }
122133
-#endif
122134
-
122135122228
/* The cookie mask contains one bit for each database file open.
122136122229
** (Bit 0 is for main, bit 1 is for temp, and so forth.) Bits are
122137122230
** set for each database that is used. Generate code to start a
122138122231
** transaction on each used database and to verify the schema cookie
122139122232
** on each used database.
@@ -122258,20 +122351,10 @@
122258122351
sqlite3DbFree(db, zSql);
122259122352
memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ);
122260122353
pParse->nested--;
122261122354
}
122262122355
122263
-#if SQLITE_USER_AUTHENTICATION
122264
-/*
122265
-** Return TRUE if zTable is the name of the system table that stores the
122266
-** list of users and their access credentials.
122267
-*/
122268
-SQLITE_PRIVATE int sqlite3UserAuthTable(const char *zTable){
122269
- return sqlite3_stricmp(zTable, "sqlite_user")==0;
122270
-}
122271
-#endif
122272
-
122273122356
/*
122274122357
** Locate the in-memory structure that describes a particular database
122275122358
** table given the name of that table and (optionally) the name of the
122276122359
** database containing the table. Return NULL if not found.
122277122360
**
@@ -122286,17 +122369,10 @@
122286122369
Table *p = 0;
122287122370
int i;
122288122371
122289122372
/* All mutexes are required for schema access. Make sure we hold them. */
122290122373
assert( zDatabase!=0 || sqlite3BtreeHoldsAllMutexes(db) );
122291
-#if SQLITE_USER_AUTHENTICATION
122292
- /* Only the admin user is allowed to know that the sqlite_user table
122293
- ** exists */
122294
- if( db->auth.authLevel<UAUTH_Admin && sqlite3UserAuthTable(zName)!=0 ){
122295
- return 0;
122296
- }
122297
-#endif
122298122374
if( zDatabase ){
122299122375
for(i=0; i<db->nDb; i++){
122300122376
if( sqlite3StrICmp(zDatabase, db->aDb[i].zDbSName)==0 ) break;
122301122377
}
122302122378
if( i>=db->nDb ){
@@ -125951,13 +126027,10 @@
125951126027
125952126028
assert( pTab!=0 );
125953126029
if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0
125954126030
&& db->init.busy==0
125955126031
&& pTblName!=0
125956
-#if SQLITE_USER_AUTHENTICATION
125957
- && sqlite3UserAuthTable(pTab->zName)==0
125958
-#endif
125959126032
){
125960126033
sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName);
125961126034
goto exit_create_index;
125962126035
}
125963126036
#ifndef SQLITE_OMIT_VIEW
@@ -129661,20 +129734,19 @@
129661129734
const unsigned char *z;
129662129735
const unsigned char *z2;
129663129736
int len;
129664129737
int p0type;
129665129738
i64 p1, p2;
129666
- int negP2 = 0;
129667129739
129668129740
assert( argc==3 || argc==2 );
129669129741
if( sqlite3_value_type(argv[1])==SQLITE_NULL
129670129742
|| (argc==3 && sqlite3_value_type(argv[2])==SQLITE_NULL)
129671129743
){
129672129744
return;
129673129745
}
129674129746
p0type = sqlite3_value_type(argv[0]);
129675
- p1 = sqlite3_value_int(argv[1]);
129747
+ p1 = sqlite3_value_int64(argv[1]);
129676129748
if( p0type==SQLITE_BLOB ){
129677129749
len = sqlite3_value_bytes(argv[0]);
129678129750
z = sqlite3_value_blob(argv[0]);
129679129751
if( z==0 ) return;
129680129752
assert( len==sqlite3_value_bytes(argv[0]) );
@@ -129695,36 +129767,36 @@
129695129767
** from 2009-02-02 for compatibility of applications that exploited the
129696129768
** old buggy behavior. */
129697129769
if( p1==0 ) p1 = 1; /* <rdar://problem/6778339> */
129698129770
#endif
129699129771
if( argc==3 ){
129700
- p2 = sqlite3_value_int(argv[2]);
129701
- if( p2<0 ){
129702
- p2 = -p2;
129703
- negP2 = 1;
129704
- }
129772
+ p2 = sqlite3_value_int64(argv[2]);
129705129773
}else{
129706129774
p2 = sqlite3_context_db_handle(context)->aLimit[SQLITE_LIMIT_LENGTH];
129707129775
}
129708129776
if( p1<0 ){
129709129777
p1 += len;
129710129778
if( p1<0 ){
129711
- p2 += p1;
129712
- if( p2<0 ) p2 = 0;
129779
+ if( p2<0 ){
129780
+ p2 = 0;
129781
+ }else{
129782
+ p2 += p1;
129783
+ }
129713129784
p1 = 0;
129714129785
}
129715129786
}else if( p1>0 ){
129716129787
p1--;
129717129788
}else if( p2>0 ){
129718129789
p2--;
129719129790
}
129720
- if( negP2 ){
129791
+ if( p2<0 ){
129792
+ if( p2<-p1 ){
129793
+ p2 = p1;
129794
+ }else{
129795
+ p2 = -p2;
129796
+ }
129721129797
p1 -= p2;
129722
- if( p1<0 ){
129723
- p2 += p1;
129724
- p1 = 0;
129725
- }
129726129798
}
129727129799
assert( p1>=0 && p2>=0 );
129728129800
if( p0type!=SQLITE_BLOB ){
129729129801
while( *z && p1 ){
129730129802
SQLITE_SKIP_UTF8(z);
@@ -129734,13 +129806,15 @@
129734129806
SQLITE_SKIP_UTF8(z2);
129735129807
}
129736129808
sqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT,
129737129809
SQLITE_UTF8);
129738129810
}else{
129739
- if( p1+p2>len ){
129811
+ if( p1>=len ){
129812
+ p1 = p2 = 0;
129813
+ }else if( p2>len-p1 ){
129740129814
p2 = len-p1;
129741
- if( p2<0 ) p2 = 0;
129815
+ assert( p2>0 );
129742129816
}
129743129817
sqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT);
129744129818
}
129745129819
}
129746129820
@@ -129747,17 +129821,17 @@
129747129821
/*
129748129822
** Implementation of the round() function
129749129823
*/
129750129824
#ifndef SQLITE_OMIT_FLOATING_POINT
129751129825
static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
129752
- int n = 0;
129826
+ i64 n = 0;
129753129827
double r;
129754129828
char *zBuf;
129755129829
assert( argc==1 || argc==2 );
129756129830
if( argc==2 ){
129757129831
if( SQLITE_NULL==sqlite3_value_type(argv[1]) ) return;
129758
- n = sqlite3_value_int(argv[1]);
129832
+ n = sqlite3_value_int64(argv[1]);
129759129833
if( n>30 ) n = 30;
129760129834
if( n<0 ) n = 0;
129761129835
}
129762129836
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
129763129837
r = sqlite3_value_double(argv[0]);
@@ -129768,11 +129842,11 @@
129768129842
if( r<-4503599627370496.0 || r>+4503599627370496.0 ){
129769129843
/* The value has no fractional part so there is nothing to round */
129770129844
}else if( n==0 ){
129771129845
r = (double)((sqlite_int64)(r+(r<0?-0.5:+0.5)));
129772129846
}else{
129773
- zBuf = sqlite3_mprintf("%!.*f",n,r);
129847
+ zBuf = sqlite3_mprintf("%!.*f",(int)n,r);
129774129848
if( zBuf==0 ){
129775129849
sqlite3_result_error_nomem(context);
129776129850
return;
129777129851
}
129778129852
sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8);
@@ -131985,13 +132059,10 @@
131985132059
#endif
131986132060
#ifndef SQLITE_OMIT_LOAD_EXTENSION
131987132061
SFUNCTION(load_extension, 1, 0, 0, loadExt ),
131988132062
SFUNCTION(load_extension, 2, 0, 0, loadExt ),
131989132063
#endif
131990
-#if SQLITE_USER_AUTHENTICATION
131991
- FUNCTION(sqlite_crypt, 2, 0, 0, sqlite3CryptFunc ),
131992
-#endif
131993132064
#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
131994132065
DFUNCTION(sqlite_compileoption_used,1, 0, 0, compileoptionusedFunc ),
131995132066
DFUNCTION(sqlite_compileoption_get, 1, 0, 0, compileoptiongetFunc ),
131996132067
#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */
131997132068
INLINE_FUNC(unlikely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY),
@@ -132124,11 +132195,14 @@
132124132195
MFUNCTION(degrees, 1, radToDeg, math1Func ),
132125132196
MFUNCTION(pi, 0, 0, piFunc ),
132126132197
#endif /* SQLITE_ENABLE_MATH_FUNCTIONS */
132127132198
FUNCTION(sign, 1, 0, 0, signFunc ),
132128132199
INLINE_FUNC(coalesce, -1, INLINEFUNC_coalesce, 0 ),
132200
+ INLINE_FUNC(iif, 2, INLINEFUNC_iif, 0 ),
132129132201
INLINE_FUNC(iif, 3, INLINEFUNC_iif, 0 ),
132202
+ INLINE_FUNC(if, 2, INLINEFUNC_iif, 0 ),
132203
+ INLINE_FUNC(if, 3, INLINEFUNC_iif, 0 ),
132130132204
};
132131132205
#ifndef SQLITE_OMIT_ALTERTABLE
132132132206
sqlite3AlterFunctions();
132133132207
#endif
132134132208
sqlite3WindowFunctions();
@@ -140637,16 +140711,10 @@
140637140711
if( db->autoCommit==0 ){
140638140712
/* Foreign key support may not be enabled or disabled while not
140639140713
** in auto-commit mode. */
140640140714
mask &= ~(SQLITE_ForeignKeys);
140641140715
}
140642
-#if SQLITE_USER_AUTHENTICATION
140643
- if( db->auth.authLevel==UAUTH_User ){
140644
- /* Do not allow non-admin users to modify the schema arbitrarily */
140645
- mask &= ~(SQLITE_WriteSchema);
140646
- }
140647
-#endif
140648140716
140649140717
if( sqlite3GetBoolean(zRight, 0) ){
140650140718
if( (mask & SQLITE_WriteSchema)==0
140651140719
|| (db->flags & SQLITE_Defensive)==0
140652140720
){
@@ -140778,11 +140846,12 @@
140778140846
pTab = sqliteHashData(k);
140779140847
if( pTab->nCol==0 ){
140780140848
char *zSql = sqlite3MPrintf(db, "SELECT*FROM\"%w\"", pTab->zName);
140781140849
if( zSql ){
140782140850
sqlite3_stmt *pDummy = 0;
140783
- (void)sqlite3_prepare(db, zSql, -1, &pDummy, 0);
140851
+ (void)sqlite3_prepare_v3(db, zSql, -1, SQLITE_PREPARE_DONT_LOG,
140852
+ &pDummy, 0);
140784140853
(void)sqlite3_finalize(pDummy);
140785140854
sqlite3DbFree(db, zSql);
140786140855
}
140787140856
if( db->mallocFailed ){
140788140857
sqlite3ErrorMsg(db->pParse, "out of memory");
@@ -141259,11 +141328,11 @@
141259141328
sqlite3VdbeAddOp3(v, OP_Null, 0, 8, 8+cnt);
141260141329
sqlite3ClearTempRegCache(pParse);
141261141330
141262141331
/* Do the b-tree integrity checks */
141263141332
sqlite3VdbeAddOp4(v, OP_IntegrityCk, 1, cnt, 8, (char*)aRoot,P4_INTARRAY);
141264
- sqlite3VdbeChangeP5(v, (u8)i);
141333
+ sqlite3VdbeChangeP5(v, (u16)i);
141265141334
addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v);
141266141335
sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0,
141267141336
sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName),
141268141337
P4_DYNAMIC);
141269141338
sqlite3VdbeAddOp3(v, OP_Concat, 2, 3, 3);
@@ -142879,18 +142948,11 @@
142879142948
encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3;
142880142949
if( encoding==0 ) encoding = SQLITE_UTF8;
142881142950
#else
142882142951
encoding = SQLITE_UTF8;
142883142952
#endif
142884
- if( db->nVdbeActive>0 && encoding!=ENC(db)
142885
- && (db->mDbFlags & DBFLAG_Vacuum)==0
142886
- ){
142887
- rc = SQLITE_LOCKED;
142888
- goto initone_error_out;
142889
- }else{
142890
- sqlite3SetTextEncoding(db, encoding);
142891
- }
142953
+ sqlite3SetTextEncoding(db, encoding);
142892142954
}else{
142893142955
/* If opening an attached database, the encoding much match ENC(db) */
142894142956
if( (meta[BTREE_TEXT_ENCODING-1] & 3)!=ENC(db) ){
142895142957
sqlite3SetString(pzErrMsg, db, "attached databases must use the same"
142896142958
" text encoding as main database");
@@ -147584,36 +147646,36 @@
147584147646
return pExpr;
147585147647
}
147586147648
if( pSubst->isOuterJoin ){
147587147649
ExprSetProperty(pNew, EP_CanBeNull);
147588147650
}
147651
+ if( pNew->op==TK_TRUEFALSE ){
147652
+ pNew->u.iValue = sqlite3ExprTruthValue(pNew);
147653
+ pNew->op = TK_INTEGER;
147654
+ ExprSetProperty(pNew, EP_IntValue);
147655
+ }
147656
+
147657
+ /* Ensure that the expression now has an implicit collation sequence,
147658
+ ** just as it did when it was a column of a view or sub-query. */
147659
+ {
147660
+ CollSeq *pNat = sqlite3ExprCollSeq(pSubst->pParse, pNew);
147661
+ CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse,
147662
+ pSubst->pCList->a[iColumn].pExpr
147663
+ );
147664
+ if( pNat!=pColl || (pNew->op!=TK_COLUMN && pNew->op!=TK_COLLATE) ){
147665
+ pNew = sqlite3ExprAddCollateString(pSubst->pParse, pNew,
147666
+ (pColl ? pColl->zName : "BINARY")
147667
+ );
147668
+ }
147669
+ }
147670
+ ExprClearProperty(pNew, EP_Collate);
147589147671
if( ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) ){
147590147672
sqlite3SetJoinExpr(pNew, pExpr->w.iJoin,
147591147673
pExpr->flags & (EP_OuterON|EP_InnerON));
147592147674
}
147593147675
sqlite3ExprDelete(db, pExpr);
147594147676
pExpr = pNew;
147595
- if( pExpr->op==TK_TRUEFALSE ){
147596
- pExpr->u.iValue = sqlite3ExprTruthValue(pExpr);
147597
- pExpr->op = TK_INTEGER;
147598
- ExprSetProperty(pExpr, EP_IntValue);
147599
- }
147600
-
147601
- /* Ensure that the expression now has an implicit collation sequence,
147602
- ** just as it did when it was a column of a view or sub-query. */
147603
- {
147604
- CollSeq *pNat = sqlite3ExprCollSeq(pSubst->pParse, pExpr);
147605
- CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse,
147606
- pSubst->pCList->a[iColumn].pExpr
147607
- );
147608
- if( pNat!=pColl || (pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE) ){
147609
- pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr,
147610
- (pColl ? pColl->zName : "BINARY")
147611
- );
147612
- }
147613
- }
147614
- ExprClearProperty(pExpr, EP_Collate);
147615147677
}
147616147678
}
147617147679
}else{
147618147680
if( pExpr->op==TK_IF_NULL_ROW && pExpr->iTable==pSubst->iTable ){
147619147681
pExpr->iTable = pSubst->iNewTable;
@@ -148346,20 +148408,20 @@
148346148408
}
148347148409
148348148410
/* Transfer the FROM clause terms from the subquery into the
148349148411
** outer query.
148350148412
*/
148413
+ iNewParent = pSubSrc->a[0].iCursor;
148351148414
for(i=0; i<nSubSrc; i++){
148352148415
SrcItem *pItem = &pSrc->a[i+iFrom];
148353148416
assert( pItem->fg.isTabFunc==0 );
148354148417
assert( pItem->fg.isSubquery
148355148418
|| pItem->fg.fixedSchema
148356148419
|| pItem->u4.zDatabase==0 );
148357148420
if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing);
148358148421
*pItem = pSubSrc->a[i];
148359148422
pItem->fg.jointype |= ltorj;
148360
- iNewParent = pSubSrc->a[i].iCursor;
148361148423
memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i]));
148362148424
}
148363148425
pSrc->a[iFrom].fg.jointype &= JT_LTORJ;
148364148426
pSrc->a[iFrom].fg.jointype |= jointype | ltorj;
148365148427
@@ -148395,10 +148457,11 @@
148395148457
pSub->pOrderBy = 0;
148396148458
}
148397148459
pWhere = pSub->pWhere;
148398148460
pSub->pWhere = 0;
148399148461
if( isOuterJoin>0 ){
148462
+ assert( pSubSrc->nSrc==1 );
148400148463
sqlite3SetJoinExpr(pWhere, iNewParent, EP_OuterON);
148401148464
}
148402148465
if( pWhere ){
148403148466
if( pParent->pWhere ){
148404148467
pParent->pWhere = sqlite3PExpr(pParse, TK_AND, pWhere, pParent->pWhere);
@@ -150498,11 +150561,11 @@
150498150561
}
150499150562
sqlite3ReleaseTempReg(pParse, regSubtype);
150500150563
}
150501150564
sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
150502150565
sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
150503
- sqlite3VdbeChangeP5(v, (u8)nArg);
150566
+ sqlite3VdbeChangeP5(v, (u16)nArg);
150504150567
sqlite3VdbeAddOp2(v, OP_Next, pF->iOBTab, iTop+1); VdbeCoverage(v);
150505150568
sqlite3VdbeJumpHere(v, iTop);
150506150569
sqlite3ReleaseTempRange(pParse, regAgg, nArg);
150507150570
}
150508150571
sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i),
@@ -150661,11 +150724,11 @@
150661150724
sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0,
150662150725
(char *)pColl, P4_COLLSEQ);
150663150726
}
150664150727
sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
150665150728
sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
150666
- sqlite3VdbeChangeP5(v, (u8)nArg);
150729
+ sqlite3VdbeChangeP5(v, (u16)nArg);
150667150730
sqlite3ReleaseTempRange(pParse, regAgg, nArg);
150668150731
}
150669150732
if( addrNext ){
150670150733
sqlite3VdbeResolveLabel(v, addrNext);
150671150734
}
@@ -151494,11 +151557,11 @@
151494151557
sqlite3TreeViewSelect(0, p, 0);
151495151558
}
151496151559
#endif
151497151560
assert( pSubq->pSelect && (pSub->selFlags & SF_PushDown)!=0 );
151498151561
}else{
151499
- TREETRACE(0x4000,pParse,p,("WHERE-lcause push-down not possible\n"));
151562
+ TREETRACE(0x4000,pParse,p,("WHERE-clause push-down not possible\n"));
151500151563
}
151501151564
151502151565
/* Convert unused result columns of the subquery into simple NULL
151503151566
** expressions, to avoid unneeded searching and computation.
151504151567
** tag-select-0440
@@ -154055,11 +154118,11 @@
154055154118
/* Set the P5 operand of the OP_Program instruction to non-zero if
154056154119
** recursive invocation of this trigger program is disallowed. Recursive
154057154120
** invocation is disallowed if (a) the sub-program is really a trigger,
154058154121
** not a foreign key action, and (b) the flag to enable recursive triggers
154059154122
** is clear. */
154060
- sqlite3VdbeChangeP5(v, (u8)bRecursive);
154123
+ sqlite3VdbeChangeP5(v, (u16)bRecursive);
154061154124
}
154062154125
}
154063154126
154064154127
/*
154065154128
** This is called to code the required FOR EACH ROW triggers for an operation
@@ -158268,13 +158331,21 @@
158268158331
SQLITE_PRIVATE int sqlite3WhereExplainBloomFilter(
158269158332
const Parse *pParse, /* Parse context */
158270158333
const WhereInfo *pWInfo, /* WHERE clause */
158271158334
const WhereLevel *pLevel /* Bloom filter on this level */
158272158335
);
158336
+SQLITE_PRIVATE void sqlite3WhereAddExplainText(
158337
+ Parse *pParse, /* Parse context */
158338
+ int addr,
158339
+ SrcList *pTabList, /* Table list this loop refers to */
158340
+ WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */
158341
+ u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */
158342
+);
158273158343
#else
158274158344
# define sqlite3WhereExplainOneScan(u,v,w,x) 0
158275158345
# define sqlite3WhereExplainBloomFilter(u,v,w) 0
158346
+# define sqlite3WhereAddExplainText(u,v,w,x,y)
158276158347
#endif /* SQLITE_OMIT_EXPLAIN */
158277158348
#ifdef SQLITE_ENABLE_STMT_SCANSTATUS
158278158349
SQLITE_PRIVATE void sqlite3WhereAddScanStatus(
158279158350
Vdbe *v, /* Vdbe to add scanstatus entry to */
158280158351
SrcList *pSrclist, /* FROM clause pLvl reads data from */
@@ -158472,42 +158543,42 @@
158472158543
}
158473158544
sqlite3_str_append(pStr, ")", 1);
158474158545
}
158475158546
158476158547
/*
158477
-** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN
158478
-** command, or if stmt_scanstatus_v2() stats are enabled, or if SQLITE_DEBUG
158479
-** was defined at compile-time. If it is not a no-op, a single OP_Explain
158480
-** opcode is added to the output to describe the table scan strategy in pLevel.
158481
-**
158482
-** If an OP_Explain opcode is added to the VM, its address is returned.
158483
-** Otherwise, if no OP_Explain is coded, zero is returned.
158548
+** This function sets the P4 value of an existing OP_Explain opcode to
158549
+** text describing the loop in pLevel. If the OP_Explain opcode already has
158550
+** a P4 value, it is freed before it is overwritten.
158484158551
*/
158485
-SQLITE_PRIVATE int sqlite3WhereExplainOneScan(
158552
+SQLITE_PRIVATE void sqlite3WhereAddExplainText(
158486158553
Parse *pParse, /* Parse context */
158554
+ int addr, /* Address of OP_Explain opcode */
158487158555
SrcList *pTabList, /* Table list this loop refers to */
158488158556
WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */
158489158557
u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */
158490158558
){
158491
- int ret = 0;
158492158559
#if !defined(SQLITE_DEBUG)
158493158560
if( sqlite3ParseToplevel(pParse)->explain==2 || IS_STMT_SCANSTATUS(pParse->db) )
158494158561
#endif
158495158562
{
158563
+ VdbeOp *pOp = sqlite3VdbeGetOp(pParse->pVdbe, addr);
158564
+
158496158565
SrcItem *pItem = &pTabList->a[pLevel->iFrom];
158497
- Vdbe *v = pParse->pVdbe; /* VM being constructed */
158498158566
sqlite3 *db = pParse->db; /* Database handle */
158499158567
int isSearch; /* True for a SEARCH. False for SCAN. */
158500158568
WhereLoop *pLoop; /* The controlling WhereLoop object */
158501158569
u32 flags; /* Flags that describe this loop */
158570
+#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN)
158502158571
char *zMsg; /* Text to add to EQP output */
158572
+#endif
158503158573
StrAccum str; /* EQP output string */
158504158574
char zBuf[100]; /* Initial space for EQP output string */
158505158575
158576
+ if( db->mallocFailed ) return;
158577
+
158506158578
pLoop = pLevel->pWLoop;
158507158579
flags = pLoop->wsFlags;
158508
- if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_OR_SUBCLAUSE) ) return 0;
158509158580
158510158581
isSearch = (flags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0
158511158582
|| ((flags&WHERE_VIRTUALTABLE)==0 && (pLoop->u.btree.nEq>0))
158512158583
|| (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX));
158513158584
@@ -158527,11 +158598,11 @@
158527158598
}
158528158599
}else if( flags & WHERE_PARTIALIDX ){
158529158600
zFmt = "AUTOMATIC PARTIAL COVERING INDEX";
158530158601
}else if( flags & WHERE_AUTO_INDEX ){
158531158602
zFmt = "AUTOMATIC COVERING INDEX";
158532
- }else if( flags & WHERE_IDX_ONLY ){
158603
+ }else if( flags & (WHERE_IDX_ONLY|WHERE_EXPRIDX) ){
158533158604
zFmt = "COVERING INDEX %s";
158534158605
}else{
158535158606
zFmt = "INDEX %s";
158536158607
}
158537158608
if( zFmt ){
@@ -158579,15 +158650,54 @@
158579158650
sqlite3LogEstToInt(pLoop->nOut));
158580158651
}else{
158581158652
sqlite3_str_append(&str, " (~1 row)", 9);
158582158653
}
158583158654
#endif
158655
+#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN)
158584158656
zMsg = sqlite3StrAccumFinish(&str);
158585158657
sqlite3ExplainBreakpoint("",zMsg);
158586
- ret = sqlite3VdbeAddOp4(v, OP_Explain, sqlite3VdbeCurrentAddr(v),
158587
- pParse->addrExplain, pLoop->rRun,
158588
- zMsg, P4_DYNAMIC);
158658
+#endif
158659
+
158660
+ assert( pOp->opcode==OP_Explain );
158661
+ assert( pOp->p4type==P4_DYNAMIC || pOp->p4.z==0 );
158662
+ sqlite3DbFree(db, pOp->p4.z);
158663
+ pOp->p4type = P4_DYNAMIC;
158664
+ pOp->p4.z = sqlite3StrAccumFinish(&str);
158665
+ }
158666
+}
158667
+
158668
+
158669
+/*
158670
+** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN
158671
+** command, or if stmt_scanstatus_v2() stats are enabled, or if SQLITE_DEBUG
158672
+** was defined at compile-time. If it is not a no-op, a single OP_Explain
158673
+** opcode is added to the output to describe the table scan strategy in pLevel.
158674
+**
158675
+** If an OP_Explain opcode is added to the VM, its address is returned.
158676
+** Otherwise, if no OP_Explain is coded, zero is returned.
158677
+*/
158678
+SQLITE_PRIVATE int sqlite3WhereExplainOneScan(
158679
+ Parse *pParse, /* Parse context */
158680
+ SrcList *pTabList, /* Table list this loop refers to */
158681
+ WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */
158682
+ u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */
158683
+){
158684
+ int ret = 0;
158685
+#if !defined(SQLITE_DEBUG)
158686
+ if( sqlite3ParseToplevel(pParse)->explain==2 || IS_STMT_SCANSTATUS(pParse->db) )
158687
+#endif
158688
+ {
158689
+ if( (pLevel->pWLoop->wsFlags & WHERE_MULTI_OR)==0
158690
+ && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0
158691
+ ){
158692
+ Vdbe *v = pParse->pVdbe;
158693
+ int addr = sqlite3VdbeCurrentAddr(v);
158694
+ ret = sqlite3VdbeAddOp3(
158695
+ v, OP_Explain, addr, pParse->addrExplain, pLevel->pWLoop->rRun
158696
+ );
158697
+ sqlite3WhereAddExplainText(pParse, addr, pTabList, pLevel, wctrlFlags);
158698
+ }
158589158699
}
158590158700
return ret;
158591158701
}
158592158702
158593158703
/*
@@ -158682,13 +158792,14 @@
158682158792
if( wsFlags & WHERE_INDEXED ){
158683158793
sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur);
158684158794
}
158685158795
}else{
158686158796
int addr;
158797
+ VdbeOp *pOp;
158687158798
assert( pSrclist->a[pLvl->iFrom].fg.isSubquery );
158688158799
addr = pSrclist->a[pLvl->iFrom].u4.pSubq->addrFillSub;
158689
- VdbeOp *pOp = sqlite3VdbeGetOp(v, addr-1);
158800
+ pOp = sqlite3VdbeGetOp(v, addr-1);
158690158801
assert( sqlite3VdbeDb(v)->mallocFailed || pOp->opcode==OP_InitCoroutine );
158691158802
assert( sqlite3VdbeDb(v)->mallocFailed || pOp->p2>addr );
158692158803
sqlite3VdbeScanStatusRange(v, addrExplain, addr, pOp->p2-1);
158693158804
}
158694158805
}
@@ -158937,10 +159048,11 @@
158937159048
if( pOrigLhs ){
158938159049
sqlite3ExprListDelete(db, pOrigLhs);
158939159050
pNew->pLeft->x.pList = pLhs;
158940159051
}
158941159052
pSelect->pEList = pRhs;
159053
+ pSelect->selId = ++pParse->nSelect; /* Req'd for SubrtnSig validity */
158942159054
if( pLhs && pLhs->nExpr==1 ){
158943159055
/* Take care here not to generate a TK_VECTOR containing only a
158944159056
** single value. Since the parser never creates such a vector, some
158945159057
** of the subroutines do not handle this case. */
158946159058
Expr *p = pLhs->a[0].pExpr;
@@ -164009,11 +164121,11 @@
164009164121
|| pTerm->pExpr->w.iJoin != pSrc->iCursor
164010164122
){
164011164123
return 0;
164012164124
}
164013164125
if( (pSrc->fg.jointype & (JT_LEFT|JT_RIGHT))!=0
164014
- && ExprHasProperty(pTerm->pExpr, EP_InnerON)
164126
+ && NEVER(ExprHasProperty(pTerm->pExpr, EP_InnerON))
164015164127
){
164016164128
return 0;
164017164129
}
164018164130
return 1;
164019164131
}
@@ -165502,11 +165614,11 @@
165502165614
return rc;
165503165615
}
165504165616
#endif /* SQLITE_ENABLE_STAT4 */
165505165617
165506165618
165507
-#ifdef WHERETRACE_ENABLED
165619
+#if defined(WHERETRACE_ENABLED) || defined(SQLITE_DEBUG)
165508165620
/*
165509165621
** Print the content of a WhereTerm object
165510165622
*/
165511165623
SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){
165512165624
if( pTerm==0 ){
@@ -165545,10 +165657,13 @@
165545165657
sqlite3DebugPrintf(" iParent=%d", pTerm->iParent);
165546165658
}
165547165659
sqlite3DebugPrintf("\n");
165548165660
sqlite3TreeViewExpr(0, pTerm->pExpr, 0);
165549165661
}
165662
+}
165663
+SQLITE_PRIVATE void sqlite3ShowWhereTerm(WhereTerm *pTerm){
165664
+ sqlite3WhereTermPrint(pTerm, 0);
165550165665
}
165551165666
#endif
165552165667
165553165668
#ifdef WHERETRACE_ENABLED
165554165669
/*
@@ -166731,11 +166846,10 @@
166731166846
pParse = pWC->pWInfo->pParse;
166732166847
while( pWhere->op==TK_AND ){
166733166848
if( !whereUsablePartialIndex(iTab,jointype,pWC,pWhere->pLeft) ) return 0;
166734166849
pWhere = pWhere->pRight;
166735166850
}
166736
- if( pParse->db->flags & SQLITE_EnableQPSG ) pParse = 0;
166737166851
for(i=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){
166738166852
Expr *pExpr;
166739166853
pExpr = pTerm->pExpr;
166740166854
if( (!ExprHasProperty(pExpr, EP_OuterON) || pExpr->w.iJoin==iTab)
166741166855
&& ((jointype & JT_OUTER)==0 || ExprHasProperty(pExpr, EP_OuterON))
@@ -169392,11 +169506,11 @@
169392169506
break;
169393169507
}
169394169508
}
169395169509
if( hasRightJoin
169396169510
&& ExprHasProperty(pTerm->pExpr, EP_InnerON)
169397
- && pTerm->pExpr->w.iJoin==pItem->iCursor
169511
+ && NEVER(pTerm->pExpr->w.iJoin==pItem->iCursor)
169398169512
){
169399169513
break; /* restriction (5) */
169400169514
}
169401169515
}
169402169516
if( pTerm<pEnd ) continue;
@@ -170312,10 +170426,11 @@
170312170426
int pc,
170313170427
VdbeOp *pOp
170314170428
){
170315170429
if( (db->flags & SQLITE_VdbeAddopTrace)==0 ) return;
170316170430
sqlite3VdbePrintOp(0, pc, pOp);
170431
+ sqlite3ShowWhereTerm(0); /* So compiler won't complain about unused func */
170317170432
}
170318170433
#endif
170319170434
170320170435
/*
170321170436
** Generate the end of the WHERE loop. See comments on
@@ -170611,18 +170726,32 @@
170611170726
x = sqlite3TableColumnToIndex(pIdx, x);
170612170727
if( x>=0 ){
170613170728
pOp->p2 = x;
170614170729
pOp->p1 = pLevel->iIdxCur;
170615170730
OpcodeRewriteTrace(db, k, pOp);
170616
- }else{
170617
- /* Unable to translate the table reference into an index
170618
- ** reference. Verify that this is harmless - that the
170619
- ** table being referenced really is open.
170620
- */
170731
+ }else if( pLoop->wsFlags & (WHERE_IDX_ONLY|WHERE_EXPRIDX) ){
170621170732
if( pLoop->wsFlags & WHERE_IDX_ONLY ){
170733
+ /* An error. pLoop is supposed to be a covering index loop,
170734
+ ** and yet the VM code refers to a column of the table that
170735
+ ** is not part of the index. */
170622170736
sqlite3ErrorMsg(pParse, "internal query planner error");
170623170737
pParse->rc = SQLITE_INTERNAL;
170738
+ }else{
170739
+ /* The WHERE_EXPRIDX flag is set by the planner when it is likely
170740
+ ** that pLoop is a covering index loop, but it is not possible
170741
+ ** to be 100% sure. In this case, any OP_Explain opcode
170742
+ ** corresponding to this loop describes the index as a "COVERING
170743
+ ** INDEX". But, pOp proves that pLoop is not actually a covering
170744
+ ** index loop. So clear the WHERE_EXPRIDX flag and rewrite the
170745
+ ** text that accompanies the OP_Explain opcode, if any. */
170746
+ pLoop->wsFlags &= ~WHERE_EXPRIDX;
170747
+ sqlite3WhereAddExplainText(pParse,
170748
+ pLevel->addrBody-1,
170749
+ pTabList,
170750
+ pLevel,
170751
+ pWInfo->wctrlFlags
170752
+ );
170624170753
}
170625170754
}
170626170755
}else if( pOp->opcode==OP_Rowid ){
170627170756
pOp->p1 = pLevel->iIdxCur;
170628170757
pOp->opcode = OP_IdxRowid;
@@ -172326,10 +172455,11 @@
172326172455
for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
172327172456
FuncDef *pFunc = pWin->pWFunc;
172328172457
int regArg;
172329172458
int nArg = pWin->bExprArgs ? 0 : windowArgCount(pWin);
172330172459
int i;
172460
+ int addrIf = 0;
172331172461
172332172462
assert( bInverse==0 || pWin->eStart!=TK_UNBOUNDED );
172333172463
172334172464
/* All OVER clauses in the same window function aggregate step must
172335172465
** be the same. */
@@ -172341,10 +172471,22 @@
172341172471
}else{
172342172472
sqlite3VdbeAddOp3(v, OP_Column, pMWin->iEphCsr, pWin->iArgCol+i, reg+i);
172343172473
}
172344172474
}
172345172475
regArg = reg;
172476
+
172477
+ if( pWin->pFilter ){
172478
+ int regTmp;
172479
+ assert( ExprUseXList(pWin->pOwner) );
172480
+ assert( pWin->bExprArgs || !nArg ||nArg==pWin->pOwner->x.pList->nExpr );
172481
+ assert( pWin->bExprArgs || nArg ||pWin->pOwner->x.pList==0 );
172482
+ regTmp = sqlite3GetTempReg(pParse);
172483
+ sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol+nArg,regTmp);
172484
+ addrIf = sqlite3VdbeAddOp3(v, OP_IfNot, regTmp, 0, 1);
172485
+ VdbeCoverage(v);
172486
+ sqlite3ReleaseTempReg(pParse, regTmp);
172487
+ }
172346172488
172347172489
if( pMWin->regStartRowid==0
172348172490
&& (pFunc->funcFlags & SQLITE_FUNC_MINMAX)
172349172491
&& (pWin->eStart!=TK_UNBOUNDED)
172350172492
){
@@ -172361,29 +172503,17 @@
172361172503
sqlite3VdbeAddOp1(v, OP_Delete, pWin->csrApp);
172362172504
sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2);
172363172505
}
172364172506
sqlite3VdbeJumpHere(v, addrIsNull);
172365172507
}else if( pWin->regApp ){
172508
+ assert( pWin->pFilter==0 );
172366172509
assert( pFunc->zName==nth_valueName
172367172510
|| pFunc->zName==first_valueName
172368172511
);
172369172512
assert( bInverse==0 || bInverse==1 );
172370172513
sqlite3VdbeAddOp2(v, OP_AddImm, pWin->regApp+1-bInverse, 1);
172371172514
}else if( pFunc->xSFunc!=noopStepFunc ){
172372
- int addrIf = 0;
172373
- if( pWin->pFilter ){
172374
- int regTmp;
172375
- assert( ExprUseXList(pWin->pOwner) );
172376
- assert( pWin->bExprArgs || !nArg ||nArg==pWin->pOwner->x.pList->nExpr );
172377
- assert( pWin->bExprArgs || nArg ||pWin->pOwner->x.pList==0 );
172378
- regTmp = sqlite3GetTempReg(pParse);
172379
- sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol+nArg,regTmp);
172380
- addrIf = sqlite3VdbeAddOp3(v, OP_IfNot, regTmp, 0, 1);
172381
- VdbeCoverage(v);
172382
- sqlite3ReleaseTempReg(pParse, regTmp);
172383
- }
172384
-
172385172515
if( pWin->bExprArgs ){
172386172516
int iOp = sqlite3VdbeCurrentAddr(v);
172387172517
int iEnd;
172388172518
172389172519
assert( ExprUseXList(pWin->pOwner) );
@@ -172406,16 +172536,17 @@
172406172536
sqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ);
172407172537
}
172408172538
sqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep,
172409172539
bInverse, regArg, pWin->regAccum);
172410172540
sqlite3VdbeAppendP4(v, pFunc, P4_FUNCDEF);
172411
- sqlite3VdbeChangeP5(v, (u8)nArg);
172541
+ sqlite3VdbeChangeP5(v, (u16)nArg);
172412172542
if( pWin->bExprArgs ){
172413172543
sqlite3ReleaseTempRange(pParse, regArg, nArg);
172414172544
}
172415
- if( addrIf ) sqlite3VdbeJumpHere(v, addrIf);
172416172545
}
172546
+
172547
+ if( addrIf ) sqlite3VdbeJumpHere(v, addrIf);
172417172548
}
172418172549
}
172419172550
172420172551
/*
172421172552
** Values that may be passed as the second argument to windowCodeOp().
@@ -173837,10 +173968,17 @@
173837173968
** Then the "b" IdList records the list "a,b,c".
173838173969
*/
173839173970
struct TrigEvent { int a; IdList * b; };
173840173971
173841173972
struct FrameBound { int eType; Expr *pExpr; };
173973
+
173974
+/*
173975
+** Generate a syntax error
173976
+*/
173977
+static void parserSyntaxError(Parse *pParse, Token *p){
173978
+ sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", p);
173979
+}
173842173980
173843173981
/*
173844173982
** Disable lookaside memory allocation for objects that might be
173845173983
** shared across database connections.
173846173984
*/
@@ -177730,11 +177868,15 @@
177730177868
}
177731177869
break;
177732177870
case 84: /* cmd ::= select */
177733177871
{
177734177872
SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0, 0};
177735
- sqlite3Select(pParse, yymsp[0].minor.yy555, &dest);
177873
+ if( (pParse->db->mDbFlags & DBFLAG_EncodingFixed)!=0
177874
+ || sqlite3ReadSchema(pParse)==SQLITE_OK
177875
+ ){
177876
+ sqlite3Select(pParse, yymsp[0].minor.yy555, &dest);
177877
+ }
177736177878
sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy555);
177737177879
}
177738177880
break;
177739177881
case 85: /* select ::= WITH wqlist selectnowith */
177740177882
{yymsp[-2].minor.yy555 = attachWithToSelect(pParse,yymsp[0].minor.yy555,yymsp[-1].minor.yy59);}
@@ -178201,11 +178343,11 @@
178201178343
** that look like this: #1 #2 ... These terms refer to registers
178202178344
** in the virtual machine. #N is the N-th register. */
178203178345
Token t = yymsp[0].minor.yy0; /*A-overwrites-X*/
178204178346
assert( t.n>=2 );
178205178347
if( pParse->nested==0 ){
178206
- sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t);
178348
+ parserSyntaxError(pParse, &t);
178207178349
yymsp[0].minor.yy454 = 0;
178208178350
}else{
178209178351
yymsp[0].minor.yy454 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0);
178210178352
if( yymsp[0].minor.yy454 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy454->iTable);
178211178353
}
@@ -179049,11 +179191,11 @@
179049179191
#define TOKEN yyminor
179050179192
/************ Begin %syntax_error code ****************************************/
179051179193
179052179194
UNUSED_PARAMETER(yymajor); /* Silence some compiler warnings */
179053179195
if( TOKEN.z[0] ){
179054
- sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN);
179196
+ parserSyntaxError(pParse, &TOKEN);
179055179197
}else{
179056179198
sqlite3ErrorMsg(pParse, "incomplete input");
179057179199
}
179058179200
/************ End %syntax_error code ******************************************/
179059179201
sqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument variable */
@@ -180540,11 +180682,13 @@
180540180682
}
180541180683
if( pParse->zErrMsg || (pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE) ){
180542180684
if( pParse->zErrMsg==0 ){
180543180685
pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc));
180544180686
}
180545
- sqlite3_log(pParse->rc, "%s in \"%s\"", pParse->zErrMsg, pParse->zTail);
180687
+ if( (pParse->prepFlags & SQLITE_PREPARE_DONT_LOG)==0 ){
180688
+ sqlite3_log(pParse->rc, "%s in \"%s\"", pParse->zErrMsg, pParse->zTail);
180689
+ }
180546180690
nErr++;
180547180691
}
180548180692
pParse->zTail = zSql;
180549180693
#ifndef SQLITE_OMIT_VIRTUALTABLE
180550180694
sqlite3_free(pParse->apVtabLock);
@@ -182513,14 +182657,10 @@
182513182657
#endif
182514182658
182515182659
sqlite3Error(db, SQLITE_OK); /* Deallocates any cached error strings. */
182516182660
sqlite3ValueFree(db->pErr);
182517182661
sqlite3CloseExtensions(db);
182518
-#if SQLITE_USER_AUTHENTICATION
182519
- sqlite3_free(db->auth.zAuthUser);
182520
- sqlite3_free(db->auth.zAuthPW);
182521
-#endif
182522182662
182523182663
db->eOpenState = SQLITE_STATE_ERROR;
182524182664
182525182665
/* The temp-database schema is allocated differently from the other schema
182526182666
** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()).
@@ -183951,12 +184091,12 @@
183951184091
# error SQLITE_MAX_COMPOUND_SELECT must be at least 2
183952184092
#endif
183953184093
#if SQLITE_MAX_VDBE_OP<40
183954184094
# error SQLITE_MAX_VDBE_OP must be at least 40
183955184095
#endif
183956
-#if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>127
183957
-# error SQLITE_MAX_FUNCTION_ARG must be between 0 and 127
184096
+#if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>32767
184097
+# error SQLITE_MAX_FUNCTION_ARG must be between 0 and 32767
183958184098
#endif
183959184099
#if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>125
183960184100
# error SQLITE_MAX_ATTACHED must be between 0 and 125
183961184101
#endif
183962184102
#if SQLITE_MAX_LIKE_PATTERN_LENGTH<1
@@ -184019,12 +184159,12 @@
184019184159
}
184020184160
oldLimit = db->aLimit[limitId];
184021184161
if( newLimit>=0 ){ /* IMP: R-52476-28732 */
184022184162
if( newLimit>aHardLimit[limitId] ){
184023184163
newLimit = aHardLimit[limitId]; /* IMP: R-51463-25634 */
184024
- }else if( newLimit<1 && limitId==SQLITE_LIMIT_LENGTH ){
184025
- newLimit = 1;
184164
+ }else if( newLimit<SQLITE_MIN_LENGTH && limitId==SQLITE_LIMIT_LENGTH ){
184165
+ newLimit = SQLITE_MIN_LENGTH;
184026184166
}
184027184167
db->aLimit[limitId] = newLimit;
184028184168
}
184029184169
return oldLimit; /* IMP: R-53341-35419 */
184030184170
}
@@ -185364,11 +185504,10 @@
185364185504
rc = x;
185365185505
#if defined(SQLITE_DEBUG)
185366185506
/* Invoke these debugging routines so that the compiler does not
185367185507
** issue "defined but not used" warnings. */
185368185508
if( x==9999 ){
185369
- sqlite3ShowExpr(0);
185370185509
sqlite3ShowExpr(0);
185371185510
sqlite3ShowExprList(0);
185372185511
sqlite3ShowIdList(0);
185373185512
sqlite3ShowSrcList(0);
185374185513
sqlite3ShowWith(0);
@@ -189796,14 +189935,19 @@
189796189935
189797189936
assert_fts3_nc( p!=0 && *p1!=0 && *p2!=0 );
189798189937
if( *p1==POS_COLUMN ){
189799189938
p1++;
189800189939
p1 += fts3GetVarint32(p1, &iCol1);
189940
+ /* iCol1==0 indicates corruption. Column 0 does not have a POS_COLUMN
189941
+ ** entry, so this is actually end-of-doclist. */
189942
+ if( iCol1==0 ) return 0;
189801189943
}
189802189944
if( *p2==POS_COLUMN ){
189803189945
p2++;
189804189946
p2 += fts3GetVarint32(p2, &iCol2);
189947
+ /* As above, iCol2==0 indicates corruption. */
189948
+ if( iCol2==0 ) return 0;
189805189949
}
189806189950
189807189951
while( 1 ){
189808189952
if( iCol1==iCol2 ){
189809189953
char *pSave = p;
@@ -192970,11 +193114,11 @@
192970193114
for(p=pExpr; p->pLeft; p=p->pLeft){
192971193115
assert( p->pRight->pPhrase->doclist.nList>0 );
192972193116
nTmp += p->pRight->pPhrase->doclist.nList;
192973193117
}
192974193118
nTmp += p->pPhrase->doclist.nList;
192975
- aTmp = sqlite3_malloc64(nTmp*2);
193119
+ aTmp = sqlite3_malloc64(nTmp*2 + FTS3_VARINT_MAX);
192976193120
if( !aTmp ){
192977193121
*pRc = SQLITE_NOMEM;
192978193122
res = 0;
192979193123
}else{
192980193124
char *aPoslist = p->pPhrase->doclist.pList;
@@ -193621,11 +193765,11 @@
193621193765
SQLITE_PRIVATE int sqlite3Fts3Corrupt(){
193622193766
return SQLITE_CORRUPT_VTAB;
193623193767
}
193624193768
#endif
193625193769
193626
-#if !SQLITE_CORE
193770
+#if !defined(SQLITE_CORE)
193627193771
/*
193628193772
** Initialize API pointer table, if required.
193629193773
*/
193630193774
#ifdef _WIN32
193631193775
__declspec(dllexport)
@@ -194523,14 +194667,15 @@
194523194667
rc = pModule->xNext(pCursor, &zByte, &nByte, &iBegin, &iEnd, &iPos);
194524194668
if( rc==SQLITE_OK ){
194525194669
Fts3PhraseToken *pToken;
194526194670
194527194671
p = fts3ReallocOrFree(p, nSpace + ii*sizeof(Fts3PhraseToken));
194528
- if( !p ) goto no_mem;
194529
-
194530194672
zTemp = fts3ReallocOrFree(zTemp, nTemp + nByte);
194531
- if( !zTemp ) goto no_mem;
194673
+ if( !zTemp || !p ){
194674
+ rc = SQLITE_NOMEM;
194675
+ goto getnextstring_out;
194676
+ }
194532194677
194533194678
assert( nToken==ii );
194534194679
pToken = &((Fts3Phrase *)(&p[1]))->aToken[ii];
194535194680
memset(pToken, 0, sizeof(Fts3PhraseToken));
194536194681
@@ -194541,53 +194686,51 @@
194541194686
pToken->isPrefix = (iEnd<nInput && zInput[iEnd]=='*');
194542194687
pToken->bFirst = (iBegin>0 && zInput[iBegin-1]=='^');
194543194688
nToken = ii+1;
194544194689
}
194545194690
}
194546
-
194547
- pModule->xClose(pCursor);
194548
- pCursor = 0;
194549194691
}
194550194692
194551194693
if( rc==SQLITE_DONE ){
194552194694
int jj;
194553194695
char *zBuf = 0;
194554194696
194555194697
p = fts3ReallocOrFree(p, nSpace + nToken*sizeof(Fts3PhraseToken) + nTemp);
194556
- if( !p ) goto no_mem;
194698
+ if( !p ){
194699
+ rc = SQLITE_NOMEM;
194700
+ goto getnextstring_out;
194701
+ }
194557194702
memset(p, 0, (char *)&(((Fts3Phrase *)&p[1])->aToken[0])-(char *)p);
194558194703
p->eType = FTSQUERY_PHRASE;
194559194704
p->pPhrase = (Fts3Phrase *)&p[1];
194560194705
p->pPhrase->iColumn = pParse->iDefaultCol;
194561194706
p->pPhrase->nToken = nToken;
194562194707
194563194708
zBuf = (char *)&p->pPhrase->aToken[nToken];
194709
+ assert( nTemp==0 || zTemp );
194564194710
if( zTemp ){
194565194711
memcpy(zBuf, zTemp, nTemp);
194566
- sqlite3_free(zTemp);
194567
- }else{
194568
- assert( nTemp==0 );
194569194712
}
194570194713
194571194714
for(jj=0; jj<p->pPhrase->nToken; jj++){
194572194715
p->pPhrase->aToken[jj].z = zBuf;
194573194716
zBuf += p->pPhrase->aToken[jj].n;
194574194717
}
194575194718
rc = SQLITE_OK;
194576194719
}
194577194720
194578
- *ppExpr = p;
194579
- return rc;
194580
-no_mem:
194581
-
194721
+ getnextstring_out:
194582194722
if( pCursor ){
194583194723
pModule->xClose(pCursor);
194584194724
}
194585194725
sqlite3_free(zTemp);
194586
- sqlite3_free(p);
194587
- *ppExpr = 0;
194588
- return SQLITE_NOMEM;
194726
+ if( rc!=SQLITE_OK ){
194727
+ sqlite3_free(p);
194728
+ p = 0;
194729
+ }
194730
+ *ppExpr = p;
194731
+ return rc;
194589194732
}
194590194733
194591194734
/*
194592194735
** The output variable *ppExpr is populated with an allocated Fts3Expr
194593194736
** structure, or set to 0 if the end of the input buffer is reached.
@@ -215395,12 +215538,12 @@
215395215538
#endif
215396215539
}
215397215540
sqlite3_str_append(pOut, "}", 1);
215398215541
}
215399215542
errCode = sqlite3_str_errcode(pOut);
215400
- sqlite3_result_text(ctx, sqlite3_str_finish(pOut), -1, sqlite3_free);
215401215543
sqlite3_result_error_code(ctx, errCode);
215544
+ sqlite3_result_text(ctx, sqlite3_str_finish(pOut), -1, sqlite3_free);
215402215545
}
215403215546
215404215547
/* This routine implements an SQL function that returns the "depth" parameter
215405215548
** from the front of a blob that is an r-tree node. For example:
215406215549
**
@@ -217912,11 +218055,11 @@
217912218055
return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY,
217913218056
(void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback
217914218057
);
217915218058
}
217916218059
217917
-#if !SQLITE_CORE
218060
+#ifndef SQLITE_CORE
217918218061
#ifdef _WIN32
217919218062
__declspec(dllexport)
217920218063
#endif
217921218064
SQLITE_API int sqlite3_rtree_init(
217922218065
sqlite3 *db,
@@ -218503,11 +218646,11 @@
218503218646
}
218504218647
218505218648
return rc;
218506218649
}
218507218650
218508
-#if !SQLITE_CORE
218651
+#ifndef SQLITE_CORE
218509218652
#ifdef _WIN32
218510218653
__declspec(dllexport)
218511218654
#endif
218512218655
SQLITE_API int sqlite3_icu_init(
218513218656
sqlite3 *db,
@@ -226177,10 +226320,12 @@
226177226320
if( (rc = sqlite3PagerWrite(pDbPage))==SQLITE_OK && pData ){
226178226321
unsigned char *aPage = sqlite3PagerGetData(pDbPage);
226179226322
memcpy(aPage, pData, szPage);
226180226323
pTab->pgnoTrunc = 0;
226181226324
}
226325
+ }else{
226326
+ pTab->pgnoTrunc = 0;
226182226327
}
226183226328
sqlite3PagerUnref(pDbPage);
226184226329
return rc;
226185226330
226186226331
update_fail:
@@ -226210,11 +226355,15 @@
226210226355
static int dbpageSync(sqlite3_vtab *pVtab){
226211226356
DbpageTable *pTab = (DbpageTable *)pVtab;
226212226357
if( pTab->pgnoTrunc>0 ){
226213226358
Btree *pBt = pTab->db->aDb[pTab->iDbTrunc].pBt;
226214226359
Pager *pPager = sqlite3BtreePager(pBt);
226215
- sqlite3PagerTruncateImage(pPager, pTab->pgnoTrunc);
226360
+ sqlite3BtreeEnter(pBt);
226361
+ if( pTab->pgnoTrunc<sqlite3BtreeLastPage(pBt) ){
226362
+ sqlite3PagerTruncateImage(pPager, pTab->pgnoTrunc);
226363
+ }
226364
+ sqlite3BtreeLeave(pBt);
226216226365
}
226217226366
pTab->pgnoTrunc = 0;
226218226367
return SQLITE_OK;
226219226368
}
226220226369
@@ -232804,20 +232953,46 @@
232804232953
#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
232805232954
232806232955
/************** End of sqlite3session.c **************************************/
232807232956
/************** Begin file fts5.c ********************************************/
232808232957
232809
-
232958
+/*
232959
+** This, the "fts5.c" source file, is a composite file that is itself
232960
+** assembled from the following files:
232961
+**
232962
+** fts5.h
232963
+** fts5Int.h
232964
+** fts5parse.h <--- Generated from fts5parse.y by Lemon
232965
+** fts5parse.c <--- Generated from fts5parse.y by Lemon
232966
+** fts5_aux.c
232967
+** fts5_buffer.c
232968
+** fts5_config.c
232969
+** fts5_expr.c
232970
+** fts5_hash.c
232971
+** fts5_index.c
232972
+** fts5_main.c
232973
+** fts5_storage.c
232974
+** fts5_tokenize.c
232975
+** fts5_unicode2.c
232976
+** fts5_varint.c
232977
+** fts5_vocab.c
232978
+*/
232810232979
#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5)
232811232980
232812232981
#if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
232813232982
# define NDEBUG 1
232814232983
#endif
232815232984
#if defined(NDEBUG) && defined(SQLITE_DEBUG)
232816232985
# undef NDEBUG
232817232986
#endif
232818232987
232988
+#ifdef HAVE_STDINT_H
232989
+/* #include <stdint.h> */
232990
+#endif
232991
+#ifdef HAVE_INTTYPES_H
232992
+/* #include <inttypes.h> */
232993
+#endif
232819232994
/*
232820232995
** 2014 May 31
232821232996
**
232822232997
** The author disclaims copyright to this source code. In place of
232823232998
** a legal notice, here is a blessing:
@@ -233114,17 +233289,32 @@
233114233289
** This is used to access token iToken of phrase hit iIdx within the
233115233290
** current row. If iIdx is less than zero or greater than or equal to the
233116233291
** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise,
233117233292
** output variable (*ppToken) is set to point to a buffer containing the
233118233293
** matching document token, and (*pnToken) to the size of that buffer in
233119
-** bytes. This API is not available if the specified token matches a
233120
-** prefix query term. In that case both output variables are always set
233121
-** to 0.
233294
+** bytes.
233122233295
**
233123233296
** The output text is not a copy of the document text that was tokenized.
233124233297
** It is the output of the tokenizer module. For tokendata=1 tables, this
233125233298
** includes any embedded 0x00 and trailing data.
233299
+**
233300
+** This API may be slow in some cases if the token identified by parameters
233301
+** iIdx and iToken matched a prefix token in the query. In most cases, the
233302
+** first call to this API for each prefix token in the query is forced
233303
+** to scan the portion of the full-text index that matches the prefix
233304
+** token to collect the extra data required by this API. If the prefix
233305
+** token matches a large number of token instances in the document set,
233306
+** this may be a performance problem.
233307
+**
233308
+** If the user knows in advance that a query may use this API for a
233309
+** prefix token, FTS5 may be configured to collect all required data as part
233310
+** of the initial querying of the full-text index, avoiding the second scan
233311
+** entirely. This also causes prefix queries that do not use this API to
233312
+** run more slowly and use more memory. FTS5 may be configured in this way
233313
+** either on a per-table basis using the [FTS5 insttoken | 'insttoken']
233314
+** option, or on a per-query basis using the
233315
+** [fts5_insttoken | fts5_insttoken()] user function.
233126233316
**
233127233317
** This API can be quite slow if used with an FTS5 table created with the
233128233318
** "detail=none" or "detail=column" option.
233129233319
**
233130233320
** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale)
@@ -233803,11 +233993,12 @@
233803233993
int nUsermerge; /* 'usermerge' setting */
233804233994
int nHashSize; /* Bytes of memory for in-memory hash */
233805233995
char *zRank; /* Name of rank function */
233806233996
char *zRankArgs; /* Arguments to rank function */
233807233997
int bSecureDelete; /* 'secure-delete' */
233808
- int nDeleteMerge; /* 'deletemerge' */
233998
+ int nDeleteMerge; /* 'deletemerge' */
233999
+ int bPrefixInsttoken; /* 'prefix-insttoken' */
233809234000
233810234001
/* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */
233811234002
char **pzErrmsg;
233812234003
233813234004
#ifdef SQLITE_DEBUG
@@ -234060,11 +234251,18 @@
234060234251
static int sqlite3Fts5StructureTest(Fts5Index*, void*);
234061234252
234062234253
/*
234063234254
** Used by xInstToken():
234064234255
*/
234065
-static int sqlite3Fts5IterToken(Fts5IndexIter*, i64, int, int, const char**, int*);
234256
+static int sqlite3Fts5IterToken(
234257
+ Fts5IndexIter *pIndexIter,
234258
+ const char *pToken, int nToken,
234259
+ i64 iRowid,
234260
+ int iCol,
234261
+ int iOff,
234262
+ const char **ppOut, int *pnOut
234263
+);
234066234264
234067234265
/*
234068234266
** Insert or remove data to or from the index. Each time a document is
234069234267
** added to or removed from the index, this function is called one or more
234070234268
** times.
@@ -238274,10 +238472,23 @@
238274238472
if( bVal<0 ){
238275238473
*pbBadkey = 1;
238276238474
}else{
238277238475
pConfig->bSecureDelete = (bVal ? 1 : 0);
238278238476
}
238477
+ }
238478
+
238479
+ else if( 0==sqlite3_stricmp(zKey, "insttoken") ){
238480
+ int bVal = -1;
238481
+ if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
238482
+ bVal = sqlite3_value_int(pVal);
238483
+ }
238484
+ if( bVal<0 ){
238485
+ *pbBadkey = 1;
238486
+ }else{
238487
+ pConfig->bPrefixInsttoken = (bVal ? 1 : 0);
238488
+ }
238489
+
238279238490
}else{
238280238491
*pbBadkey = 1;
238281238492
}
238282238493
return rc;
238283238494
}
@@ -241409,11 +241620,11 @@
241409241620
&& memcmp(pT->pTerm, pToken, pT->nQueryTerm)==0
241410241621
){
241411241622
int rc = sqlite3Fts5PoslistWriterAppend(
241412241623
&pExpr->apExprPhrase[i]->poslist, &p->aPopulator[i].writer, p->iOff
241413241624
);
241414
- if( rc==SQLITE_OK && pExpr->pConfig->bTokendata && !pT->bPrefix ){
241625
+ if( rc==SQLITE_OK && (pExpr->pConfig->bTokendata || pT->bPrefix) ){
241415241626
int iCol = p->iOff>>32;
241416241627
int iTokOff = p->iOff & 0x7FFFFFFF;
241417241628
rc = sqlite3Fts5IndexIterWriteTokendata(
241418241629
pT->pIter, pToken, nToken, iRowid, iCol, iTokOff
241419241630
);
@@ -241602,19 +241813,18 @@
241602241813
pPhrase = pExpr->apExprPhrase[iPhrase];
241603241814
if( iToken<0 || iToken>=pPhrase->nTerm ){
241604241815
return SQLITE_RANGE;
241605241816
}
241606241817
pTerm = &pPhrase->aTerm[iToken];
241607
- if( pTerm->bPrefix==0 ){
241608
- if( pExpr->pConfig->bTokendata ){
241609
- rc = sqlite3Fts5IterToken(
241610
- pTerm->pIter, iRowid, iCol, iOff+iToken, ppOut, pnOut
241611
- );
241612
- }else{
241613
- *ppOut = pTerm->pTerm;
241614
- *pnOut = pTerm->nFullTerm;
241615
- }
241818
+ if( pExpr->pConfig->bTokendata || pTerm->bPrefix ){
241819
+ rc = sqlite3Fts5IterToken(
241820
+ pTerm->pIter, pTerm->pTerm, pTerm->nQueryTerm,
241821
+ iRowid, iCol, iOff+iToken, ppOut, pnOut
241822
+ );
241823
+ }else{
241824
+ *ppOut = pTerm->pTerm;
241825
+ *pnOut = pTerm->nFullTerm;
241616241826
}
241617241827
return rc;
241618241828
}
241619241829
241620241830
/*
@@ -248425,10 +248635,387 @@
248425248635
fts5BufferFree(&tmp);
248426248636
memset(&out.p[out.n], 0, FTS5_DATA_ZERO_PADDING);
248427248637
*p1 = out;
248428248638
}
248429248639
248640
+
248641
+/*
248642
+** Iterate through a range of entries in the FTS index, invoking the xVisit
248643
+** callback for each of them.
248644
+**
248645
+** Parameter pToken points to an nToken buffer containing an FTS index term
248646
+** (i.e. a document term with the preceding 1 byte index identifier -
248647
+** FTS5_MAIN_PREFIX or similar). If bPrefix is true, then the call visits
248648
+** all entries for terms that have pToken/nToken as a prefix. If bPrefix
248649
+** is false, then only entries with pToken/nToken as the entire key are
248650
+** visited.
248651
+**
248652
+** If the current table is a tokendata=1 table, then if bPrefix is true then
248653
+** each index term is treated separately. However, if bPrefix is false, then
248654
+** all index terms corresponding to pToken/nToken are collapsed into a single
248655
+** term before the callback is invoked.
248656
+**
248657
+** The callback invoked for each entry visited is specified by paramter xVisit.
248658
+** Each time it is invoked, it is passed a pointer to the Fts5Index object,
248659
+** a copy of the 7th paramter to this function (pCtx) and a pointer to the
248660
+** iterator that indicates the current entry. If the current entry is the
248661
+** first with a new term (i.e. different from that of the previous entry,
248662
+** including the very first term), then the final two parameters are passed
248663
+** a pointer to the term and its size in bytes, respectively. If the current
248664
+** entry is not the first associated with its term, these two parameters
248665
+** are passed 0.
248666
+**
248667
+** If parameter pColset is not NULL, then it is used to filter entries before
248668
+** the callback is invoked.
248669
+*/
248670
+static int fts5VisitEntries(
248671
+ Fts5Index *p, /* Fts5 index object */
248672
+ Fts5Colset *pColset, /* Columns filter to apply, or NULL */
248673
+ u8 *pToken, /* Buffer containing token */
248674
+ int nToken, /* Size of buffer pToken in bytes */
248675
+ int bPrefix, /* True for a prefix scan */
248676
+ void (*xVisit)(Fts5Index*, void *pCtx, Fts5Iter *pIter, const u8*, int),
248677
+ void *pCtx /* Passed as second argument to xVisit() */
248678
+){
248679
+ const int flags = (bPrefix ? FTS5INDEX_QUERY_SCAN : 0)
248680
+ | FTS5INDEX_QUERY_SKIPEMPTY
248681
+ | FTS5INDEX_QUERY_NOOUTPUT;
248682
+ Fts5Iter *p1 = 0; /* Iterator used to gather data from index */
248683
+ int bNewTerm = 1;
248684
+ Fts5Structure *pStruct = fts5StructureRead(p);
248685
+
248686
+ fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1);
248687
+ fts5IterSetOutputCb(&p->rc, p1);
248688
+ for( /* no-op */ ;
248689
+ fts5MultiIterEof(p, p1)==0;
248690
+ fts5MultiIterNext2(p, p1, &bNewTerm)
248691
+ ){
248692
+ Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ];
248693
+ int nNew = 0;
248694
+ const u8 *pNew = 0;
248695
+
248696
+ p1->xSetOutputs(p1, pSeg);
248697
+ if( p->rc ) break;
248698
+
248699
+ if( bNewTerm ){
248700
+ nNew = pSeg->term.n;
248701
+ pNew = pSeg->term.p;
248702
+ if( nNew<nToken || memcmp(pToken, pNew, nToken) ) break;
248703
+ }
248704
+
248705
+ xVisit(p, pCtx, p1, pNew, nNew);
248706
+ }
248707
+ fts5MultiIterFree(p1);
248708
+
248709
+ fts5StructureRelease(pStruct);
248710
+ return p->rc;
248711
+}
248712
+
248713
+
248714
+/*
248715
+** Usually, a tokendata=1 iterator (struct Fts5TokenDataIter) accumulates an
248716
+** array of these for each row it visits (so all iRowid fields are the same).
248717
+** Or, for an iterator used by an "ORDER BY rank" query, it accumulates an
248718
+** array of these for the entire query (in which case iRowid fields may take
248719
+** a variety of values).
248720
+**
248721
+** Each instance in the array indicates the iterator (and therefore term)
248722
+** associated with position iPos of rowid iRowid. This is used by the
248723
+** xInstToken() API.
248724
+**
248725
+** iRowid:
248726
+** Rowid for the current entry.
248727
+**
248728
+** iPos:
248729
+** Position of current entry within row. In the usual ((iCol<<32)+iOff)
248730
+** format (e.g. see macros FTS5_POS2COLUMN() and FTS5_POS2OFFSET()).
248731
+**
248732
+** iIter:
248733
+** If the Fts5TokenDataIter iterator that the entry is part of is
248734
+** actually an iterator (i.e. with nIter>0, not just a container for
248735
+** Fts5TokenDataMap structures), then this variable is an index into
248736
+** the apIter[] array. The corresponding term is that which the iterator
248737
+** at apIter[iIter] currently points to.
248738
+**
248739
+** Or, if the Fts5TokenDataIter iterator is just a container object
248740
+** (nIter==0), then iIter is an index into the term.p[] buffer where
248741
+** the term is stored.
248742
+**
248743
+** nByte:
248744
+** In the case where iIter is an index into term.p[], this variable
248745
+** is the size of the term in bytes. If iIter is an index into apIter[],
248746
+** this variable is unused.
248747
+*/
248748
+struct Fts5TokenDataMap {
248749
+ i64 iRowid; /* Row this token is located in */
248750
+ i64 iPos; /* Position of token */
248751
+ int iIter; /* Iterator token was read from */
248752
+ int nByte; /* Length of token in bytes (or 0) */
248753
+};
248754
+
248755
+/*
248756
+** An object used to supplement Fts5Iter for tokendata=1 iterators.
248757
+**
248758
+** This object serves two purposes. The first is as a container for an array
248759
+** of Fts5TokenDataMap structures, which are used to find the token required
248760
+** when the xInstToken() API is used. This is done by the nMapAlloc, nMap and
248761
+** aMap[] variables.
248762
+*/
248763
+struct Fts5TokenDataIter {
248764
+ int nMapAlloc; /* Allocated size of aMap[] in entries */
248765
+ int nMap; /* Number of valid entries in aMap[] */
248766
+ Fts5TokenDataMap *aMap; /* Array of (rowid+pos -> token) mappings */
248767
+
248768
+ /* The following are used for prefix-queries only. */
248769
+ Fts5Buffer terms;
248770
+
248771
+ /* The following are used for other full-token tokendata queries only. */
248772
+ int nIter;
248773
+ int nIterAlloc;
248774
+ Fts5PoslistReader *aPoslistReader;
248775
+ int *aPoslistToIter;
248776
+ Fts5Iter *apIter[1];
248777
+};
248778
+
248779
+/*
248780
+** The two input arrays - a1[] and a2[] - are in sorted order. This function
248781
+** merges the two arrays together and writes the result to output array
248782
+** aOut[]. aOut[] is guaranteed to be large enough to hold the result.
248783
+**
248784
+** Duplicate entries are copied into the output. So the size of the output
248785
+** array is always (n1+n2) entries.
248786
+*/
248787
+static void fts5TokendataMerge(
248788
+ Fts5TokenDataMap *a1, int n1, /* Input array 1 */
248789
+ Fts5TokenDataMap *a2, int n2, /* Input array 2 */
248790
+ Fts5TokenDataMap *aOut /* Output array */
248791
+){
248792
+ int i1 = 0;
248793
+ int i2 = 0;
248794
+
248795
+ assert( n1>=0 && n2>=0 );
248796
+ while( i1<n1 || i2<n2 ){
248797
+ Fts5TokenDataMap *pOut = &aOut[i1+i2];
248798
+ if( i2>=n2 || (i1<n1 && (
248799
+ a1[i1].iRowid<a2[i2].iRowid
248800
+ || (a1[i1].iRowid==a2[i2].iRowid && a1[i1].iPos<=a2[i2].iPos)
248801
+ ))){
248802
+ memcpy(pOut, &a1[i1], sizeof(Fts5TokenDataMap));
248803
+ i1++;
248804
+ }else{
248805
+ memcpy(pOut, &a2[i2], sizeof(Fts5TokenDataMap));
248806
+ i2++;
248807
+ }
248808
+ }
248809
+}
248810
+
248811
+
248812
+/*
248813
+** Append a mapping to the token-map belonging to object pT.
248814
+*/
248815
+static void fts5TokendataIterAppendMap(
248816
+ Fts5Index *p,
248817
+ Fts5TokenDataIter *pT,
248818
+ int iIter,
248819
+ int nByte,
248820
+ i64 iRowid,
248821
+ i64 iPos
248822
+){
248823
+ if( p->rc==SQLITE_OK ){
248824
+ if( pT->nMap==pT->nMapAlloc ){
248825
+ int nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64;
248826
+ int nAlloc = nNew * sizeof(Fts5TokenDataMap);
248827
+ Fts5TokenDataMap *aNew;
248828
+
248829
+ aNew = (Fts5TokenDataMap*)sqlite3_realloc(pT->aMap, nAlloc);
248830
+ if( aNew==0 ){
248831
+ p->rc = SQLITE_NOMEM;
248832
+ return;
248833
+ }
248834
+
248835
+ pT->aMap = aNew;
248836
+ pT->nMapAlloc = nNew;
248837
+ }
248838
+
248839
+ pT->aMap[pT->nMap].iRowid = iRowid;
248840
+ pT->aMap[pT->nMap].iPos = iPos;
248841
+ pT->aMap[pT->nMap].iIter = iIter;
248842
+ pT->aMap[pT->nMap].nByte = nByte;
248843
+ pT->nMap++;
248844
+ }
248845
+}
248846
+
248847
+/*
248848
+** Sort the contents of the pT->aMap[] array.
248849
+**
248850
+** The sorting algorithm requries a malloc(). If this fails, an error code
248851
+** is left in Fts5Index.rc before returning.
248852
+*/
248853
+static void fts5TokendataIterSortMap(Fts5Index *p, Fts5TokenDataIter *pT){
248854
+ Fts5TokenDataMap *aTmp = 0;
248855
+ int nByte = pT->nMap * sizeof(Fts5TokenDataMap);
248856
+
248857
+ aTmp = (Fts5TokenDataMap*)sqlite3Fts5MallocZero(&p->rc, nByte);
248858
+ if( aTmp ){
248859
+ Fts5TokenDataMap *a1 = pT->aMap;
248860
+ Fts5TokenDataMap *a2 = aTmp;
248861
+ i64 nHalf;
248862
+
248863
+ for(nHalf=1; nHalf<pT->nMap; nHalf=nHalf*2){
248864
+ int i1;
248865
+ for(i1=0; i1<pT->nMap; i1+=(nHalf*2)){
248866
+ int n1 = MIN(nHalf, pT->nMap-i1);
248867
+ int n2 = MIN(nHalf, pT->nMap-i1-n1);
248868
+ fts5TokendataMerge(&a1[i1], n1, &a1[i1+n1], n2, &a2[i1]);
248869
+ }
248870
+ SWAPVAL(Fts5TokenDataMap*, a1, a2);
248871
+ }
248872
+
248873
+ if( a1!=pT->aMap ){
248874
+ memcpy(pT->aMap, a1, pT->nMap*sizeof(Fts5TokenDataMap));
248875
+ }
248876
+ sqlite3_free(aTmp);
248877
+
248878
+#ifdef SQLITE_DEBUG
248879
+ {
248880
+ int ii;
248881
+ for(ii=1; ii<pT->nMap; ii++){
248882
+ Fts5TokenDataMap *p1 = &pT->aMap[ii-1];
248883
+ Fts5TokenDataMap *p2 = &pT->aMap[ii];
248884
+ assert( p1->iRowid<p2->iRowid
248885
+ || (p1->iRowid==p2->iRowid && p1->iPos<=p2->iPos)
248886
+ );
248887
+ }
248888
+ }
248889
+#endif
248890
+ }
248891
+}
248892
+
248893
+/*
248894
+** Delete an Fts5TokenDataIter structure and its contents.
248895
+*/
248896
+static void fts5TokendataIterDelete(Fts5TokenDataIter *pSet){
248897
+ if( pSet ){
248898
+ int ii;
248899
+ for(ii=0; ii<pSet->nIter; ii++){
248900
+ fts5MultiIterFree(pSet->apIter[ii]);
248901
+ }
248902
+ fts5BufferFree(&pSet->terms);
248903
+ sqlite3_free(pSet->aPoslistReader);
248904
+ sqlite3_free(pSet->aMap);
248905
+ sqlite3_free(pSet);
248906
+ }
248907
+}
248908
+
248909
+
248910
+/*
248911
+** fts5VisitEntries() context object used by fts5SetupPrefixIterTokendata()
248912
+** to pass data to prefixIterSetupTokendataCb().
248913
+*/
248914
+typedef struct TokendataSetupCtx TokendataSetupCtx;
248915
+struct TokendataSetupCtx {
248916
+ Fts5TokenDataIter *pT; /* Object being populated with mappings */
248917
+ int iTermOff; /* Offset of current term in terms.p[] */
248918
+ int nTermByte; /* Size of current term in bytes */
248919
+};
248920
+
248921
+/*
248922
+** fts5VisitEntries() callback used by fts5SetupPrefixIterTokendata(). This
248923
+** callback adds an entry to the Fts5TokenDataIter.aMap[] array for each
248924
+** position in the current position-list. It doesn't matter that some of
248925
+** these may be out of order - they will be sorted later.
248926
+*/
248927
+static void prefixIterSetupTokendataCb(
248928
+ Fts5Index *p,
248929
+ void *pCtx,
248930
+ Fts5Iter *p1,
248931
+ const u8 *pNew,
248932
+ int nNew
248933
+){
248934
+ TokendataSetupCtx *pSetup = (TokendataSetupCtx*)pCtx;
248935
+ int iPosOff = 0;
248936
+ i64 iPos = 0;
248937
+
248938
+ if( pNew ){
248939
+ pSetup->nTermByte = nNew-1;
248940
+ pSetup->iTermOff = pSetup->pT->terms.n;
248941
+ fts5BufferAppendBlob(&p->rc, &pSetup->pT->terms, nNew-1, pNew+1);
248942
+ }
248943
+
248944
+ while( 0==sqlite3Fts5PoslistNext64(
248945
+ p1->base.pData, p1->base.nData, &iPosOff, &iPos
248946
+ ) ){
248947
+ fts5TokendataIterAppendMap(p,
248948
+ pSetup->pT, pSetup->iTermOff, pSetup->nTermByte, p1->base.iRowid, iPos
248949
+ );
248950
+ }
248951
+}
248952
+
248953
+
248954
+/*
248955
+** Context object passed by fts5SetupPrefixIter() to fts5VisitEntries().
248956
+*/
248957
+typedef struct PrefixSetupCtx PrefixSetupCtx;
248958
+struct PrefixSetupCtx {
248959
+ void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*);
248960
+ void (*xAppend)(Fts5Index*, u64, Fts5Iter*, Fts5Buffer*);
248961
+ i64 iLastRowid;
248962
+ int nMerge;
248963
+ Fts5Buffer *aBuf;
248964
+ int nBuf;
248965
+ Fts5Buffer doclist;
248966
+ TokendataSetupCtx *pTokendata;
248967
+};
248968
+
248969
+/*
248970
+** fts5VisitEntries() callback used by fts5SetupPrefixIter()
248971
+*/
248972
+static void prefixIterSetupCb(
248973
+ Fts5Index *p,
248974
+ void *pCtx,
248975
+ Fts5Iter *p1,
248976
+ const u8 *pNew,
248977
+ int nNew
248978
+){
248979
+ PrefixSetupCtx *pSetup = (PrefixSetupCtx*)pCtx;
248980
+ const int nMerge = pSetup->nMerge;
248981
+
248982
+ if( p1->base.nData>0 ){
248983
+ if( p1->base.iRowid<=pSetup->iLastRowid && pSetup->doclist.n>0 ){
248984
+ int i;
248985
+ for(i=0; p->rc==SQLITE_OK && pSetup->doclist.n; i++){
248986
+ int i1 = i*nMerge;
248987
+ int iStore;
248988
+ assert( i1+nMerge<=pSetup->nBuf );
248989
+ for(iStore=i1; iStore<i1+nMerge; iStore++){
248990
+ if( pSetup->aBuf[iStore].n==0 ){
248991
+ fts5BufferSwap(&pSetup->doclist, &pSetup->aBuf[iStore]);
248992
+ fts5BufferZero(&pSetup->doclist);
248993
+ break;
248994
+ }
248995
+ }
248996
+ if( iStore==i1+nMerge ){
248997
+ pSetup->xMerge(p, &pSetup->doclist, nMerge, &pSetup->aBuf[i1]);
248998
+ for(iStore=i1; iStore<i1+nMerge; iStore++){
248999
+ fts5BufferZero(&pSetup->aBuf[iStore]);
249000
+ }
249001
+ }
249002
+ }
249003
+ pSetup->iLastRowid = 0;
249004
+ }
249005
+
249006
+ pSetup->xAppend(
249007
+ p, (u64)p1->base.iRowid-(u64)pSetup->iLastRowid, p1, &pSetup->doclist
249008
+ );
249009
+ pSetup->iLastRowid = p1->base.iRowid;
249010
+ }
249011
+
249012
+ if( pSetup->pTokendata ){
249013
+ prefixIterSetupTokendataCb(p, (void*)pSetup->pTokendata, p1, pNew, nNew);
249014
+ }
249015
+}
249016
+
248430249017
static void fts5SetupPrefixIter(
248431249018
Fts5Index *p, /* Index to read from */
248432249019
int bDesc, /* True for "ORDER BY rowid DESC" */
248433249020
int iIdx, /* Index to scan for data */
248434249021
u8 *pToken, /* Buffer containing prefix to match */
@@ -248435,137 +249022,91 @@
248435249022
int nToken, /* Size of buffer pToken in bytes */
248436249023
Fts5Colset *pColset, /* Restrict matches to these columns */
248437249024
Fts5Iter **ppIter /* OUT: New iterator */
248438249025
){
248439249026
Fts5Structure *pStruct;
248440
- Fts5Buffer *aBuf;
248441
- int nBuf = 32;
248442
- int nMerge = 1;
249027
+ PrefixSetupCtx s;
249028
+ TokendataSetupCtx s2;
248443249029
248444
- void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*);
248445
- void (*xAppend)(Fts5Index*, u64, Fts5Iter*, Fts5Buffer*);
249030
+ memset(&s, 0, sizeof(s));
249031
+ memset(&s2, 0, sizeof(s2));
249032
+
249033
+ s.nMerge = 1;
249034
+ s.iLastRowid = 0;
249035
+ s.nBuf = 32;
249036
+ if( iIdx==0
249037
+ && p->pConfig->eDetail==FTS5_DETAIL_FULL
249038
+ && p->pConfig->bPrefixInsttoken
249039
+ ){
249040
+ s.pTokendata = &s2;
249041
+ s2.pT = (Fts5TokenDataIter*)fts5IdxMalloc(p, sizeof(*s2.pT));
249042
+ }
249043
+
248446249044
if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){
248447
- xMerge = fts5MergeRowidLists;
248448
- xAppend = fts5AppendRowid;
249045
+ s.xMerge = fts5MergeRowidLists;
249046
+ s.xAppend = fts5AppendRowid;
248449249047
}else{
248450
- nMerge = FTS5_MERGE_NLIST-1;
248451
- nBuf = nMerge*8; /* Sufficient to merge (16^8)==(2^32) lists */
248452
- xMerge = fts5MergePrefixLists;
248453
- xAppend = fts5AppendPoslist;
249048
+ s.nMerge = FTS5_MERGE_NLIST-1;
249049
+ s.nBuf = s.nMerge*8; /* Sufficient to merge (16^8)==(2^32) lists */
249050
+ s.xMerge = fts5MergePrefixLists;
249051
+ s.xAppend = fts5AppendPoslist;
248454249052
}
248455249053
248456
- aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*nBuf);
249054
+ s.aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*s.nBuf);
248457249055
pStruct = fts5StructureRead(p);
248458
- assert( p->rc!=SQLITE_OK || (aBuf && pStruct) );
249056
+ assert( p->rc!=SQLITE_OK || (s.aBuf && pStruct) );
248459249057
248460249058
if( p->rc==SQLITE_OK ){
248461
- const int flags = FTS5INDEX_QUERY_SCAN
248462
- | FTS5INDEX_QUERY_SKIPEMPTY
248463
- | FTS5INDEX_QUERY_NOOUTPUT;
249059
+ void *pCtx = (void*)&s;
248464249060
int i;
248465
- i64 iLastRowid = 0;
248466
- Fts5Iter *p1 = 0; /* Iterator used to gather data from index */
248467249061
Fts5Data *pData;
248468
- Fts5Buffer doclist;
248469
- int bNewTerm = 1;
248470
-
248471
- memset(&doclist, 0, sizeof(doclist));
248472249062
248473249063
/* If iIdx is non-zero, then it is the number of a prefix-index for
248474249064
** prefixes 1 character longer than the prefix being queried for. That
248475249065
** index contains all the doclists required, except for the one
248476249066
** corresponding to the prefix itself. That one is extracted from the
248477249067
** main term index here. */
248478249068
if( iIdx!=0 ){
248479
- int dummy = 0;
248480
- const int f2 = FTS5INDEX_QUERY_SKIPEMPTY|FTS5INDEX_QUERY_NOOUTPUT;
248481249069
pToken[0] = FTS5_MAIN_PREFIX;
248482
- fts5MultiIterNew(p, pStruct, f2, pColset, pToken, nToken, -1, 0, &p1);
248483
- fts5IterSetOutputCb(&p->rc, p1);
248484
- for(;
248485
- fts5MultiIterEof(p, p1)==0;
248486
- fts5MultiIterNext2(p, p1, &dummy)
248487
- ){
248488
- Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ];
248489
- p1->xSetOutputs(p1, pSeg);
248490
- if( p1->base.nData ){
248491
- xAppend(p, (u64)p1->base.iRowid-(u64)iLastRowid, p1, &doclist);
248492
- iLastRowid = p1->base.iRowid;
248493
- }
248494
- }
248495
- fts5MultiIterFree(p1);
249070
+ fts5VisitEntries(p, pColset, pToken, nToken, 0, prefixIterSetupCb, pCtx);
248496249071
}
248497249072
248498249073
pToken[0] = FTS5_MAIN_PREFIX + iIdx;
248499
- fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1);
248500
- fts5IterSetOutputCb(&p->rc, p1);
248501
-
248502
- for( /* no-op */ ;
248503
- fts5MultiIterEof(p, p1)==0;
248504
- fts5MultiIterNext2(p, p1, &bNewTerm)
248505
- ){
248506
- Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ];
248507
- int nTerm = pSeg->term.n;
248508
- const u8 *pTerm = pSeg->term.p;
248509
- p1->xSetOutputs(p1, pSeg);
248510
-
248511
- assert_nc( memcmp(pToken, pTerm, MIN(nToken, nTerm))<=0 );
248512
- if( bNewTerm ){
248513
- if( nTerm<nToken || memcmp(pToken, pTerm, nToken) ) break;
248514
- }
248515
-
248516
- if( p1->base.nData==0 ) continue;
248517
- if( p1->base.iRowid<=iLastRowid && doclist.n>0 ){
248518
- for(i=0; p->rc==SQLITE_OK && doclist.n; i++){
248519
- int i1 = i*nMerge;
248520
- int iStore;
248521
- assert( i1+nMerge<=nBuf );
248522
- for(iStore=i1; iStore<i1+nMerge; iStore++){
248523
- if( aBuf[iStore].n==0 ){
248524
- fts5BufferSwap(&doclist, &aBuf[iStore]);
248525
- fts5BufferZero(&doclist);
248526
- break;
248527
- }
248528
- }
248529
- if( iStore==i1+nMerge ){
248530
- xMerge(p, &doclist, nMerge, &aBuf[i1]);
248531
- for(iStore=i1; iStore<i1+nMerge; iStore++){
248532
- fts5BufferZero(&aBuf[iStore]);
248533
- }
248534
- }
248535
- }
248536
- iLastRowid = 0;
248537
- }
248538
-
248539
- xAppend(p, (u64)p1->base.iRowid-(u64)iLastRowid, p1, &doclist);
248540
- iLastRowid = p1->base.iRowid;
248541
- }
248542
-
248543
- assert( (nBuf%nMerge)==0 );
248544
- for(i=0; i<nBuf; i+=nMerge){
249074
+ fts5VisitEntries(p, pColset, pToken, nToken, 1, prefixIterSetupCb, pCtx);
249075
+
249076
+ assert( (s.nBuf%s.nMerge)==0 );
249077
+ for(i=0; i<s.nBuf; i+=s.nMerge){
248545249078
int iFree;
248546249079
if( p->rc==SQLITE_OK ){
248547
- xMerge(p, &doclist, nMerge, &aBuf[i]);
249080
+ s.xMerge(p, &s.doclist, s.nMerge, &s.aBuf[i]);
248548249081
}
248549
- for(iFree=i; iFree<i+nMerge; iFree++){
248550
- fts5BufferFree(&aBuf[iFree]);
249082
+ for(iFree=i; iFree<i+s.nMerge; iFree++){
249083
+ fts5BufferFree(&s.aBuf[iFree]);
248551249084
}
248552249085
}
248553
- fts5MultiIterFree(p1);
248554249086
248555
- pData = fts5IdxMalloc(p, sizeof(*pData)+doclist.n+FTS5_DATA_ZERO_PADDING);
249087
+ pData = fts5IdxMalloc(p, sizeof(*pData)+s.doclist.n+FTS5_DATA_ZERO_PADDING);
249088
+ assert( pData!=0 || p->rc!=SQLITE_OK );
248556249089
if( pData ){
248557249090
pData->p = (u8*)&pData[1];
248558
- pData->nn = pData->szLeaf = doclist.n;
248559
- if( doclist.n ) memcpy(pData->p, doclist.p, doclist.n);
249091
+ pData->nn = pData->szLeaf = s.doclist.n;
249092
+ if( s.doclist.n ) memcpy(pData->p, s.doclist.p, s.doclist.n);
248560249093
fts5MultiIterNew2(p, pData, bDesc, ppIter);
248561249094
}
248562
- fts5BufferFree(&doclist);
249095
+
249096
+ assert( (*ppIter)!=0 || p->rc!=SQLITE_OK );
249097
+ if( p->rc==SQLITE_OK && s.pTokendata ){
249098
+ fts5TokendataIterSortMap(p, s2.pT);
249099
+ (*ppIter)->pTokenDataIter = s2.pT;
249100
+ s2.pT = 0;
249101
+ }
248563249102
}
248564249103
249104
+ fts5TokendataIterDelete(s2.pT);
249105
+ fts5BufferFree(&s.doclist);
248565249106
fts5StructureRelease(pStruct);
248566
- sqlite3_free(aBuf);
249107
+ sqlite3_free(s.aBuf);
248567249108
}
248568249109
248569249110
248570249111
/*
248571249112
** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain
@@ -248815,42 +249356,10 @@
248815249356
static void fts5SegIterSetEOF(Fts5SegIter *pSeg){
248816249357
fts5DataRelease(pSeg->pLeaf);
248817249358
pSeg->pLeaf = 0;
248818249359
}
248819249360
248820
-/*
248821
-** Usually, a tokendata=1 iterator (struct Fts5TokenDataIter) accumulates an
248822
-** array of these for each row it visits. Or, for an iterator used by an
248823
-** "ORDER BY rank" query, it accumulates an array of these for the entire
248824
-** query.
248825
-**
248826
-** Each instance in the array indicates the iterator (and therefore term)
248827
-** associated with position iPos of rowid iRowid. This is used by the
248828
-** xInstToken() API.
248829
-*/
248830
-struct Fts5TokenDataMap {
248831
- i64 iRowid; /* Row this token is located in */
248832
- i64 iPos; /* Position of token */
248833
- int iIter; /* Iterator token was read from */
248834
-};
248835
-
248836
-/*
248837
-** An object used to supplement Fts5Iter for tokendata=1 iterators.
248838
-*/
248839
-struct Fts5TokenDataIter {
248840
- int nIter;
248841
- int nIterAlloc;
248842
-
248843
- int nMap;
248844
- int nMapAlloc;
248845
- Fts5TokenDataMap *aMap;
248846
-
248847
- Fts5PoslistReader *aPoslistReader;
248848
- int *aPoslistToIter;
248849
- Fts5Iter *apIter[1];
248850
-};
248851
-
248852249361
/*
248853249362
** This function appends iterator pAppend to Fts5TokenDataIter pIn and
248854249363
** returns the result.
248855249364
*/
248856249365
static Fts5TokenDataIter *fts5AppendTokendataIter(
@@ -248883,58 +249392,10 @@
248883249392
assert( pRet==0 || pRet->nIter<=pRet->nIterAlloc );
248884249393
248885249394
return pRet;
248886249395
}
248887249396
248888
-/*
248889
-** Delete an Fts5TokenDataIter structure and its contents.
248890
-*/
248891
-static void fts5TokendataIterDelete(Fts5TokenDataIter *pSet){
248892
- if( pSet ){
248893
- int ii;
248894
- for(ii=0; ii<pSet->nIter; ii++){
248895
- fts5MultiIterFree(pSet->apIter[ii]);
248896
- }
248897
- sqlite3_free(pSet->aPoslistReader);
248898
- sqlite3_free(pSet->aMap);
248899
- sqlite3_free(pSet);
248900
- }
248901
-}
248902
-
248903
-/*
248904
-** Append a mapping to the token-map belonging to object pT.
248905
-*/
248906
-static void fts5TokendataIterAppendMap(
248907
- Fts5Index *p,
248908
- Fts5TokenDataIter *pT,
248909
- int iIter,
248910
- i64 iRowid,
248911
- i64 iPos
248912
-){
248913
- if( p->rc==SQLITE_OK ){
248914
- if( pT->nMap==pT->nMapAlloc ){
248915
- int nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64;
248916
- int nByte = nNew * sizeof(Fts5TokenDataMap);
248917
- Fts5TokenDataMap *aNew;
248918
-
248919
- aNew = (Fts5TokenDataMap*)sqlite3_realloc(pT->aMap, nByte);
248920
- if( aNew==0 ){
248921
- p->rc = SQLITE_NOMEM;
248922
- return;
248923
- }
248924
-
248925
- pT->aMap = aNew;
248926
- pT->nMapAlloc = nNew;
248927
- }
248928
-
248929
- pT->aMap[pT->nMap].iRowid = iRowid;
248930
- pT->aMap[pT->nMap].iPos = iPos;
248931
- pT->aMap[pT->nMap].iIter = iIter;
248932
- pT->nMap++;
248933
- }
248934
-}
248935
-
248936249397
/*
248937249398
** The iterator passed as the only argument must be a tokendata=1 iterator
248938249399
** (pIter->pTokenDataIter!=0). This function sets the iterator output
248939249400
** variables (pIter->base.*) according to the contents of the current
248940249401
** row.
@@ -248971,11 +249432,11 @@
248971249432
int eDetail = pIter->pIndex->pConfig->eDetail;
248972249433
pIter->base.bEof = 0;
248973249434
pIter->base.iRowid = iRowid;
248974249435
248975249436
if( nHit==1 && eDetail==FTS5_DETAIL_FULL ){
248976
- fts5TokendataIterAppendMap(pIter->pIndex, pT, iMin, iRowid, -1);
249437
+ fts5TokendataIterAppendMap(pIter->pIndex, pT, iMin, 0, iRowid, -1);
248977249438
}else
248978249439
if( nHit>1 && eDetail!=FTS5_DETAIL_NONE ){
248979249440
int nReader = 0;
248980249441
int nByte = 0;
248981249442
i64 iPrev = 0;
@@ -249224,10 +249685,11 @@
249224249685
249225249686
if( p->rc==SQLITE_OK ){
249226249687
pRet = fts5MultiIterAlloc(p, 0);
249227249688
}
249228249689
if( pRet ){
249690
+ pRet->nSeg = 0;
249229249691
pRet->pTokenDataIter = pSet;
249230249692
if( pSet ){
249231249693
fts5IterSetOutputsTokendata(pRet);
249232249694
}else{
249233249695
pRet->base.bEof = 1;
@@ -249238,11 +249700,10 @@
249238249700
249239249701
fts5StructureRelease(pStruct);
249240249702
fts5BufferFree(&bSeek);
249241249703
return pRet;
249242249704
}
249243
-
249244249705
249245249706
/*
249246249707
** Open a new iterator to iterate though all rowid that match the
249247249708
** specified token or token prefix.
249248249709
*/
@@ -249262,12 +249723,18 @@
249262249723
249263249724
if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){
249264249725
int iIdx = 0; /* Index to search */
249265249726
int iPrefixIdx = 0; /* +1 prefix index */
249266249727
int bTokendata = pConfig->bTokendata;
249728
+ assert( buf.p!=0 );
249267249729
if( nToken>0 ) memcpy(&buf.p[1], pToken, nToken);
249268249730
249731
+ /* The NOTOKENDATA flag is set when each token in a tokendata=1 table
249732
+ ** should be treated individually, instead of merging all those with
249733
+ ** a common prefix into a single entry. This is used, for example, by
249734
+ ** queries performed as part of an integrity-check, or by the fts5vocab
249735
+ ** module. */
249269249736
if( flags & (FTS5INDEX_QUERY_NOTOKENDATA|FTS5INDEX_QUERY_SCAN) ){
249270249737
bTokendata = 0;
249271249738
}
249272249739
249273249740
/* Figure out which index to search and set iIdx accordingly. If this
@@ -249294,11 +249761,11 @@
249294249761
if( nIdxChar==nChar+1 ) iPrefixIdx = iIdx;
249295249762
}
249296249763
}
249297249764
249298249765
if( bTokendata && iIdx==0 ){
249299
- buf.p[0] = '0';
249766
+ buf.p[0] = FTS5_MAIN_PREFIX;
249300249767
pRet = fts5SetupTokendataIter(p, buf.p, nToken+1, pColset);
249301249768
}else if( iIdx<=pConfig->nPrefix ){
249302249769
/* Straight index lookup */
249303249770
Fts5Structure *pStruct = fts5StructureRead(p);
249304249771
buf.p[0] = (u8)(FTS5_MAIN_PREFIX + iIdx);
@@ -249307,11 +249774,11 @@
249307249774
pColset, buf.p, nToken+1, -1, 0, &pRet
249308249775
);
249309249776
fts5StructureRelease(pStruct);
249310249777
}
249311249778
}else{
249312
- /* Scan multiple terms in the main index */
249779
+ /* Scan multiple terms in the main index for a prefix query. */
249313249780
int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0;
249314249781
fts5SetupPrefixIter(p, bDesc, iPrefixIdx, buf.p, nToken+1, pColset,&pRet);
249315249782
if( pRet==0 ){
249316249783
assert( p->rc!=SQLITE_OK );
249317249784
}else{
@@ -249343,11 +249810,12 @@
249343249810
** Move to the next matching rowid.
249344249811
*/
249345249812
static int sqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){
249346249813
Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
249347249814
assert( pIter->pIndex->rc==SQLITE_OK );
249348
- if( pIter->pTokenDataIter ){
249815
+ if( pIter->nSeg==0 ){
249816
+ assert( pIter->pTokenDataIter );
249349249817
fts5TokendataIterNext(pIter, 0, 0);
249350249818
}else{
249351249819
fts5MultiIterNext(pIter->pIndex, pIter, 0, 0);
249352249820
}
249353249821
return fts5IndexReturn(pIter->pIndex);
@@ -249380,11 +249848,12 @@
249380249848
** definition of "at or after" depends on whether this iterator iterates
249381249849
** in ascending or descending rowid order.
249382249850
*/
249383249851
static int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){
249384249852
Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
249385
- if( pIter->pTokenDataIter ){
249853
+ if( pIter->nSeg==0 ){
249854
+ assert( pIter->pTokenDataIter );
249386249855
fts5TokendataIterNext(pIter, 1, iMatch);
249387249856
}else{
249388249857
fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch);
249389249858
}
249390249859
return fts5IndexReturn(pIter->pIndex);
@@ -249398,32 +249867,88 @@
249398249867
const char *z = (const char*)fts5MultiIterTerm((Fts5Iter*)pIndexIter, &n);
249399249868
assert_nc( z || n<=1 );
249400249869
*pn = n-1;
249401249870
return (z ? &z[1] : 0);
249402249871
}
249872
+
249873
+/*
249874
+** pIter is a prefix query. This function populates pIter->pTokenDataIter
249875
+** with an Fts5TokenDataIter object containing mappings for all rows
249876
+** matched by the query.
249877
+*/
249878
+static int fts5SetupPrefixIterTokendata(
249879
+ Fts5Iter *pIter,
249880
+ const char *pToken, /* Token prefix to search for */
249881
+ int nToken /* Size of pToken in bytes */
249882
+){
249883
+ Fts5Index *p = pIter->pIndex;
249884
+ Fts5Buffer token = {0, 0, 0};
249885
+ TokendataSetupCtx ctx;
249886
+
249887
+ memset(&ctx, 0, sizeof(ctx));
249888
+
249889
+ fts5BufferGrow(&p->rc, &token, nToken+1);
249890
+ assert( token.p!=0 || p->rc!=SQLITE_OK );
249891
+ ctx.pT = (Fts5TokenDataIter*)sqlite3Fts5MallocZero(&p->rc, sizeof(*ctx.pT));
249892
+
249893
+ if( p->rc==SQLITE_OK ){
249894
+
249895
+ /* Fill in the token prefix to search for */
249896
+ token.p[0] = FTS5_MAIN_PREFIX;
249897
+ memcpy(&token.p[1], pToken, nToken);
249898
+ token.n = nToken+1;
249899
+
249900
+ fts5VisitEntries(
249901
+ p, 0, token.p, token.n, 1, prefixIterSetupTokendataCb, (void*)&ctx
249902
+ );
249903
+
249904
+ fts5TokendataIterSortMap(p, ctx.pT);
249905
+ }
249906
+
249907
+ if( p->rc==SQLITE_OK ){
249908
+ pIter->pTokenDataIter = ctx.pT;
249909
+ }else{
249910
+ fts5TokendataIterDelete(ctx.pT);
249911
+ }
249912
+ fts5BufferFree(&token);
249913
+
249914
+ return fts5IndexReturn(p);
249915
+}
249403249916
249404249917
/*
249405249918
** This is used by xInstToken() to access the token at offset iOff, column
249406249919
** iCol of row iRowid. The token is returned via output variables *ppOut
249407249920
** and *pnOut. The iterator passed as the first argument must be a tokendata=1
249408249921
** iterator (pIter->pTokenDataIter!=0).
249922
+**
249923
+** pToken/nToken:
249409249924
*/
249410249925
static int sqlite3Fts5IterToken(
249411249926
Fts5IndexIter *pIndexIter,
249927
+ const char *pToken, int nToken,
249412249928
i64 iRowid,
249413249929
int iCol,
249414249930
int iOff,
249415249931
const char **ppOut, int *pnOut
249416249932
){
249417249933
Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
249418249934
Fts5TokenDataIter *pT = pIter->pTokenDataIter;
249419
- Fts5TokenDataMap *aMap = pT->aMap;
249420249935
i64 iPos = (((i64)iCol)<<32) + iOff;
249421
-
249936
+ Fts5TokenDataMap *aMap = 0;
249422249937
int i1 = 0;
249423
- int i2 = pT->nMap;
249938
+ int i2 = 0;
249424249939
int iTest = 0;
249940
+
249941
+ assert( pT || (pToken && pIter->nSeg>0) );
249942
+ if( pT==0 ){
249943
+ int rc = fts5SetupPrefixIterTokendata(pIter, pToken, nToken);
249944
+ if( rc!=SQLITE_OK ) return rc;
249945
+ pT = pIter->pTokenDataIter;
249946
+ }
249947
+
249948
+ i2 = pT->nMap;
249949
+ aMap = pT->aMap;
249425249950
249426249951
while( i2>i1 ){
249427249952
iTest = (i1 + i2) / 2;
249428249953
249429249954
if( aMap[iTest].iRowid<iRowid ){
@@ -249443,13 +249968,19 @@
249443249968
}
249444249969
}
249445249970
}
249446249971
249447249972
if( i2>i1 ){
249448
- Fts5Iter *pMap = pT->apIter[aMap[iTest].iIter];
249449
- *ppOut = (const char*)pMap->aSeg[0].term.p+1;
249450
- *pnOut = pMap->aSeg[0].term.n-1;
249973
+ if( pIter->nSeg==0 ){
249974
+ Fts5Iter *pMap = pT->apIter[aMap[iTest].iIter];
249975
+ *ppOut = (const char*)pMap->aSeg[0].term.p+1;
249976
+ *pnOut = pMap->aSeg[0].term.n-1;
249977
+ }else{
249978
+ Fts5TokenDataMap *p = &aMap[iTest];
249979
+ *ppOut = (const char*)&pT->terms.p[p->iIter];
249980
+ *pnOut = aMap[iTest].nByte;
249981
+ }
249451249982
}
249452249983
249453249984
return SQLITE_OK;
249454249985
}
249455249986
@@ -249457,11 +249988,13 @@
249457249988
** Clear any existing entries from the token-map associated with the
249458249989
** iterator passed as the only argument.
249459249990
*/
249460249991
static void sqlite3Fts5IndexIterClearTokendata(Fts5IndexIter *pIndexIter){
249461249992
Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
249462
- if( pIter && pIter->pTokenDataIter ){
249993
+ if( pIter && pIter->pTokenDataIter
249994
+ && (pIter->nSeg==0 || pIter->pIndex->pConfig->eDetail!=FTS5_DETAIL_FULL)
249995
+ ){
249463249996
pIter->pTokenDataIter->nMap = 0;
249464249997
}
249465249998
}
249466249999
249467250000
/*
@@ -249477,21 +250010,33 @@
249477250010
i64 iRowid, int iCol, int iOff
249478250011
){
249479250012
Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
249480250013
Fts5TokenDataIter *pT = pIter->pTokenDataIter;
249481250014
Fts5Index *p = pIter->pIndex;
249482
- int ii;
250015
+ i64 iPos = (((i64)iCol)<<32) + iOff;
249483250016
249484250017
assert( p->pConfig->eDetail!=FTS5_DETAIL_FULL );
249485
- assert( pIter->pTokenDataIter );
249486
-
249487
- for(ii=0; ii<pT->nIter; ii++){
249488
- Fts5Buffer *pTerm = &pT->apIter[ii]->aSeg[0].term;
249489
- if( nToken==pTerm->n-1 && memcmp(pToken, pTerm->p+1, nToken)==0 ) break;
249490
- }
249491
- if( ii<pT->nIter ){
249492
- fts5TokendataIterAppendMap(p, pT, ii, iRowid, (((i64)iCol)<<32) + iOff);
250018
+ assert( pIter->pTokenDataIter || pIter->nSeg>0 );
250019
+ if( pIter->nSeg>0 ){
250020
+ /* This is a prefix term iterator. */
250021
+ if( pT==0 ){
250022
+ pT = (Fts5TokenDataIter*)sqlite3Fts5MallocZero(&p->rc, sizeof(*pT));
250023
+ pIter->pTokenDataIter = pT;
250024
+ }
250025
+ if( pT ){
250026
+ fts5TokendataIterAppendMap(p, pT, pT->terms.n, nToken, iRowid, iPos);
250027
+ fts5BufferAppendBlob(&p->rc, &pT->terms, nToken, (const u8*)pToken);
250028
+ }
250029
+ }else{
250030
+ int ii;
250031
+ for(ii=0; ii<pT->nIter; ii++){
250032
+ Fts5Buffer *pTerm = &pT->apIter[ii]->aSeg[0].term;
250033
+ if( nToken==pTerm->n-1 && memcmp(pToken, pTerm->p+1, nToken)==0 ) break;
250034
+ }
250035
+ if( ii<pT->nIter ){
250036
+ fts5TokendataIterAppendMap(p, pT, ii, 0, iRowid, iPos);
250037
+ }
249493250038
}
249494250039
return fts5IndexReturn(p);
249495250040
}
249496250041
249497250042
/*
@@ -251392,10 +251937,11 @@
251392251937
** containing a copy of the header from an Fts5Config pointer.
251393251938
*/
251394251939
#define FTS5_LOCALE_HDR_SIZE ((int)sizeof( ((Fts5Global*)0)->aLocaleHdr ))
251395251940
#define FTS5_LOCALE_HDR(pConfig) ((const u8*)(pConfig->pGlobal->aLocaleHdr))
251396251941
251942
+#define FTS5_INSTTOKEN_SUBTYPE 73
251397251943
251398251944
/*
251399251945
** Each auxiliary function registered with the FTS5 module is represented
251400251946
** by an object of the following type. All such objects are stored as part
251401251947
** of the Fts5Global.pAux list.
@@ -251931,10 +252477,11 @@
251931252477
){
251932252478
/* A MATCH operator or equivalent */
251933252479
if( p->usable==0 || iCol<0 ){
251934252480
/* As there exists an unusable MATCH constraint this is an
251935252481
** unusable plan. Return SQLITE_CONSTRAINT. */
252482
+ idxStr[iIdxStr] = 0;
251936252483
return SQLITE_CONSTRAINT;
251937252484
}else{
251938252485
if( iCol==nCol+1 ){
251939252486
if( bSeenRank ) continue;
251940252487
idxStr[iIdxStr++] = 'r';
@@ -252716,10 +253263,11 @@
252716253263
sqlite3_value *pRowidEq = 0; /* rowid = ? expression (or NULL) */
252717253264
sqlite3_value *pRowidLe = 0; /* rowid <= ? expression (or NULL) */
252718253265
sqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */
252719253266
int iCol; /* Column on LHS of MATCH operator */
252720253267
char **pzErrmsg = pConfig->pzErrmsg;
253268
+ int bPrefixInsttoken = pConfig->bPrefixInsttoken;
252721253269
int i;
252722253270
int iIdxStr = 0;
252723253271
Fts5Expr *pExpr = 0;
252724253272
252725253273
assert( pConfig->bLock==0 );
@@ -252751,10 +253299,13 @@
252751253299
int bInternal = 0;
252752253300
252753253301
rc = fts5ExtractExprText(pConfig, apVal[i], &zText, &bFreeAndReset);
252754253302
if( rc!=SQLITE_OK ) goto filter_out;
252755253303
if( zText==0 ) zText = "";
253304
+ if( sqlite3_value_subtype(apVal[i])==FTS5_INSTTOKEN_SUBTYPE ){
253305
+ pConfig->bPrefixInsttoken = 1;
253306
+ }
252756253307
252757253308
iCol = 0;
252758253309
do{
252759253310
iCol = iCol*10 + (idxStr[iIdxStr]-'0');
252760253311
iIdxStr++;
@@ -252891,10 +253442,11 @@
252891253442
}
252892253443
252893253444
filter_out:
252894253445
sqlite3Fts5ExprFree(pExpr);
252895253446
pConfig->pzErrmsg = pzErrmsg;
253447
+ pConfig->bPrefixInsttoken = bPrefixInsttoken;
252896253448
return rc;
252897253449
}
252898253450
252899253451
/*
252900253452
** This is the xEof method of the virtual table. SQLite calls this
@@ -254886,11 +255438,11 @@
254886255438
int nArg, /* Number of args */
254887255439
sqlite3_value **apUnused /* Function arguments */
254888255440
){
254889255441
assert( nArg==0 );
254890255442
UNUSED_PARAM2(nArg, apUnused);
254891
- sqlite3_result_text(pCtx, "fts5: 2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f28411c85dda156f82636e", -1, SQLITE_TRANSIENT);
255443
+ sqlite3_result_text(pCtx, "fts5: 2024-12-30 21:23:53 2b17bc49655c577029919c2d409de994b0d252f8efb5da1ba0913f2c96bee552", -1, SQLITE_TRANSIENT);
254892255444
}
254893255445
254894255446
/*
254895255447
** Implementation of fts5_locale(LOCALE, TEXT) function.
254896255448
**
@@ -254949,10 +255501,24 @@
254949255501
assert( &pCsr[nText]==&pBlob[nBlob] );
254950255502
254951255503
sqlite3_result_blob(pCtx, pBlob, nBlob, sqlite3_free);
254952255504
}
254953255505
}
255506
+
255507
+/*
255508
+** Implementation of fts5_insttoken() function.
255509
+*/
255510
+static void fts5InsttokenFunc(
255511
+ sqlite3_context *pCtx, /* Function call context */
255512
+ int nArg, /* Number of args */
255513
+ sqlite3_value **apArg /* Function arguments */
255514
+){
255515
+ assert( nArg==1 );
255516
+ (void)nArg;
255517
+ sqlite3_result_value(pCtx, apArg[0]);
255518
+ sqlite3_result_subtype(pCtx, FTS5_INSTTOKEN_SUBTYPE);
255519
+}
254954255520
254955255521
/*
254956255522
** Return true if zName is the extension on one of the shadow tables used
254957255523
** by this module.
254958255524
*/
@@ -255079,13 +255645,20 @@
255079255645
);
255080255646
}
255081255647
if( rc==SQLITE_OK ){
255082255648
rc = sqlite3_create_function(
255083255649
db, "fts5_locale", 2,
255084
- SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE,
255650
+ SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE|SQLITE_SUBTYPE,
255085255651
p, fts5LocaleFunc, 0, 0
255086255652
);
255653
+ }
255654
+ if( rc==SQLITE_OK ){
255655
+ rc = sqlite3_create_function(
255656
+ db, "fts5_insttoken", 1,
255657
+ SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE,
255658
+ p, fts5InsttokenFunc, 0, 0
255659
+ );
255087255660
}
255088255661
}
255089255662
255090255663
/* If SQLITE_FTS5_ENABLE_TEST_MI is defined, assume that the file
255091255664
** fts5_test_mi.c is compiled and linked into the executable. And call
@@ -258007,22 +258580,22 @@
258007258580
int rc = SQLITE_OK;
258008258581
char aBuf[32];
258009258582
char *zOut = aBuf;
258010258583
int ii;
258011258584
const unsigned char *zIn = (const unsigned char*)pText;
258012
- const unsigned char *zEof = &zIn[nText];
258013
- u32 iCode;
258585
+ const unsigned char *zEof = (zIn ? &zIn[nText] : 0);
258586
+ u32 iCode = 0;
258014258587
int aStart[3]; /* Input offset of each character in aBuf[] */
258015258588
258016258589
UNUSED_PARAM(unusedFlags);
258017258590
258018258591
/* Populate aBuf[] with the characters for the first trigram. */
258019258592
for(ii=0; ii<3; ii++){
258020258593
do {
258021258594
aStart[ii] = zIn - (const unsigned char*)pText;
258595
+ if( zIn>=zEof ) return SQLITE_OK;
258022258596
READ_UTF8(zIn, zEof, iCode);
258023
- if( iCode==0 ) return SQLITE_OK;
258024258597
if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam);
258025258598
}while( iCode==0 );
258026258599
WRITE_UTF8(zOut, iCode);
258027258600
}
258028258601
@@ -258039,12 +258612,15 @@
258039258612
const char *z1;
258040258613
258041258614
/* Read characters from the input up until the first non-diacritic */
258042258615
do {
258043258616
iNext = zIn - (const unsigned char*)pText;
258617
+ if( zIn>=zEof ){
258618
+ iCode = 0;
258619
+ break;
258620
+ }
258044258621
READ_UTF8(zIn, zEof, iCode);
258045
- if( iCode==0 ) break;
258046258622
if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam);
258047258623
}while( iCode==0 );
258048258624
258049258625
/* Pass the current trigram back to fts5 */
258050258626
rc = xToken(pCtx, 0, aBuf, zOut-aBuf, aStart[0], iNext);
@@ -260077,11 +260653,11 @@
260077260653
260078260654
return sqlite3_create_module_v2(db, "fts5vocab", &fts5Vocab, p, 0);
260079260655
}
260080260656
260081260657
260082
-
260658
+/* Here ends the fts5.c composite file. */
260083260659
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) */
260084260660
260085260661
/************** End of fts5.c ************************************************/
260086260662
/************** Begin file stmt.c ********************************************/
260087260663
/*
@@ -260433,6 +261009,7 @@
260433261009
#endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */
260434261010
260435261011
/************** End of stmt.c ************************************************/
260436261012
/* Return the source-id for this library */
260437261013
SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }
261014
+#endif /* SQLITE_AMALGAMATION */
260438261015
/************************** End of sqlite3.c ******************************/
260439261016
--- extsrc/sqlite3.c
+++ extsrc/sqlite3.c
@@ -1,8 +1,8 @@
1 /******************************************************************************
2 ** This file is an amalgamation of many separate C source files from SQLite
3 ** version 3.47.0. By combining all the individual C code files into this
4 ** single large file, the entire code can be compiled as a single translation
5 ** unit. This allows many compilers to do optimizations that would not be
6 ** possible if the files were compiled separately. Performance improvements
7 ** of 5% or more are commonly seen when SQLite is compiled as a single
8 ** translation unit.
@@ -16,12 +16,15 @@
16 ** if you want a wrapper to interface SQLite with your choice of programming
17 ** language. The code for the "sqlite3" command-line shell is also in a
18 ** separate file. This file contains only code for the core SQLite library.
19 **
20 ** The content in this amalgamation comes from Fossil check-in
21 ** 03a9703e27c44437c39363d0baf82db4ebc9.
 
 
22 */
 
23 #define SQLITE_CORE 1
24 #define SQLITE_AMALGAMATION 1
25 #ifndef SQLITE_PRIVATE
26 # define SQLITE_PRIVATE static
27 #endif
@@ -460,13 +463,13 @@
460 **
461 ** See also: [sqlite3_libversion()],
462 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
463 ** [sqlite_version()] and [sqlite_source_id()].
464 */
465 #define SQLITE_VERSION "3.47.0"
466 #define SQLITE_VERSION_NUMBER 3047000
467 #define SQLITE_SOURCE_ID "2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f28411c85dda156f82636e"
468
469 /*
470 ** CAPI3REF: Run-Time Library Version Numbers
471 ** KEYWORDS: sqlite3_version sqlite3_sourceid
472 **
@@ -966,10 +969,17 @@
966 **
967 ** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying
968 ** filesystem supports doing multiple write operations atomically when those
969 ** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and
970 ** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE].
 
 
 
 
 
 
 
971 */
972 #define SQLITE_IOCAP_ATOMIC 0x00000001
973 #define SQLITE_IOCAP_ATOMIC512 0x00000002
974 #define SQLITE_IOCAP_ATOMIC1K 0x00000004
975 #define SQLITE_IOCAP_ATOMIC2K 0x00000008
@@ -982,10 +992,11 @@
982 #define SQLITE_IOCAP_SEQUENTIAL 0x00000400
983 #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800
984 #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000
985 #define SQLITE_IOCAP_IMMUTABLE 0x00002000
986 #define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000
 
987
988 /*
989 ** CAPI3REF: File Locking Levels
990 **
991 ** SQLite uses one of these integer values as the second
@@ -1128,10 +1139,11 @@
1128 ** <li> [SQLITE_IOCAP_SEQUENTIAL]
1129 ** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN]
1130 ** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE]
1131 ** <li> [SQLITE_IOCAP_IMMUTABLE]
1132 ** <li> [SQLITE_IOCAP_BATCH_ATOMIC]
 
1133 ** </ul>
1134 **
1135 ** The SQLITE_IOCAP_ATOMIC property means that all writes of
1136 ** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values
1137 ** mean that writes of blocks that are nnn bytes in size and
@@ -1405,10 +1417,15 @@
1405 ** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This
1406 ** opcode causes the xFileControl method to swap the file handle with the one
1407 ** pointed to by the pArg argument. This capability is used during testing
1408 ** and only needs to be supported when SQLITE_TEST is defined.
1409 **
 
 
 
 
 
1410 ** <li>[[SQLITE_FCNTL_WAL_BLOCK]]
1411 ** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might
1412 ** be advantageous to block on the next WAL lock if the lock is not immediately
1413 ** available. The WAL subsystem issues this signal during rare
1414 ** circumstances in order to fix a problem with priority inversion.
@@ -1558,10 +1575,11 @@
1558 #define SQLITE_FCNTL_RESERVE_BYTES 38
1559 #define SQLITE_FCNTL_CKPT_START 39
1560 #define SQLITE_FCNTL_EXTERNAL_READER 40
1561 #define SQLITE_FCNTL_CKSM_FILE 41
1562 #define SQLITE_FCNTL_RESET_CACHE 42
 
1563
1564 /* deprecated names */
1565 #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
1566 #define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE
1567 #define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO
@@ -2936,14 +2954,18 @@
2936 **
2937 ** ^These functions return the number of rows modified, inserted or
2938 ** deleted by the most recently completed INSERT, UPDATE or DELETE
2939 ** statement on the database connection specified by the only parameter.
2940 ** The two functions are identical except for the type of the return value
2941 ** and that if the number of rows modified by the most recent INSERT, UPDATE
2942 ** or DELETE is greater than the maximum value supported by type "int", then
2943 ** the return value of sqlite3_changes() is undefined. ^Executing any other
2944 ** type of SQL statement does not modify the value returned by these functions.
 
 
 
 
2945 **
2946 ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are
2947 ** considered - auxiliary changes caused by [CREATE TRIGGER | triggers],
2948 ** [foreign key actions] or [REPLACE] constraint resolution are not counted.
2949 **
@@ -4499,15 +4521,26 @@
4499 **
4500 ** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt>
4501 ** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler
4502 ** to return an error (error code SQLITE_ERROR) if the statement uses
4503 ** any virtual tables.
 
 
 
 
 
 
 
 
 
 
4504 ** </dl>
4505 */
4506 #define SQLITE_PREPARE_PERSISTENT 0x01
4507 #define SQLITE_PREPARE_NORMALIZE 0x02
4508 #define SQLITE_PREPARE_NO_VTAB 0x04
 
4509
4510 /*
4511 ** CAPI3REF: Compiling An SQL Statement
4512 ** KEYWORDS: {SQL statement compiler}
4513 ** METHOD: sqlite3
@@ -11194,11 +11227,11 @@
11194 #endif
11195
11196 #if 0
11197 } /* End of the 'extern "C"' block */
11198 #endif
11199 #endif /* SQLITE3_H */
11200
11201 /******** Begin file sqlite3rtree.h *********/
11202 /*
11203 ** 2010 August 30
11204 **
@@ -13445,17 +13478,32 @@
13445 ** This is used to access token iToken of phrase hit iIdx within the
13446 ** current row. If iIdx is less than zero or greater than or equal to the
13447 ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise,
13448 ** output variable (*ppToken) is set to point to a buffer containing the
13449 ** matching document token, and (*pnToken) to the size of that buffer in
13450 ** bytes. This API is not available if the specified token matches a
13451 ** prefix query term. In that case both output variables are always set
13452 ** to 0.
13453 **
13454 ** The output text is not a copy of the document text that was tokenized.
13455 ** It is the output of the tokenizer module. For tokendata=1 tables, this
13456 ** includes any embedded 0x00 and trailing data.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13457 **
13458 ** This API can be quite slow if used with an FTS5 table created with the
13459 ** "detail=none" or "detail=column" option.
13460 **
13461 ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale)
@@ -13886,10 +13934,11 @@
13886 #endif
13887
13888 #endif /* _FTS5_H */
13889
13890 /******** End of fts5.h *********/
 
13891
13892 /************** End of sqlite3.h *********************************************/
13893 /************** Continuing where we left off in sqliteInt.h ******************/
13894
13895 /*
@@ -13931,10 +13980,11 @@
13931 ** to count the size: 2^31-1 or 2147483647.
13932 */
13933 #ifndef SQLITE_MAX_LENGTH
13934 # define SQLITE_MAX_LENGTH 1000000000
13935 #endif
 
13936
13937 /*
13938 ** This is the maximum number of
13939 **
13940 ** * Columns in a table
@@ -13996,13 +14046,17 @@
13996 # define SQLITE_MAX_VDBE_OP 250000000
13997 #endif
13998
13999 /*
14000 ** The maximum number of arguments to an SQL function.
 
 
 
 
14001 */
14002 #ifndef SQLITE_MAX_FUNCTION_ARG
14003 # define SQLITE_MAX_FUNCTION_ARG 127
14004 #endif
14005
14006 /*
14007 ** The suggested maximum number of in-memory pages to use for
14008 ** the main database table and for temporary tables.
@@ -16000,10 +16054,26 @@
16000 #define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */
16001 #define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */
16002 #define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */
16003 #define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */
16004
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16005 /*
16006 ** Flags that make up the mask passed to sqlite3PagerGet().
16007 */
16008 #define PAGER_GET_NOCONTENT 0x01 /* Do not load data from disk */
16009 #define PAGER_GET_READONLY 0x02 /* Read-only page is acceptable */
@@ -17025,11 +17095,11 @@
17025
17026 /*
17027 ** Additional non-public SQLITE_PREPARE_* flags
17028 */
17029 #define SQLITE_PREPARE_SAVESQL 0x80 /* Preserve SQL text */
17030 #define SQLITE_PREPARE_MASK 0x0f /* Mask of public flags */
17031
17032 /*
17033 ** Prototypes for the VDBE interface. See comments on the implementation
17034 ** for a description of what each of these routines does.
17035 */
@@ -17740,51 +17810,15 @@
17740 struct FuncDefHash {
17741 FuncDef *a[SQLITE_FUNC_HASH_SZ]; /* Hash table for functions */
17742 };
17743 #define SQLITE_FUNC_HASH(C,L) (((C)+(L))%SQLITE_FUNC_HASH_SZ)
17744
17745 #if defined(SQLITE_USER_AUTHENTICATION)
17746 # warning "The SQLITE_USER_AUTHENTICATION extension is deprecated. \
17747 See ext/userauth/user-auth.txt for details."
17748 #endif
17749 #ifdef SQLITE_USER_AUTHENTICATION
17750 /*
17751 ** Information held in the "sqlite3" database connection object and used
17752 ** to manage user authentication.
17753 */
17754 typedef struct sqlite3_userauth sqlite3_userauth;
17755 struct sqlite3_userauth {
17756 u8 authLevel; /* Current authentication level */
17757 int nAuthPW; /* Size of the zAuthPW in bytes */
17758 char *zAuthPW; /* Password used to authenticate */
17759 char *zAuthUser; /* User name used to authenticate */
17760 };
17761
17762 /* Allowed values for sqlite3_userauth.authLevel */
17763 #define UAUTH_Unknown 0 /* Authentication not yet checked */
17764 #define UAUTH_Fail 1 /* User authentication failed */
17765 #define UAUTH_User 2 /* Authenticated as a normal user */
17766 #define UAUTH_Admin 3 /* Authenticated as an administrator */
17767
17768 /* Functions used only by user authorization logic */
17769 SQLITE_PRIVATE int sqlite3UserAuthTable(const char*);
17770 SQLITE_PRIVATE int sqlite3UserAuthCheckLogin(sqlite3*,const char*,u8*);
17771 SQLITE_PRIVATE void sqlite3UserAuthInit(sqlite3*);
17772 SQLITE_PRIVATE void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**);
17773
17774 #endif /* SQLITE_USER_AUTHENTICATION */
17775
17776 /*
17777 ** typedef for the authorization callback function.
17778 */
17779 #ifdef SQLITE_USER_AUTHENTICATION
17780 typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*,
17781 const char*, const char*);
17782 #else
17783 typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*,
17784 const char*);
17785 #endif
17786
17787 #ifndef SQLITE_OMIT_DEPRECATED
17788 /* This is an extra SQLITE_TRACE macro that indicates "legacy" tracing
17789 ** in the style of sqlite3_trace()
17790 */
@@ -17941,13 +17975,10 @@
17941 sqlite3 *pUnlockConnection; /* Connection to watch for unlock */
17942 void *pUnlockArg; /* Argument to xUnlockNotify */
17943 void (*xUnlockNotify)(void **, int); /* Unlock notify callback */
17944 sqlite3 *pNextBlocked; /* Next in list of all blocked connections */
17945 #endif
17946 #ifdef SQLITE_USER_AUTHENTICATION
17947 sqlite3_userauth auth; /* User authentication information */
17948 #endif
17949 };
17950
17951 /*
17952 ** A macro to discover the encoding of a database.
17953 */
@@ -18102,11 +18133,11 @@
18102 **
18103 ** The u.pHash field is used by the global built-ins. The u.pDestructor
18104 ** field is used by per-connection app-def functions.
18105 */
18106 struct FuncDef {
18107 i8 nArg; /* Number of arguments. -1 means unlimited */
18108 u32 funcFlags; /* Some combination of SQLITE_FUNC_* */
18109 void *pUserData; /* User data parameter */
18110 FuncDef *pNext; /* Next function with same name */
18111 void (*xSFunc)(sqlite3_context*,int,sqlite3_value**); /* func or agg-step */
18112 void (*xFinalize)(sqlite3_context*); /* Agg finalizer */
@@ -22850,13 +22881,10 @@
22850 "UNLINK_AFTER_CLOSE",
22851 #endif
22852 #ifdef SQLITE_UNTESTABLE
22853 "UNTESTABLE",
22854 #endif
22855 #ifdef SQLITE_USER_AUTHENTICATION
22856 "USER_AUTHENTICATION",
22857 #endif
22858 #ifdef SQLITE_USE_ALLOCA
22859 "USE_ALLOCA",
22860 #endif
22861 #ifdef SQLITE_USE_FCNTL_TRACE
22862 "USE_FCNTL_TRACE",
@@ -23700,11 +23728,11 @@
23700 Vdbe *pVdbe; /* The VM that owns this context */
23701 int iOp; /* Instruction number of OP_Function */
23702 int isError; /* Error code returned by the function. */
23703 u8 enc; /* Encoding to use for results */
23704 u8 skipFlag; /* Skip accumulator loading if true */
23705 u8 argc; /* Number of arguments */
23706 sqlite3_value *argv[1]; /* Argument set */
23707 };
23708
23709 /* A bitfield type for use inside of structures. Always follow with :N where
23710 ** N is the number of bits.
@@ -23847,10 +23875,11 @@
23847 UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */
23848 int iNewReg; /* Register for new.* values */
23849 int iBlobWrite; /* Value returned by preupdate_blobwrite() */
23850 i64 iKey1; /* First key value passed to hook */
23851 i64 iKey2; /* Second key value passed to hook */
 
23852 Mem *aNew; /* Array of new.* values */
23853 Table *pTab; /* Schema object being updated */
23854 Index *pPk; /* PK index if pTab is WITHOUT ROWID */
23855 sqlite3_value **apDflt; /* Array of default values, if required */
23856 };
@@ -32296,10 +32325,11 @@
32296 && (ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0)
32297 ){
32298 pExpr = pExpr->pLeft;
32299 }
32300 if( pExpr==0 ) return;
 
32301 db->errByteOffset = pExpr->w.iOfst;
32302 }
32303
32304 /*
32305 ** Enlarge the memory allocation on a StrAccum object so that it is
@@ -33025,11 +33055,11 @@
33025 }
33026 if( pItem->fg.isCte ){
33027 sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse);
33028 }
33029 if( pItem->fg.isOn || (pItem->fg.isUsing==0 && pItem->u3.pOn!=0) ){
33030 sqlite3_str_appendf(&x, " ON");
33031 }
33032 if( pItem->fg.isTabFunc ) sqlite3_str_appendf(&x, " isTabFunc");
33033 if( pItem->fg.isCorrelated ) sqlite3_str_appendf(&x, " isCorrelated");
33034 if( pItem->fg.isMaterialized ) sqlite3_str_appendf(&x, " isMaterialized");
33035 if( pItem->fg.viaCoroutine ) sqlite3_str_appendf(&x, " viaCoroutine");
@@ -34109,10 +34139,14 @@
34109 **
34110 ** This routines are given external linkage so that they will always be
34111 ** accessible to the debugging, and to avoid warnings about unused
34112 ** functions. But these routines only exist in debugging builds, so they
34113 ** do not contaminate the interface.
 
 
 
 
34114 */
34115 SQLITE_PRIVATE void sqlite3ShowExpr(const Expr *p){ sqlite3TreeViewExpr(0,p,0); }
34116 SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList *p){ sqlite3TreeViewExprList(0,p,0,0);}
34117 SQLITE_PRIVATE void sqlite3ShowIdList(const IdList *p){ sqlite3TreeViewIdList(0,p,0,0); }
34118 SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList *p){ sqlite3TreeViewSrcList(0,p); }
@@ -35685,12 +35719,12 @@
35685 int esign = 1; /* sign of exponent */
35686 int e = 0; /* exponent */
35687 int eValid = 1; /* True exponent is either not used or is well-formed */
35688 int nDigit = 0; /* Number of digits processed */
35689 int eType = 1; /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */
 
35690 double rr[2];
35691 u64 s2;
35692
35693 assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE );
35694 *pResult = 0.0; /* Default return value, in case of an error */
35695 if( length==0 ) return 0;
35696
@@ -35789,25 +35823,36 @@
35789
35790 /* adjust exponent by d, and update sign */
35791 e = (e*esign) + d;
35792
35793 /* Try to adjust the exponent to make it smaller */
35794 while( e>0 && s<(LARGEST_UINT64/10) ){
35795 s *= 10;
35796 e--;
35797 }
35798 while( e<0 && (s%10)==0 ){
35799 s /= 10;
35800 e++;
35801 }
35802
35803 rr[0] = (double)s;
35804 s2 = (u64)rr[0];
35805 #if defined(_MSC_VER) && _MSC_VER<1700
35806 if( s2==0x8000000000000000LL ){ s2 = 2*(u64)(0.5*rr[0]); }
 
 
35807 #endif
35808 rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s);
 
 
 
 
 
 
 
 
 
35809 if( e>0 ){
35810 while( e>=100 ){
35811 e -= 100;
35812 dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83);
35813 }
@@ -38674,11 +38719,11 @@
38674 # define F_SETLKW 7
38675 # endif
38676 # endif
38677 #else /* !SQLITE_WASI */
38678 # ifndef HAVE_FCHMOD
38679 # define HAVE_FCHMOD
38680 # endif
38681 #endif /* SQLITE_WASI */
38682
38683 #ifdef SQLITE_WASI
38684 # define osGetpid(X) (pid_t)1
@@ -42448,10 +42493,15 @@
42448 int rc = osIoctl(pFile->h, F2FS_IOC_ABORT_VOLATILE_WRITE);
42449 return rc ? SQLITE_IOERR_ROLLBACK_ATOMIC : SQLITE_OK;
42450 }
42451 #endif /* __linux__ && SQLITE_ENABLE_BATCH_ATOMIC_WRITE */
42452
 
 
 
 
 
42453 case SQLITE_FCNTL_LOCKSTATE: {
42454 *(int*)pArg = pFile->eFileLock;
42455 return SQLITE_OK;
42456 }
42457 case SQLITE_FCNTL_LAST_ERRNO: {
@@ -42589,10 +42639,11 @@
42589
42590 /* Set the POWERSAFE_OVERWRITE flag if requested. */
42591 if( pFd->ctrlFlags & UNIXFILE_PSOW ){
42592 pFd->deviceCharacteristics |= SQLITE_IOCAP_POWERSAFE_OVERWRITE;
42593 }
 
42594
42595 pFd->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE;
42596 }
42597 }
42598 #else
@@ -50328,10 +50379,15 @@
50328 OSTRACE(("FCNTL oldFile=%p, newFile=%p, rc=SQLITE_OK\n",
50329 hOldFile, pFile->h));
50330 return SQLITE_OK;
50331 }
50332 #endif
 
 
 
 
 
50333 case SQLITE_FCNTL_TEMPFILENAME: {
50334 char *zTFile = 0;
50335 int rc = winGetTempname(pFile->pVfs, &zTFile);
50336 if( rc==SQLITE_OK ){
50337 *(char**)pArg = zTFile;
@@ -50389,11 +50445,11 @@
50389 /*
50390 ** Return a vector of device characteristics.
50391 */
50392 static int winDeviceCharacteristics(sqlite3_file *id){
50393 winFile *p = (winFile*)id;
50394 return SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN |
50395 ((p->ctrlFlags & WINFILE_PSOW)?SQLITE_IOCAP_POWERSAFE_OVERWRITE:0);
50396 }
50397
50398 /*
50399 ** Windows will only let you create file view mappings
@@ -51777,11 +51833,11 @@
51777 */
51778 char *zTmpname = 0; /* For temporary filename, if necessary. */
51779
51780 int rc = SQLITE_OK; /* Function Return Code */
51781 #if !defined(NDEBUG) || SQLITE_OS_WINCE
51782 int eType = flags&0xFFFFFF00; /* Type of file to open */
51783 #endif
51784
51785 int isExclusive = (flags & SQLITE_OPEN_EXCLUSIVE);
51786 int isDelete = (flags & SQLITE_OPEN_DELETEONCLOSE);
51787 int isCreate = (flags & SQLITE_OPEN_CREATE);
@@ -57978,43 +58034,37 @@
57978 # define USEFETCH(x) ((x)->bUseFetch)
57979 #else
57980 # define USEFETCH(x) 0
57981 #endif
57982
57983 /*
57984 ** The argument to this macro is a file descriptor (type sqlite3_file*).
57985 ** Return 0 if it is not open, or non-zero (but not 1) if it is.
57986 **
57987 ** This is so that expressions can be written as:
57988 **
57989 ** if( isOpen(pPager->jfd) ){ ...
57990 **
57991 ** instead of
57992 **
57993 ** if( pPager->jfd->pMethods ){ ...
57994 */
57995 #define isOpen(pFd) ((pFd)->pMethods!=0)
57996
57997 #ifdef SQLITE_DIRECT_OVERFLOW_READ
57998 /*
57999 ** Return true if page pgno can be read directly from the database file
58000 ** by the b-tree layer. This is the case if:
58001 **
58002 ** * the database file is open,
58003 ** * there are no dirty pages in the cache, and
58004 ** * the desired page is not currently in the wal file.
 
58005 */
58006 SQLITE_PRIVATE int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){
58007 if( pPager->fd->pMethods==0 ) return 0;
58008 if( sqlite3PCacheIsDirty(pPager->pPCache) ) return 0;
 
 
58009 #ifndef SQLITE_OMIT_WAL
58010 if( pPager->pWal ){
58011 u32 iRead = 0;
58012 (void)sqlite3WalFindFrame(pPager->pWal, pgno, &iRead);
58013 return iRead==0;
58014 }
58015 #endif
 
 
 
 
 
58016 return 1;
58017 }
58018 #endif
58019
58020 #ifndef SQLITE_OMIT_WAL
@@ -59269,11 +59319,11 @@
59269 rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags);
59270 }
59271 }
59272 pPager->journalOff = 0;
59273 }else if( pPager->journalMode==PAGER_JOURNALMODE_PERSIST
59274 || (pPager->exclusiveMode && pPager->journalMode!=PAGER_JOURNALMODE_WAL)
59275 ){
59276 rc = zeroJournalHdr(pPager, hasSuper||pPager->tempFile);
59277 pPager->journalOff = 0;
59278 }else{
59279 /* This branch may be executed with Pager.journalMode==MEMORY if
@@ -67979,15 +68029,11 @@
67979 ** so it takes care to hold an exclusive lock on the corresponding
67980 ** WAL_READ_LOCK() while changing values.
67981 */
67982 static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){
67983 volatile WalCkptInfo *pInfo; /* Checkpoint information in wal-index */
67984 u32 mxReadMark; /* Largest aReadMark[] value */
67985 int mxI; /* Index of largest aReadMark[] value */
67986 int i; /* Loop counter */
67987 int rc = SQLITE_OK; /* Return code */
67988 u32 mxFrame; /* Wal frame to lock to */
67989 #ifdef SQLITE_ENABLE_SETLK_TIMEOUT
67990 int nBlockTmout = 0;
67991 #endif
67992
67993 assert( pWal->readLock<0 ); /* Not currently locked */
@@ -68089,145 +68135,151 @@
68089
68090 assert( pWal->nWiData>0 );
68091 assert( pWal->apWiData[0]!=0 );
68092 pInfo = walCkptInfo(pWal);
68093 SEH_INJECT_FAULT;
68094 if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame
68095 #ifdef SQLITE_ENABLE_SNAPSHOT
68096 && ((pWal->bGetSnapshot==0 && pWal->pSnapshot==0) || pWal->hdr.mxFrame==0)
68097 #endif
68098 ){
68099 /* The WAL has been completely backfilled (or it is empty).
68100 ** and can be safely ignored.
68101 */
68102 rc = walLockShared(pWal, WAL_READ_LOCK(0));
68103 walShmBarrier(pWal);
68104 if( rc==SQLITE_OK ){
68105 if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) ){
68106 /* It is not safe to allow the reader to continue here if frames
68107 ** may have been appended to the log before READ_LOCK(0) was obtained.
68108 ** When holding READ_LOCK(0), the reader ignores the entire log file,
68109 ** which implies that the database file contains a trustworthy
68110 ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from
68111 ** happening, this is usually correct.
68112 **
68113 ** However, if frames have been appended to the log (or if the log
68114 ** is wrapped and written for that matter) before the READ_LOCK(0)
68115 ** is obtained, that is not necessarily true. A checkpointer may
68116 ** have started to backfill the appended frames but crashed before
68117 ** it finished. Leaving a corrupt image in the database file.
68118 */
68119 walUnlockShared(pWal, WAL_READ_LOCK(0));
68120 return WAL_RETRY;
68121 }
68122 pWal->readLock = 0;
68123 return SQLITE_OK;
68124 }else if( rc!=SQLITE_BUSY ){
68125 return rc;
68126 }
68127 }
68128
68129 /* If we get this far, it means that the reader will want to use
68130 ** the WAL to get at content from recent commits. The job now is
68131 ** to select one of the aReadMark[] entries that is closest to
68132 ** but not exceeding pWal->hdr.mxFrame and lock that entry.
68133 */
68134 mxReadMark = 0;
68135 mxI = 0;
68136 mxFrame = pWal->hdr.mxFrame;
68137 #ifdef SQLITE_ENABLE_SNAPSHOT
68138 if( pWal->pSnapshot && pWal->pSnapshot->mxFrame<mxFrame ){
68139 mxFrame = pWal->pSnapshot->mxFrame;
68140 }
68141 #endif
68142 for(i=1; i<WAL_NREADER; i++){
68143 u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT;
68144 if( mxReadMark<=thisMark && thisMark<=mxFrame ){
68145 assert( thisMark!=READMARK_NOT_USED );
68146 mxReadMark = thisMark;
68147 mxI = i;
68148 }
68149 }
68150 if( (pWal->readOnly & WAL_SHM_RDONLY)==0
68151 && (mxReadMark<mxFrame || mxI==0)
68152 ){
68153 for(i=1; i<WAL_NREADER; i++){
68154 rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
68155 if( rc==SQLITE_OK ){
68156 AtomicStore(pInfo->aReadMark+i,mxFrame);
68157 mxReadMark = mxFrame;
68158 mxI = i;
68159 walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1);
68160 break;
68161 }else if( rc!=SQLITE_BUSY ){
68162 return rc;
68163 }
68164 }
68165 }
68166 if( mxI==0 ){
68167 assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 );
68168 return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT;
68169 }
68170
68171 (void)walEnableBlockingMs(pWal, nBlockTmout);
68172 rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
68173 walDisableBlocking(pWal);
68174 if( rc ){
68175 #ifdef SQLITE_ENABLE_SETLK_TIMEOUT
68176 if( rc==SQLITE_BUSY_TIMEOUT ){
68177 *pCnt |= WAL_RETRY_BLOCKED_MASK;
68178 }
68179 #else
68180 assert( rc!=SQLITE_BUSY_TIMEOUT );
68181 #endif
68182 assert( (rc&0xFF)!=SQLITE_BUSY||rc==SQLITE_BUSY||rc==SQLITE_BUSY_TIMEOUT );
68183 return (rc&0xFF)==SQLITE_BUSY ? WAL_RETRY : rc;
68184 }
68185 /* Now that the read-lock has been obtained, check that neither the
68186 ** value in the aReadMark[] array or the contents of the wal-index
68187 ** header have changed.
68188 **
68189 ** It is necessary to check that the wal-index header did not change
68190 ** between the time it was read and when the shared-lock was obtained
68191 ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility
68192 ** that the log file may have been wrapped by a writer, or that frames
68193 ** that occur later in the log than pWal->hdr.mxFrame may have been
68194 ** copied into the database by a checkpointer. If either of these things
68195 ** happened, then reading the database with the current value of
68196 ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry
68197 ** instead.
68198 **
68199 ** Before checking that the live wal-index header has not changed
68200 ** since it was read, set Wal.minFrame to the first frame in the wal
68201 ** file that has not yet been checkpointed. This client will not need
68202 ** to read any frames earlier than minFrame from the wal file - they
68203 ** can be safely read directly from the database file.
68204 **
68205 ** Because a ShmBarrier() call is made between taking the copy of
68206 ** nBackfill and checking that the wal-header in shared-memory still
68207 ** matches the one cached in pWal->hdr, it is guaranteed that the
68208 ** checkpointer that set nBackfill was not working with a wal-index
68209 ** header newer than that cached in pWal->hdr. If it were, that could
68210 ** cause a problem. The checkpointer could omit to checkpoint
68211 ** a version of page X that lies before pWal->minFrame (call that version
68212 ** A) on the basis that there is a newer version (version B) of the same
68213 ** page later in the wal file. But if version B happens to like past
68214 ** frame pWal->hdr.mxFrame - then the client would incorrectly assume
68215 ** that it can read version A from the database file. However, since
68216 ** we can guarantee that the checkpointer that set nBackfill could not
68217 ** see any pages past pWal->hdr.mxFrame, this problem does not come up.
68218 */
68219 pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT;
68220 walShmBarrier(pWal);
68221 if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark
68222 || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr))
68223 ){
68224 walUnlockShared(pWal, WAL_READ_LOCK(mxI));
68225 return WAL_RETRY;
68226 }else{
68227 assert( mxReadMark<=pWal->hdr.mxFrame );
68228 pWal->readLock = (i16)mxI;
 
 
 
 
 
 
68229 }
68230 return rc;
68231 }
68232
68233 #ifdef SQLITE_ENABLE_SNAPSHOT
@@ -87103,10 +87155,11 @@
87103 **
87104 ** All other fields of Mem can safely remain uninitialized for now. They
87105 ** will be initialized before use.
87106 */
87107 static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){
 
87108 if( N>0 ){
87109 do{
87110 p->flags = flags;
87111 p->db = db;
87112 p->szMalloc = 0;
@@ -87128,10 +87181,11 @@
87128 */
87129 static void releaseMemArray(Mem *p, int N){
87130 if( p && N ){
87131 Mem *pEnd = &p[N];
87132 sqlite3 *db = p->db;
 
87133 if( db->pnBytesFreed ){
87134 do{
87135 if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc);
87136 }while( (++p)<pEnd );
87137 return;
@@ -87608,10 +87662,11 @@
87608 assert( p!=0 );
87609 assert( p->nOp>0 );
87610 assert( pParse!=0 );
87611 assert( p->eVdbeState==VDBE_INIT_STATE );
87612 assert( pParse==p->pParse );
 
87613 p->pVList = pParse->pVList;
87614 pParse->pVList = 0;
87615 db = p->db;
87616 assert( db->mallocFailed==0 );
87617 nVar = pParse->nVar;
@@ -90488,10 +90543,11 @@
90488 db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2);
90489 db->pPreUpdate = 0;
90490 sqlite3DbFree(db, preupdate.aRecord);
90491 vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pUnpacked);
90492 vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pNewUnpacked);
 
90493 if( preupdate.aNew ){
90494 int i;
90495 for(i=0; i<pCsr->nField; i++){
90496 sqlite3VdbeMemRelease(&preupdate.aNew[i]);
90497 }
@@ -91844,11 +91900,11 @@
91844 **
91845 ** sqlite3_column_int()
91846 ** sqlite3_column_int64()
91847 ** sqlite3_column_text()
91848 ** sqlite3_column_text16()
91849 ** sqlite3_column_real()
91850 ** sqlite3_column_bytes()
91851 ** sqlite3_column_bytes16()
91852 ** sqlite3_column_blob()
91853 */
91854 static void columnMallocFailure(sqlite3_stmt *pStmt)
@@ -92706,64 +92762,68 @@
92706 if( iIdx>=p->pCsr->nField || iIdx<0 ){
92707 rc = SQLITE_RANGE;
92708 goto preupdate_old_out;
92709 }
92710
92711 /* If the old.* record has not yet been loaded into memory, do so now. */
92712 if( p->pUnpacked==0 ){
92713 u32 nRec;
92714 u8 *aRec;
92715
92716 assert( p->pCsr->eCurType==CURTYPE_BTREE );
92717 nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor);
92718 aRec = sqlite3DbMallocRaw(db, nRec);
92719 if( !aRec ) goto preupdate_old_out;
92720 rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec);
92721 if( rc==SQLITE_OK ){
92722 p->pUnpacked = vdbeUnpackRecord(&p->keyinfo, nRec, aRec);
92723 if( !p->pUnpacked ) rc = SQLITE_NOMEM;
92724 }
92725 if( rc!=SQLITE_OK ){
92726 sqlite3DbFree(db, aRec);
92727 goto preupdate_old_out;
92728 }
92729 p->aRecord = aRec;
92730 }
92731
92732 pMem = *ppValue = &p->pUnpacked->aMem[iIdx];
92733 if( iIdx==p->pTab->iPKey ){
 
92734 sqlite3VdbeMemSetInt64(pMem, p->iKey1);
92735 }else if( iIdx>=p->pUnpacked->nField ){
92736 /* This occurs when the table has been extended using ALTER TABLE
92737 ** ADD COLUMN. The value to return is the default value of the column. */
92738 Column *pCol = &p->pTab->aCol[iIdx];
92739 if( pCol->iDflt>0 ){
92740 if( p->apDflt==0 ){
92741 int nByte = sizeof(sqlite3_value*)*p->pTab->nCol;
92742 p->apDflt = (sqlite3_value**)sqlite3DbMallocZero(db, nByte);
92743 if( p->apDflt==0 ) goto preupdate_old_out;
92744 }
92745 if( p->apDflt[iIdx]==0 ){
92746 sqlite3_value *pVal = 0;
92747 Expr *pDflt;
92748 assert( p->pTab!=0 && IsOrdinaryTable(p->pTab) );
92749 pDflt = p->pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr;
92750 rc = sqlite3ValueFromExpr(db, pDflt, ENC(db), pCol->affinity, &pVal);
92751 if( rc==SQLITE_OK && pVal==0 ){
92752 rc = SQLITE_CORRUPT_BKPT;
92753 }
92754 p->apDflt[iIdx] = pVal;
92755 }
92756 *ppValue = p->apDflt[iIdx];
92757 }else{
92758 *ppValue = (sqlite3_value *)columnNullValue();
92759 }
92760 }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){
92761 if( pMem->flags & (MEM_Int|MEM_IntReal) ){
92762 testcase( pMem->flags & MEM_Int );
92763 testcase( pMem->flags & MEM_IntReal );
92764 sqlite3VdbeMemRealify(pMem);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92765 }
92766 }
92767
92768 preupdate_old_out:
92769 sqlite3Error(db, rc);
@@ -97913,13 +97973,15 @@
97913 0, pCx->uc.pCursor);
97914 pCx->isTable = 1;
97915 }
97916 }
97917 pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED);
 
97918 if( rc ){
97919 assert( !sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) );
97920 sqlite3BtreeClose(pCx->ub.pBtx);
 
97921 }else{
97922 assert( sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) );
97923 }
97924 }
97925 }
@@ -109845,11 +109907,11 @@
109845 p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight);
109846 }
109847 p5 = binaryCompareP5(pLeft, pRight, jumpIfNull);
109848 addr = sqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1,
109849 (void*)p4, P4_COLLSEQ);
109850 sqlite3VdbeChangeP5(pParse->pVdbe, (u8)p5);
109851 return addr;
109852 }
109853
109854 /*
109855 ** Return true if expression pExpr is a vector, or false otherwise.
@@ -112012,11 +112074,11 @@
112012 **
112013 ** (4) If pSrc is the right operand of a LEFT JOIN, then...
112014 ** (4a) pExpr must come from an ON clause..
112015 ** (4b) and specifically the ON clause associated with the LEFT JOIN.
112016 **
112017 ** (5) If pSrc is not the right operand of a LEFT JOIN or the left
112018 ** operand of a RIGHT JOIN, then pExpr must be from the WHERE
112019 ** clause, not an ON clause.
112020 **
112021 ** (6) Either:
112022 **
@@ -115546,35 +115608,41 @@
115546 **
115547 ** Additionally, if pExpr is a simple SQL value and the value is the
115548 ** same as that currently bound to variable pVar, non-zero is returned.
115549 ** Otherwise, if the values are not the same or if pExpr is not a simple
115550 ** SQL value, zero is returned.
 
 
 
115551 */
115552 static int exprCompareVariable(
115553 const Parse *pParse,
115554 const Expr *pVar,
115555 const Expr *pExpr
115556 ){
115557 int res = 0;
115558 int iVar;
115559 sqlite3_value *pL, *pR = 0;
115560
 
 
 
 
115561 sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR);
115562 if( pR ){
115563 iVar = pVar->iColumn;
115564 sqlite3VdbeSetVarmask(pParse->pVdbe, iVar);
115565 pL = sqlite3VdbeGetBoundValue(pParse->pReprepare, iVar, SQLITE_AFF_BLOB);
115566 if( pL ){
115567 if( sqlite3_value_type(pL)==SQLITE_TEXT ){
115568 sqlite3_value_text(pL); /* Make sure the encoding is UTF-8 */
115569 }
115570 res = 0==sqlite3MemCompare(pL, pR, 0);
115571 }
115572 sqlite3ValueFree(pR);
115573 sqlite3ValueFree(pL);
115574 }
115575
115576 return res;
115577 }
115578
115579 /*
115580 ** Do a deep comparison of two expression trees. Return 0 if the two
@@ -115596,16 +115664,14 @@
115596 ** can be sure the expressions are the same. In the places where
115597 ** this routine is used, it does not hurt to get an extra 2 - that
115598 ** just might result in some slightly slower code. But returning
115599 ** an incorrect 0 or 1 could lead to a malfunction.
115600 **
115601 ** If pParse is not NULL then TK_VARIABLE terms in pA with bindings in
115602 ** pParse->pReprepare can be matched against literals in pB. The
115603 ** pParse->pVdbe->expmask bitmask is updated for each variable referenced.
115604 ** If pParse is NULL (the normal case) then any TK_VARIABLE term in
115605 ** Argument pParse should normally be NULL. If it is not NULL and pA or
115606 ** pB causes a return value of 2.
115607 */
115608 SQLITE_PRIVATE int sqlite3ExprCompare(
115609 const Parse *pParse,
115610 const Expr *pA,
115611 const Expr *pB,
@@ -115613,12 +115679,12 @@
115613 ){
115614 u32 combinedFlags;
115615 if( pA==0 || pB==0 ){
115616 return pB==pA ? 0 : 2;
115617 }
115618 if( pParse && pA->op==TK_VARIABLE && exprCompareVariable(pParse, pA, pB) ){
115619 return 0;
115620 }
115621 combinedFlags = pA->flags | pB->flags;
115622 if( combinedFlags & EP_IntValue ){
115623 if( (pA->flags&pB->flags&EP_IntValue)!=0 && pA->u.iValue==pB->u.iValue ){
115624 return 0;
@@ -115808,23 +115874,75 @@
115808 return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1);
115809 }
115810 }
115811 return 0;
115812 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115813
115814 /*
115815 ** Return true if we can prove the pE2 will always be true if pE1 is
115816 ** true. Return false if we cannot complete the proof or if pE2 might
115817 ** be false. Examples:
115818 **
115819 ** pE1: x==5 pE2: x==5 Result: true
115820 ** pE1: x>0 pE2: x==5 Result: false
115821 ** pE1: x=21 pE2: x=21 OR y=43 Result: true
115822 ** pE1: x!=123 pE2: x IS NOT NULL Result: true
115823 ** pE1: x!=?1 pE2: x IS NOT NULL Result: true
115824 ** pE1: x IS NULL pE2: x IS NOT NULL Result: false
115825 ** pE1: x IS ?2 pE2: x IS NOT NULL Result: false
 
 
115826 **
115827 ** When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has
115828 ** Expr.iTable<0 then assume a table number given by iTab.
115829 **
115830 ** If pParse is not NULL, then the values of bound variables in pE1 are
@@ -115854,10 +115972,13 @@
115854 if( pE2->op==TK_NOTNULL
115855 && exprImpliesNotNull(pParse, pE1, pE2->pLeft, iTab, 0)
115856 ){
115857 return 1;
115858 }
 
 
 
115859 return 0;
115860 }
115861
115862 /* This is a helper function to impliesNotNullRow(). In this routine,
115863 ** set pWalker->eCode to one only if *both* of the input expressions
@@ -121265,19 +121386,10 @@
121265 rc = sqlite3Init(db, &zErrDyn);
121266 }
121267 sqlite3BtreeLeaveAll(db);
121268 assert( zErrDyn==0 || rc!=SQLITE_OK );
121269 }
121270 #ifdef SQLITE_USER_AUTHENTICATION
121271 if( rc==SQLITE_OK && !REOPEN_AS_MEMDB(db) ){
121272 u8 newAuth = 0;
121273 rc = sqlite3UserAuthCheckLogin(db, zName, &newAuth);
121274 if( newAuth<db->auth.authLevel ){
121275 rc = SQLITE_AUTH_USER;
121276 }
121277 }
121278 #endif
121279 if( rc ){
121280 if( ALWAYS(!REOPEN_AS_MEMDB(db)) ){
121281 int iDb = db->nDb - 1;
121282 assert( iDb>=2 );
121283 if( db->aDb[iDb].pBt ){
@@ -121771,15 +121883,11 @@
121771 sqlite3 *db = pParse->db; /* Database handle */
121772 char *zDb = db->aDb[iDb].zDbSName; /* Schema name of attached database */
121773 int rc; /* Auth callback return code */
121774
121775 if( db->init.busy ) return SQLITE_OK;
121776 rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext
121777 #ifdef SQLITE_USER_AUTHENTICATION
121778 ,db->auth.zAuthUser
121779 #endif
121780 );
121781 if( rc==SQLITE_DENY ){
121782 char *z = sqlite3_mprintf("%s.%s", zTab, zCol);
121783 if( db->nDb>2 || iDb!=0 ) z = sqlite3_mprintf("%s.%z", zDb, z);
121784 sqlite3ErrorMsg(pParse, "access to %z is prohibited", z);
121785 pParse->rc = SQLITE_AUTH;
@@ -121882,15 +121990,11 @@
121882 testcase( zArg1==0 );
121883 testcase( zArg2==0 );
121884 testcase( zArg3==0 );
121885 testcase( pParse->zAuthContext==0 );
121886
121887 rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext
121888 #ifdef SQLITE_USER_AUTHENTICATION
121889 ,db->auth.zAuthUser
121890 #endif
121891 );
121892 if( rc==SQLITE_DENY ){
121893 sqlite3ErrorMsg(pParse, "not authorized");
121894 pParse->rc = SQLITE_AUTH;
121895 }else if( rc!=SQLITE_OK && rc!=SQLITE_IGNORE ){
121896 rc = SQLITE_DENY;
@@ -122119,21 +122223,10 @@
122119 sqlite3VdbeJumpHere(v, addrRewind);
122120 }
122121 }
122122 sqlite3VdbeAddOp0(v, OP_Halt);
122123
122124 #if SQLITE_USER_AUTHENTICATION && !defined(SQLITE_OMIT_SHARED_CACHE)
122125 if( pParse->nTableLock>0 && db->init.busy==0 ){
122126 sqlite3UserAuthInit(db);
122127 if( db->auth.authLevel<UAUTH_User ){
122128 sqlite3ErrorMsg(pParse, "user not authenticated");
122129 pParse->rc = SQLITE_AUTH_USER;
122130 return;
122131 }
122132 }
122133 #endif
122134
122135 /* The cookie mask contains one bit for each database file open.
122136 ** (Bit 0 is for main, bit 1 is for temp, and so forth.) Bits are
122137 ** set for each database that is used. Generate code to start a
122138 ** transaction on each used database and to verify the schema cookie
122139 ** on each used database.
@@ -122258,20 +122351,10 @@
122258 sqlite3DbFree(db, zSql);
122259 memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ);
122260 pParse->nested--;
122261 }
122262
122263 #if SQLITE_USER_AUTHENTICATION
122264 /*
122265 ** Return TRUE if zTable is the name of the system table that stores the
122266 ** list of users and their access credentials.
122267 */
122268 SQLITE_PRIVATE int sqlite3UserAuthTable(const char *zTable){
122269 return sqlite3_stricmp(zTable, "sqlite_user")==0;
122270 }
122271 #endif
122272
122273 /*
122274 ** Locate the in-memory structure that describes a particular database
122275 ** table given the name of that table and (optionally) the name of the
122276 ** database containing the table. Return NULL if not found.
122277 **
@@ -122286,17 +122369,10 @@
122286 Table *p = 0;
122287 int i;
122288
122289 /* All mutexes are required for schema access. Make sure we hold them. */
122290 assert( zDatabase!=0 || sqlite3BtreeHoldsAllMutexes(db) );
122291 #if SQLITE_USER_AUTHENTICATION
122292 /* Only the admin user is allowed to know that the sqlite_user table
122293 ** exists */
122294 if( db->auth.authLevel<UAUTH_Admin && sqlite3UserAuthTable(zName)!=0 ){
122295 return 0;
122296 }
122297 #endif
122298 if( zDatabase ){
122299 for(i=0; i<db->nDb; i++){
122300 if( sqlite3StrICmp(zDatabase, db->aDb[i].zDbSName)==0 ) break;
122301 }
122302 if( i>=db->nDb ){
@@ -125951,13 +126027,10 @@
125951
125952 assert( pTab!=0 );
125953 if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0
125954 && db->init.busy==0
125955 && pTblName!=0
125956 #if SQLITE_USER_AUTHENTICATION
125957 && sqlite3UserAuthTable(pTab->zName)==0
125958 #endif
125959 ){
125960 sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName);
125961 goto exit_create_index;
125962 }
125963 #ifndef SQLITE_OMIT_VIEW
@@ -129661,20 +129734,19 @@
129661 const unsigned char *z;
129662 const unsigned char *z2;
129663 int len;
129664 int p0type;
129665 i64 p1, p2;
129666 int negP2 = 0;
129667
129668 assert( argc==3 || argc==2 );
129669 if( sqlite3_value_type(argv[1])==SQLITE_NULL
129670 || (argc==3 && sqlite3_value_type(argv[2])==SQLITE_NULL)
129671 ){
129672 return;
129673 }
129674 p0type = sqlite3_value_type(argv[0]);
129675 p1 = sqlite3_value_int(argv[1]);
129676 if( p0type==SQLITE_BLOB ){
129677 len = sqlite3_value_bytes(argv[0]);
129678 z = sqlite3_value_blob(argv[0]);
129679 if( z==0 ) return;
129680 assert( len==sqlite3_value_bytes(argv[0]) );
@@ -129695,36 +129767,36 @@
129695 ** from 2009-02-02 for compatibility of applications that exploited the
129696 ** old buggy behavior. */
129697 if( p1==0 ) p1 = 1; /* <rdar://problem/6778339> */
129698 #endif
129699 if( argc==3 ){
129700 p2 = sqlite3_value_int(argv[2]);
129701 if( p2<0 ){
129702 p2 = -p2;
129703 negP2 = 1;
129704 }
129705 }else{
129706 p2 = sqlite3_context_db_handle(context)->aLimit[SQLITE_LIMIT_LENGTH];
129707 }
129708 if( p1<0 ){
129709 p1 += len;
129710 if( p1<0 ){
129711 p2 += p1;
129712 if( p2<0 ) p2 = 0;
 
 
 
129713 p1 = 0;
129714 }
129715 }else if( p1>0 ){
129716 p1--;
129717 }else if( p2>0 ){
129718 p2--;
129719 }
129720 if( negP2 ){
 
 
 
 
 
129721 p1 -= p2;
129722 if( p1<0 ){
129723 p2 += p1;
129724 p1 = 0;
129725 }
129726 }
129727 assert( p1>=0 && p2>=0 );
129728 if( p0type!=SQLITE_BLOB ){
129729 while( *z && p1 ){
129730 SQLITE_SKIP_UTF8(z);
@@ -129734,13 +129806,15 @@
129734 SQLITE_SKIP_UTF8(z2);
129735 }
129736 sqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT,
129737 SQLITE_UTF8);
129738 }else{
129739 if( p1+p2>len ){
 
 
129740 p2 = len-p1;
129741 if( p2<0 ) p2 = 0;
129742 }
129743 sqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT);
129744 }
129745 }
129746
@@ -129747,17 +129821,17 @@
129747 /*
129748 ** Implementation of the round() function
129749 */
129750 #ifndef SQLITE_OMIT_FLOATING_POINT
129751 static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
129752 int n = 0;
129753 double r;
129754 char *zBuf;
129755 assert( argc==1 || argc==2 );
129756 if( argc==2 ){
129757 if( SQLITE_NULL==sqlite3_value_type(argv[1]) ) return;
129758 n = sqlite3_value_int(argv[1]);
129759 if( n>30 ) n = 30;
129760 if( n<0 ) n = 0;
129761 }
129762 if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
129763 r = sqlite3_value_double(argv[0]);
@@ -129768,11 +129842,11 @@
129768 if( r<-4503599627370496.0 || r>+4503599627370496.0 ){
129769 /* The value has no fractional part so there is nothing to round */
129770 }else if( n==0 ){
129771 r = (double)((sqlite_int64)(r+(r<0?-0.5:+0.5)));
129772 }else{
129773 zBuf = sqlite3_mprintf("%!.*f",n,r);
129774 if( zBuf==0 ){
129775 sqlite3_result_error_nomem(context);
129776 return;
129777 }
129778 sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8);
@@ -131985,13 +132059,10 @@
131985 #endif
131986 #ifndef SQLITE_OMIT_LOAD_EXTENSION
131987 SFUNCTION(load_extension, 1, 0, 0, loadExt ),
131988 SFUNCTION(load_extension, 2, 0, 0, loadExt ),
131989 #endif
131990 #if SQLITE_USER_AUTHENTICATION
131991 FUNCTION(sqlite_crypt, 2, 0, 0, sqlite3CryptFunc ),
131992 #endif
131993 #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
131994 DFUNCTION(sqlite_compileoption_used,1, 0, 0, compileoptionusedFunc ),
131995 DFUNCTION(sqlite_compileoption_get, 1, 0, 0, compileoptiongetFunc ),
131996 #endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */
131997 INLINE_FUNC(unlikely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY),
@@ -132124,11 +132195,14 @@
132124 MFUNCTION(degrees, 1, radToDeg, math1Func ),
132125 MFUNCTION(pi, 0, 0, piFunc ),
132126 #endif /* SQLITE_ENABLE_MATH_FUNCTIONS */
132127 FUNCTION(sign, 1, 0, 0, signFunc ),
132128 INLINE_FUNC(coalesce, -1, INLINEFUNC_coalesce, 0 ),
 
132129 INLINE_FUNC(iif, 3, INLINEFUNC_iif, 0 ),
 
 
132130 };
132131 #ifndef SQLITE_OMIT_ALTERTABLE
132132 sqlite3AlterFunctions();
132133 #endif
132134 sqlite3WindowFunctions();
@@ -140637,16 +140711,10 @@
140637 if( db->autoCommit==0 ){
140638 /* Foreign key support may not be enabled or disabled while not
140639 ** in auto-commit mode. */
140640 mask &= ~(SQLITE_ForeignKeys);
140641 }
140642 #if SQLITE_USER_AUTHENTICATION
140643 if( db->auth.authLevel==UAUTH_User ){
140644 /* Do not allow non-admin users to modify the schema arbitrarily */
140645 mask &= ~(SQLITE_WriteSchema);
140646 }
140647 #endif
140648
140649 if( sqlite3GetBoolean(zRight, 0) ){
140650 if( (mask & SQLITE_WriteSchema)==0
140651 || (db->flags & SQLITE_Defensive)==0
140652 ){
@@ -140778,11 +140846,12 @@
140778 pTab = sqliteHashData(k);
140779 if( pTab->nCol==0 ){
140780 char *zSql = sqlite3MPrintf(db, "SELECT*FROM\"%w\"", pTab->zName);
140781 if( zSql ){
140782 sqlite3_stmt *pDummy = 0;
140783 (void)sqlite3_prepare(db, zSql, -1, &pDummy, 0);
 
140784 (void)sqlite3_finalize(pDummy);
140785 sqlite3DbFree(db, zSql);
140786 }
140787 if( db->mallocFailed ){
140788 sqlite3ErrorMsg(db->pParse, "out of memory");
@@ -141259,11 +141328,11 @@
141259 sqlite3VdbeAddOp3(v, OP_Null, 0, 8, 8+cnt);
141260 sqlite3ClearTempRegCache(pParse);
141261
141262 /* Do the b-tree integrity checks */
141263 sqlite3VdbeAddOp4(v, OP_IntegrityCk, 1, cnt, 8, (char*)aRoot,P4_INTARRAY);
141264 sqlite3VdbeChangeP5(v, (u8)i);
141265 addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v);
141266 sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0,
141267 sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName),
141268 P4_DYNAMIC);
141269 sqlite3VdbeAddOp3(v, OP_Concat, 2, 3, 3);
@@ -142879,18 +142948,11 @@
142879 encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3;
142880 if( encoding==0 ) encoding = SQLITE_UTF8;
142881 #else
142882 encoding = SQLITE_UTF8;
142883 #endif
142884 if( db->nVdbeActive>0 && encoding!=ENC(db)
142885 && (db->mDbFlags & DBFLAG_Vacuum)==0
142886 ){
142887 rc = SQLITE_LOCKED;
142888 goto initone_error_out;
142889 }else{
142890 sqlite3SetTextEncoding(db, encoding);
142891 }
142892 }else{
142893 /* If opening an attached database, the encoding much match ENC(db) */
142894 if( (meta[BTREE_TEXT_ENCODING-1] & 3)!=ENC(db) ){
142895 sqlite3SetString(pzErrMsg, db, "attached databases must use the same"
142896 " text encoding as main database");
@@ -147584,36 +147646,36 @@
147584 return pExpr;
147585 }
147586 if( pSubst->isOuterJoin ){
147587 ExprSetProperty(pNew, EP_CanBeNull);
147588 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147589 if( ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) ){
147590 sqlite3SetJoinExpr(pNew, pExpr->w.iJoin,
147591 pExpr->flags & (EP_OuterON|EP_InnerON));
147592 }
147593 sqlite3ExprDelete(db, pExpr);
147594 pExpr = pNew;
147595 if( pExpr->op==TK_TRUEFALSE ){
147596 pExpr->u.iValue = sqlite3ExprTruthValue(pExpr);
147597 pExpr->op = TK_INTEGER;
147598 ExprSetProperty(pExpr, EP_IntValue);
147599 }
147600
147601 /* Ensure that the expression now has an implicit collation sequence,
147602 ** just as it did when it was a column of a view or sub-query. */
147603 {
147604 CollSeq *pNat = sqlite3ExprCollSeq(pSubst->pParse, pExpr);
147605 CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse,
147606 pSubst->pCList->a[iColumn].pExpr
147607 );
147608 if( pNat!=pColl || (pExpr->op!=TK_COLUMN && pExpr->op!=TK_COLLATE) ){
147609 pExpr = sqlite3ExprAddCollateString(pSubst->pParse, pExpr,
147610 (pColl ? pColl->zName : "BINARY")
147611 );
147612 }
147613 }
147614 ExprClearProperty(pExpr, EP_Collate);
147615 }
147616 }
147617 }else{
147618 if( pExpr->op==TK_IF_NULL_ROW && pExpr->iTable==pSubst->iTable ){
147619 pExpr->iTable = pSubst->iNewTable;
@@ -148346,20 +148408,20 @@
148346 }
148347
148348 /* Transfer the FROM clause terms from the subquery into the
148349 ** outer query.
148350 */
 
148351 for(i=0; i<nSubSrc; i++){
148352 SrcItem *pItem = &pSrc->a[i+iFrom];
148353 assert( pItem->fg.isTabFunc==0 );
148354 assert( pItem->fg.isSubquery
148355 || pItem->fg.fixedSchema
148356 || pItem->u4.zDatabase==0 );
148357 if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing);
148358 *pItem = pSubSrc->a[i];
148359 pItem->fg.jointype |= ltorj;
148360 iNewParent = pSubSrc->a[i].iCursor;
148361 memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i]));
148362 }
148363 pSrc->a[iFrom].fg.jointype &= JT_LTORJ;
148364 pSrc->a[iFrom].fg.jointype |= jointype | ltorj;
148365
@@ -148395,10 +148457,11 @@
148395 pSub->pOrderBy = 0;
148396 }
148397 pWhere = pSub->pWhere;
148398 pSub->pWhere = 0;
148399 if( isOuterJoin>0 ){
 
148400 sqlite3SetJoinExpr(pWhere, iNewParent, EP_OuterON);
148401 }
148402 if( pWhere ){
148403 if( pParent->pWhere ){
148404 pParent->pWhere = sqlite3PExpr(pParse, TK_AND, pWhere, pParent->pWhere);
@@ -150498,11 +150561,11 @@
150498 }
150499 sqlite3ReleaseTempReg(pParse, regSubtype);
150500 }
150501 sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
150502 sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
150503 sqlite3VdbeChangeP5(v, (u8)nArg);
150504 sqlite3VdbeAddOp2(v, OP_Next, pF->iOBTab, iTop+1); VdbeCoverage(v);
150505 sqlite3VdbeJumpHere(v, iTop);
150506 sqlite3ReleaseTempRange(pParse, regAgg, nArg);
150507 }
150508 sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i),
@@ -150661,11 +150724,11 @@
150661 sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0,
150662 (char *)pColl, P4_COLLSEQ);
150663 }
150664 sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
150665 sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
150666 sqlite3VdbeChangeP5(v, (u8)nArg);
150667 sqlite3ReleaseTempRange(pParse, regAgg, nArg);
150668 }
150669 if( addrNext ){
150670 sqlite3VdbeResolveLabel(v, addrNext);
150671 }
@@ -151494,11 +151557,11 @@
151494 sqlite3TreeViewSelect(0, p, 0);
151495 }
151496 #endif
151497 assert( pSubq->pSelect && (pSub->selFlags & SF_PushDown)!=0 );
151498 }else{
151499 TREETRACE(0x4000,pParse,p,("WHERE-lcause push-down not possible\n"));
151500 }
151501
151502 /* Convert unused result columns of the subquery into simple NULL
151503 ** expressions, to avoid unneeded searching and computation.
151504 ** tag-select-0440
@@ -154055,11 +154118,11 @@
154055 /* Set the P5 operand of the OP_Program instruction to non-zero if
154056 ** recursive invocation of this trigger program is disallowed. Recursive
154057 ** invocation is disallowed if (a) the sub-program is really a trigger,
154058 ** not a foreign key action, and (b) the flag to enable recursive triggers
154059 ** is clear. */
154060 sqlite3VdbeChangeP5(v, (u8)bRecursive);
154061 }
154062 }
154063
154064 /*
154065 ** This is called to code the required FOR EACH ROW triggers for an operation
@@ -158268,13 +158331,21 @@
158268 SQLITE_PRIVATE int sqlite3WhereExplainBloomFilter(
158269 const Parse *pParse, /* Parse context */
158270 const WhereInfo *pWInfo, /* WHERE clause */
158271 const WhereLevel *pLevel /* Bloom filter on this level */
158272 );
 
 
 
 
 
 
 
158273 #else
158274 # define sqlite3WhereExplainOneScan(u,v,w,x) 0
158275 # define sqlite3WhereExplainBloomFilter(u,v,w) 0
 
158276 #endif /* SQLITE_OMIT_EXPLAIN */
158277 #ifdef SQLITE_ENABLE_STMT_SCANSTATUS
158278 SQLITE_PRIVATE void sqlite3WhereAddScanStatus(
158279 Vdbe *v, /* Vdbe to add scanstatus entry to */
158280 SrcList *pSrclist, /* FROM clause pLvl reads data from */
@@ -158472,42 +158543,42 @@
158472 }
158473 sqlite3_str_append(pStr, ")", 1);
158474 }
158475
158476 /*
158477 ** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN
158478 ** command, or if stmt_scanstatus_v2() stats are enabled, or if SQLITE_DEBUG
158479 ** was defined at compile-time. If it is not a no-op, a single OP_Explain
158480 ** opcode is added to the output to describe the table scan strategy in pLevel.
158481 **
158482 ** If an OP_Explain opcode is added to the VM, its address is returned.
158483 ** Otherwise, if no OP_Explain is coded, zero is returned.
158484 */
158485 SQLITE_PRIVATE int sqlite3WhereExplainOneScan(
158486 Parse *pParse, /* Parse context */
 
158487 SrcList *pTabList, /* Table list this loop refers to */
158488 WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */
158489 u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */
158490 ){
158491 int ret = 0;
158492 #if !defined(SQLITE_DEBUG)
158493 if( sqlite3ParseToplevel(pParse)->explain==2 || IS_STMT_SCANSTATUS(pParse->db) )
158494 #endif
158495 {
 
 
158496 SrcItem *pItem = &pTabList->a[pLevel->iFrom];
158497 Vdbe *v = pParse->pVdbe; /* VM being constructed */
158498 sqlite3 *db = pParse->db; /* Database handle */
158499 int isSearch; /* True for a SEARCH. False for SCAN. */
158500 WhereLoop *pLoop; /* The controlling WhereLoop object */
158501 u32 flags; /* Flags that describe this loop */
 
158502 char *zMsg; /* Text to add to EQP output */
 
158503 StrAccum str; /* EQP output string */
158504 char zBuf[100]; /* Initial space for EQP output string */
158505
 
 
158506 pLoop = pLevel->pWLoop;
158507 flags = pLoop->wsFlags;
158508 if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_OR_SUBCLAUSE) ) return 0;
158509
158510 isSearch = (flags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0
158511 || ((flags&WHERE_VIRTUALTABLE)==0 && (pLoop->u.btree.nEq>0))
158512 || (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX));
158513
@@ -158527,11 +158598,11 @@
158527 }
158528 }else if( flags & WHERE_PARTIALIDX ){
158529 zFmt = "AUTOMATIC PARTIAL COVERING INDEX";
158530 }else if( flags & WHERE_AUTO_INDEX ){
158531 zFmt = "AUTOMATIC COVERING INDEX";
158532 }else if( flags & WHERE_IDX_ONLY ){
158533 zFmt = "COVERING INDEX %s";
158534 }else{
158535 zFmt = "INDEX %s";
158536 }
158537 if( zFmt ){
@@ -158579,15 +158650,54 @@
158579 sqlite3LogEstToInt(pLoop->nOut));
158580 }else{
158581 sqlite3_str_append(&str, " (~1 row)", 9);
158582 }
158583 #endif
 
158584 zMsg = sqlite3StrAccumFinish(&str);
158585 sqlite3ExplainBreakpoint("",zMsg);
158586 ret = sqlite3VdbeAddOp4(v, OP_Explain, sqlite3VdbeCurrentAddr(v),
158587 pParse->addrExplain, pLoop->rRun,
158588 zMsg, P4_DYNAMIC);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158589 }
158590 return ret;
158591 }
158592
158593 /*
@@ -158682,13 +158792,14 @@
158682 if( wsFlags & WHERE_INDEXED ){
158683 sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur);
158684 }
158685 }else{
158686 int addr;
 
158687 assert( pSrclist->a[pLvl->iFrom].fg.isSubquery );
158688 addr = pSrclist->a[pLvl->iFrom].u4.pSubq->addrFillSub;
158689 VdbeOp *pOp = sqlite3VdbeGetOp(v, addr-1);
158690 assert( sqlite3VdbeDb(v)->mallocFailed || pOp->opcode==OP_InitCoroutine );
158691 assert( sqlite3VdbeDb(v)->mallocFailed || pOp->p2>addr );
158692 sqlite3VdbeScanStatusRange(v, addrExplain, addr, pOp->p2-1);
158693 }
158694 }
@@ -158937,10 +159048,11 @@
158937 if( pOrigLhs ){
158938 sqlite3ExprListDelete(db, pOrigLhs);
158939 pNew->pLeft->x.pList = pLhs;
158940 }
158941 pSelect->pEList = pRhs;
 
158942 if( pLhs && pLhs->nExpr==1 ){
158943 /* Take care here not to generate a TK_VECTOR containing only a
158944 ** single value. Since the parser never creates such a vector, some
158945 ** of the subroutines do not handle this case. */
158946 Expr *p = pLhs->a[0].pExpr;
@@ -164009,11 +164121,11 @@
164009 || pTerm->pExpr->w.iJoin != pSrc->iCursor
164010 ){
164011 return 0;
164012 }
164013 if( (pSrc->fg.jointype & (JT_LEFT|JT_RIGHT))!=0
164014 && ExprHasProperty(pTerm->pExpr, EP_InnerON)
164015 ){
164016 return 0;
164017 }
164018 return 1;
164019 }
@@ -165502,11 +165614,11 @@
165502 return rc;
165503 }
165504 #endif /* SQLITE_ENABLE_STAT4 */
165505
165506
165507 #ifdef WHERETRACE_ENABLED
165508 /*
165509 ** Print the content of a WhereTerm object
165510 */
165511 SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){
165512 if( pTerm==0 ){
@@ -165545,10 +165657,13 @@
165545 sqlite3DebugPrintf(" iParent=%d", pTerm->iParent);
165546 }
165547 sqlite3DebugPrintf("\n");
165548 sqlite3TreeViewExpr(0, pTerm->pExpr, 0);
165549 }
 
 
 
165550 }
165551 #endif
165552
165553 #ifdef WHERETRACE_ENABLED
165554 /*
@@ -166731,11 +166846,10 @@
166731 pParse = pWC->pWInfo->pParse;
166732 while( pWhere->op==TK_AND ){
166733 if( !whereUsablePartialIndex(iTab,jointype,pWC,pWhere->pLeft) ) return 0;
166734 pWhere = pWhere->pRight;
166735 }
166736 if( pParse->db->flags & SQLITE_EnableQPSG ) pParse = 0;
166737 for(i=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){
166738 Expr *pExpr;
166739 pExpr = pTerm->pExpr;
166740 if( (!ExprHasProperty(pExpr, EP_OuterON) || pExpr->w.iJoin==iTab)
166741 && ((jointype & JT_OUTER)==0 || ExprHasProperty(pExpr, EP_OuterON))
@@ -169392,11 +169506,11 @@
169392 break;
169393 }
169394 }
169395 if( hasRightJoin
169396 && ExprHasProperty(pTerm->pExpr, EP_InnerON)
169397 && pTerm->pExpr->w.iJoin==pItem->iCursor
169398 ){
169399 break; /* restriction (5) */
169400 }
169401 }
169402 if( pTerm<pEnd ) continue;
@@ -170312,10 +170426,11 @@
170312 int pc,
170313 VdbeOp *pOp
170314 ){
170315 if( (db->flags & SQLITE_VdbeAddopTrace)==0 ) return;
170316 sqlite3VdbePrintOp(0, pc, pOp);
 
170317 }
170318 #endif
170319
170320 /*
170321 ** Generate the end of the WHERE loop. See comments on
@@ -170611,18 +170726,32 @@
170611 x = sqlite3TableColumnToIndex(pIdx, x);
170612 if( x>=0 ){
170613 pOp->p2 = x;
170614 pOp->p1 = pLevel->iIdxCur;
170615 OpcodeRewriteTrace(db, k, pOp);
170616 }else{
170617 /* Unable to translate the table reference into an index
170618 ** reference. Verify that this is harmless - that the
170619 ** table being referenced really is open.
170620 */
170621 if( pLoop->wsFlags & WHERE_IDX_ONLY ){
 
 
 
170622 sqlite3ErrorMsg(pParse, "internal query planner error");
170623 pParse->rc = SQLITE_INTERNAL;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170624 }
170625 }
170626 }else if( pOp->opcode==OP_Rowid ){
170627 pOp->p1 = pLevel->iIdxCur;
170628 pOp->opcode = OP_IdxRowid;
@@ -172326,10 +172455,11 @@
172326 for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
172327 FuncDef *pFunc = pWin->pWFunc;
172328 int regArg;
172329 int nArg = pWin->bExprArgs ? 0 : windowArgCount(pWin);
172330 int i;
 
172331
172332 assert( bInverse==0 || pWin->eStart!=TK_UNBOUNDED );
172333
172334 /* All OVER clauses in the same window function aggregate step must
172335 ** be the same. */
@@ -172341,10 +172471,22 @@
172341 }else{
172342 sqlite3VdbeAddOp3(v, OP_Column, pMWin->iEphCsr, pWin->iArgCol+i, reg+i);
172343 }
172344 }
172345 regArg = reg;
 
 
 
 
 
 
 
 
 
 
 
 
172346
172347 if( pMWin->regStartRowid==0
172348 && (pFunc->funcFlags & SQLITE_FUNC_MINMAX)
172349 && (pWin->eStart!=TK_UNBOUNDED)
172350 ){
@@ -172361,29 +172503,17 @@
172361 sqlite3VdbeAddOp1(v, OP_Delete, pWin->csrApp);
172362 sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2);
172363 }
172364 sqlite3VdbeJumpHere(v, addrIsNull);
172365 }else if( pWin->regApp ){
 
172366 assert( pFunc->zName==nth_valueName
172367 || pFunc->zName==first_valueName
172368 );
172369 assert( bInverse==0 || bInverse==1 );
172370 sqlite3VdbeAddOp2(v, OP_AddImm, pWin->regApp+1-bInverse, 1);
172371 }else if( pFunc->xSFunc!=noopStepFunc ){
172372 int addrIf = 0;
172373 if( pWin->pFilter ){
172374 int regTmp;
172375 assert( ExprUseXList(pWin->pOwner) );
172376 assert( pWin->bExprArgs || !nArg ||nArg==pWin->pOwner->x.pList->nExpr );
172377 assert( pWin->bExprArgs || nArg ||pWin->pOwner->x.pList==0 );
172378 regTmp = sqlite3GetTempReg(pParse);
172379 sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol+nArg,regTmp);
172380 addrIf = sqlite3VdbeAddOp3(v, OP_IfNot, regTmp, 0, 1);
172381 VdbeCoverage(v);
172382 sqlite3ReleaseTempReg(pParse, regTmp);
172383 }
172384
172385 if( pWin->bExprArgs ){
172386 int iOp = sqlite3VdbeCurrentAddr(v);
172387 int iEnd;
172388
172389 assert( ExprUseXList(pWin->pOwner) );
@@ -172406,16 +172536,17 @@
172406 sqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ);
172407 }
172408 sqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep,
172409 bInverse, regArg, pWin->regAccum);
172410 sqlite3VdbeAppendP4(v, pFunc, P4_FUNCDEF);
172411 sqlite3VdbeChangeP5(v, (u8)nArg);
172412 if( pWin->bExprArgs ){
172413 sqlite3ReleaseTempRange(pParse, regArg, nArg);
172414 }
172415 if( addrIf ) sqlite3VdbeJumpHere(v, addrIf);
172416 }
 
 
172417 }
172418 }
172419
172420 /*
172421 ** Values that may be passed as the second argument to windowCodeOp().
@@ -173837,10 +173968,17 @@
173837 ** Then the "b" IdList records the list "a,b,c".
173838 */
173839 struct TrigEvent { int a; IdList * b; };
173840
173841 struct FrameBound { int eType; Expr *pExpr; };
 
 
 
 
 
 
 
173842
173843 /*
173844 ** Disable lookaside memory allocation for objects that might be
173845 ** shared across database connections.
173846 */
@@ -177730,11 +177868,15 @@
177730 }
177731 break;
177732 case 84: /* cmd ::= select */
177733 {
177734 SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0, 0};
177735 sqlite3Select(pParse, yymsp[0].minor.yy555, &dest);
 
 
 
 
177736 sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy555);
177737 }
177738 break;
177739 case 85: /* select ::= WITH wqlist selectnowith */
177740 {yymsp[-2].minor.yy555 = attachWithToSelect(pParse,yymsp[0].minor.yy555,yymsp[-1].minor.yy59);}
@@ -178201,11 +178343,11 @@
178201 ** that look like this: #1 #2 ... These terms refer to registers
178202 ** in the virtual machine. #N is the N-th register. */
178203 Token t = yymsp[0].minor.yy0; /*A-overwrites-X*/
178204 assert( t.n>=2 );
178205 if( pParse->nested==0 ){
178206 sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t);
178207 yymsp[0].minor.yy454 = 0;
178208 }else{
178209 yymsp[0].minor.yy454 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0);
178210 if( yymsp[0].minor.yy454 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy454->iTable);
178211 }
@@ -179049,11 +179191,11 @@
179049 #define TOKEN yyminor
179050 /************ Begin %syntax_error code ****************************************/
179051
179052 UNUSED_PARAMETER(yymajor); /* Silence some compiler warnings */
179053 if( TOKEN.z[0] ){
179054 sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN);
179055 }else{
179056 sqlite3ErrorMsg(pParse, "incomplete input");
179057 }
179058 /************ End %syntax_error code ******************************************/
179059 sqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument variable */
@@ -180540,11 +180682,13 @@
180540 }
180541 if( pParse->zErrMsg || (pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE) ){
180542 if( pParse->zErrMsg==0 ){
180543 pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc));
180544 }
180545 sqlite3_log(pParse->rc, "%s in \"%s\"", pParse->zErrMsg, pParse->zTail);
 
 
180546 nErr++;
180547 }
180548 pParse->zTail = zSql;
180549 #ifndef SQLITE_OMIT_VIRTUALTABLE
180550 sqlite3_free(pParse->apVtabLock);
@@ -182513,14 +182657,10 @@
182513 #endif
182514
182515 sqlite3Error(db, SQLITE_OK); /* Deallocates any cached error strings. */
182516 sqlite3ValueFree(db->pErr);
182517 sqlite3CloseExtensions(db);
182518 #if SQLITE_USER_AUTHENTICATION
182519 sqlite3_free(db->auth.zAuthUser);
182520 sqlite3_free(db->auth.zAuthPW);
182521 #endif
182522
182523 db->eOpenState = SQLITE_STATE_ERROR;
182524
182525 /* The temp-database schema is allocated differently from the other schema
182526 ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()).
@@ -183951,12 +184091,12 @@
183951 # error SQLITE_MAX_COMPOUND_SELECT must be at least 2
183952 #endif
183953 #if SQLITE_MAX_VDBE_OP<40
183954 # error SQLITE_MAX_VDBE_OP must be at least 40
183955 #endif
183956 #if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>127
183957 # error SQLITE_MAX_FUNCTION_ARG must be between 0 and 127
183958 #endif
183959 #if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>125
183960 # error SQLITE_MAX_ATTACHED must be between 0 and 125
183961 #endif
183962 #if SQLITE_MAX_LIKE_PATTERN_LENGTH<1
@@ -184019,12 +184159,12 @@
184019 }
184020 oldLimit = db->aLimit[limitId];
184021 if( newLimit>=0 ){ /* IMP: R-52476-28732 */
184022 if( newLimit>aHardLimit[limitId] ){
184023 newLimit = aHardLimit[limitId]; /* IMP: R-51463-25634 */
184024 }else if( newLimit<1 && limitId==SQLITE_LIMIT_LENGTH ){
184025 newLimit = 1;
184026 }
184027 db->aLimit[limitId] = newLimit;
184028 }
184029 return oldLimit; /* IMP: R-53341-35419 */
184030 }
@@ -185364,11 +185504,10 @@
185364 rc = x;
185365 #if defined(SQLITE_DEBUG)
185366 /* Invoke these debugging routines so that the compiler does not
185367 ** issue "defined but not used" warnings. */
185368 if( x==9999 ){
185369 sqlite3ShowExpr(0);
185370 sqlite3ShowExpr(0);
185371 sqlite3ShowExprList(0);
185372 sqlite3ShowIdList(0);
185373 sqlite3ShowSrcList(0);
185374 sqlite3ShowWith(0);
@@ -189796,14 +189935,19 @@
189796
189797 assert_fts3_nc( p!=0 && *p1!=0 && *p2!=0 );
189798 if( *p1==POS_COLUMN ){
189799 p1++;
189800 p1 += fts3GetVarint32(p1, &iCol1);
 
 
 
189801 }
189802 if( *p2==POS_COLUMN ){
189803 p2++;
189804 p2 += fts3GetVarint32(p2, &iCol2);
 
 
189805 }
189806
189807 while( 1 ){
189808 if( iCol1==iCol2 ){
189809 char *pSave = p;
@@ -192970,11 +193114,11 @@
192970 for(p=pExpr; p->pLeft; p=p->pLeft){
192971 assert( p->pRight->pPhrase->doclist.nList>0 );
192972 nTmp += p->pRight->pPhrase->doclist.nList;
192973 }
192974 nTmp += p->pPhrase->doclist.nList;
192975 aTmp = sqlite3_malloc64(nTmp*2);
192976 if( !aTmp ){
192977 *pRc = SQLITE_NOMEM;
192978 res = 0;
192979 }else{
192980 char *aPoslist = p->pPhrase->doclist.pList;
@@ -193621,11 +193765,11 @@
193621 SQLITE_PRIVATE int sqlite3Fts3Corrupt(){
193622 return SQLITE_CORRUPT_VTAB;
193623 }
193624 #endif
193625
193626 #if !SQLITE_CORE
193627 /*
193628 ** Initialize API pointer table, if required.
193629 */
193630 #ifdef _WIN32
193631 __declspec(dllexport)
@@ -194523,14 +194667,15 @@
194523 rc = pModule->xNext(pCursor, &zByte, &nByte, &iBegin, &iEnd, &iPos);
194524 if( rc==SQLITE_OK ){
194525 Fts3PhraseToken *pToken;
194526
194527 p = fts3ReallocOrFree(p, nSpace + ii*sizeof(Fts3PhraseToken));
194528 if( !p ) goto no_mem;
194529
194530 zTemp = fts3ReallocOrFree(zTemp, nTemp + nByte);
194531 if( !zTemp ) goto no_mem;
 
 
 
194532
194533 assert( nToken==ii );
194534 pToken = &((Fts3Phrase *)(&p[1]))->aToken[ii];
194535 memset(pToken, 0, sizeof(Fts3PhraseToken));
194536
@@ -194541,53 +194686,51 @@
194541 pToken->isPrefix = (iEnd<nInput && zInput[iEnd]=='*');
194542 pToken->bFirst = (iBegin>0 && zInput[iBegin-1]=='^');
194543 nToken = ii+1;
194544 }
194545 }
194546
194547 pModule->xClose(pCursor);
194548 pCursor = 0;
194549 }
194550
194551 if( rc==SQLITE_DONE ){
194552 int jj;
194553 char *zBuf = 0;
194554
194555 p = fts3ReallocOrFree(p, nSpace + nToken*sizeof(Fts3PhraseToken) + nTemp);
194556 if( !p ) goto no_mem;
 
 
 
194557 memset(p, 0, (char *)&(((Fts3Phrase *)&p[1])->aToken[0])-(char *)p);
194558 p->eType = FTSQUERY_PHRASE;
194559 p->pPhrase = (Fts3Phrase *)&p[1];
194560 p->pPhrase->iColumn = pParse->iDefaultCol;
194561 p->pPhrase->nToken = nToken;
194562
194563 zBuf = (char *)&p->pPhrase->aToken[nToken];
 
194564 if( zTemp ){
194565 memcpy(zBuf, zTemp, nTemp);
194566 sqlite3_free(zTemp);
194567 }else{
194568 assert( nTemp==0 );
194569 }
194570
194571 for(jj=0; jj<p->pPhrase->nToken; jj++){
194572 p->pPhrase->aToken[jj].z = zBuf;
194573 zBuf += p->pPhrase->aToken[jj].n;
194574 }
194575 rc = SQLITE_OK;
194576 }
194577
194578 *ppExpr = p;
194579 return rc;
194580 no_mem:
194581
194582 if( pCursor ){
194583 pModule->xClose(pCursor);
194584 }
194585 sqlite3_free(zTemp);
194586 sqlite3_free(p);
194587 *ppExpr = 0;
194588 return SQLITE_NOMEM;
 
 
 
194589 }
194590
194591 /*
194592 ** The output variable *ppExpr is populated with an allocated Fts3Expr
194593 ** structure, or set to 0 if the end of the input buffer is reached.
@@ -215395,12 +215538,12 @@
215395 #endif
215396 }
215397 sqlite3_str_append(pOut, "}", 1);
215398 }
215399 errCode = sqlite3_str_errcode(pOut);
215400 sqlite3_result_text(ctx, sqlite3_str_finish(pOut), -1, sqlite3_free);
215401 sqlite3_result_error_code(ctx, errCode);
 
215402 }
215403
215404 /* This routine implements an SQL function that returns the "depth" parameter
215405 ** from the front of a blob that is an r-tree node. For example:
215406 **
@@ -217912,11 +218055,11 @@
217912 return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY,
217913 (void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback
217914 );
217915 }
217916
217917 #if !SQLITE_CORE
217918 #ifdef _WIN32
217919 __declspec(dllexport)
217920 #endif
217921 SQLITE_API int sqlite3_rtree_init(
217922 sqlite3 *db,
@@ -218503,11 +218646,11 @@
218503 }
218504
218505 return rc;
218506 }
218507
218508 #if !SQLITE_CORE
218509 #ifdef _WIN32
218510 __declspec(dllexport)
218511 #endif
218512 SQLITE_API int sqlite3_icu_init(
218513 sqlite3 *db,
@@ -226177,10 +226320,12 @@
226177 if( (rc = sqlite3PagerWrite(pDbPage))==SQLITE_OK && pData ){
226178 unsigned char *aPage = sqlite3PagerGetData(pDbPage);
226179 memcpy(aPage, pData, szPage);
226180 pTab->pgnoTrunc = 0;
226181 }
 
 
226182 }
226183 sqlite3PagerUnref(pDbPage);
226184 return rc;
226185
226186 update_fail:
@@ -226210,11 +226355,15 @@
226210 static int dbpageSync(sqlite3_vtab *pVtab){
226211 DbpageTable *pTab = (DbpageTable *)pVtab;
226212 if( pTab->pgnoTrunc>0 ){
226213 Btree *pBt = pTab->db->aDb[pTab->iDbTrunc].pBt;
226214 Pager *pPager = sqlite3BtreePager(pBt);
226215 sqlite3PagerTruncateImage(pPager, pTab->pgnoTrunc);
 
 
 
 
226216 }
226217 pTab->pgnoTrunc = 0;
226218 return SQLITE_OK;
226219 }
226220
@@ -232804,20 +232953,46 @@
232804 #endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
232805
232806 /************** End of sqlite3session.c **************************************/
232807 /************** Begin file fts5.c ********************************************/
232808
232809
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232810 #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5)
232811
232812 #if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
232813 # define NDEBUG 1
232814 #endif
232815 #if defined(NDEBUG) && defined(SQLITE_DEBUG)
232816 # undef NDEBUG
232817 #endif
232818
 
 
 
 
 
 
232819 /*
232820 ** 2014 May 31
232821 **
232822 ** The author disclaims copyright to this source code. In place of
232823 ** a legal notice, here is a blessing:
@@ -233114,17 +233289,32 @@
233114 ** This is used to access token iToken of phrase hit iIdx within the
233115 ** current row. If iIdx is less than zero or greater than or equal to the
233116 ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise,
233117 ** output variable (*ppToken) is set to point to a buffer containing the
233118 ** matching document token, and (*pnToken) to the size of that buffer in
233119 ** bytes. This API is not available if the specified token matches a
233120 ** prefix query term. In that case both output variables are always set
233121 ** to 0.
233122 **
233123 ** The output text is not a copy of the document text that was tokenized.
233124 ** It is the output of the tokenizer module. For tokendata=1 tables, this
233125 ** includes any embedded 0x00 and trailing data.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233126 **
233127 ** This API can be quite slow if used with an FTS5 table created with the
233128 ** "detail=none" or "detail=column" option.
233129 **
233130 ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale)
@@ -233803,11 +233993,12 @@
233803 int nUsermerge; /* 'usermerge' setting */
233804 int nHashSize; /* Bytes of memory for in-memory hash */
233805 char *zRank; /* Name of rank function */
233806 char *zRankArgs; /* Arguments to rank function */
233807 int bSecureDelete; /* 'secure-delete' */
233808 int nDeleteMerge; /* 'deletemerge' */
 
233809
233810 /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */
233811 char **pzErrmsg;
233812
233813 #ifdef SQLITE_DEBUG
@@ -234060,11 +234251,18 @@
234060 static int sqlite3Fts5StructureTest(Fts5Index*, void*);
234061
234062 /*
234063 ** Used by xInstToken():
234064 */
234065 static int sqlite3Fts5IterToken(Fts5IndexIter*, i64, int, int, const char**, int*);
 
 
 
 
 
 
 
234066
234067 /*
234068 ** Insert or remove data to or from the index. Each time a document is
234069 ** added to or removed from the index, this function is called one or more
234070 ** times.
@@ -238274,10 +238472,23 @@
238274 if( bVal<0 ){
238275 *pbBadkey = 1;
238276 }else{
238277 pConfig->bSecureDelete = (bVal ? 1 : 0);
238278 }
 
 
 
 
 
 
 
 
 
 
 
 
 
238279 }else{
238280 *pbBadkey = 1;
238281 }
238282 return rc;
238283 }
@@ -241409,11 +241620,11 @@
241409 && memcmp(pT->pTerm, pToken, pT->nQueryTerm)==0
241410 ){
241411 int rc = sqlite3Fts5PoslistWriterAppend(
241412 &pExpr->apExprPhrase[i]->poslist, &p->aPopulator[i].writer, p->iOff
241413 );
241414 if( rc==SQLITE_OK && pExpr->pConfig->bTokendata && !pT->bPrefix ){
241415 int iCol = p->iOff>>32;
241416 int iTokOff = p->iOff & 0x7FFFFFFF;
241417 rc = sqlite3Fts5IndexIterWriteTokendata(
241418 pT->pIter, pToken, nToken, iRowid, iCol, iTokOff
241419 );
@@ -241602,19 +241813,18 @@
241602 pPhrase = pExpr->apExprPhrase[iPhrase];
241603 if( iToken<0 || iToken>=pPhrase->nTerm ){
241604 return SQLITE_RANGE;
241605 }
241606 pTerm = &pPhrase->aTerm[iToken];
241607 if( pTerm->bPrefix==0 ){
241608 if( pExpr->pConfig->bTokendata ){
241609 rc = sqlite3Fts5IterToken(
241610 pTerm->pIter, iRowid, iCol, iOff+iToken, ppOut, pnOut
241611 );
241612 }else{
241613 *ppOut = pTerm->pTerm;
241614 *pnOut = pTerm->nFullTerm;
241615 }
241616 }
241617 return rc;
241618 }
241619
241620 /*
@@ -248425,10 +248635,387 @@
248425 fts5BufferFree(&tmp);
248426 memset(&out.p[out.n], 0, FTS5_DATA_ZERO_PADDING);
248427 *p1 = out;
248428 }
248429
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248430 static void fts5SetupPrefixIter(
248431 Fts5Index *p, /* Index to read from */
248432 int bDesc, /* True for "ORDER BY rowid DESC" */
248433 int iIdx, /* Index to scan for data */
248434 u8 *pToken, /* Buffer containing prefix to match */
@@ -248435,137 +249022,91 @@
248435 int nToken, /* Size of buffer pToken in bytes */
248436 Fts5Colset *pColset, /* Restrict matches to these columns */
248437 Fts5Iter **ppIter /* OUT: New iterator */
248438 ){
248439 Fts5Structure *pStruct;
248440 Fts5Buffer *aBuf;
248441 int nBuf = 32;
248442 int nMerge = 1;
248443
248444 void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*);
248445 void (*xAppend)(Fts5Index*, u64, Fts5Iter*, Fts5Buffer*);
 
 
 
 
 
 
 
 
 
 
 
 
248446 if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){
248447 xMerge = fts5MergeRowidLists;
248448 xAppend = fts5AppendRowid;
248449 }else{
248450 nMerge = FTS5_MERGE_NLIST-1;
248451 nBuf = nMerge*8; /* Sufficient to merge (16^8)==(2^32) lists */
248452 xMerge = fts5MergePrefixLists;
248453 xAppend = fts5AppendPoslist;
248454 }
248455
248456 aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*nBuf);
248457 pStruct = fts5StructureRead(p);
248458 assert( p->rc!=SQLITE_OK || (aBuf && pStruct) );
248459
248460 if( p->rc==SQLITE_OK ){
248461 const int flags = FTS5INDEX_QUERY_SCAN
248462 | FTS5INDEX_QUERY_SKIPEMPTY
248463 | FTS5INDEX_QUERY_NOOUTPUT;
248464 int i;
248465 i64 iLastRowid = 0;
248466 Fts5Iter *p1 = 0; /* Iterator used to gather data from index */
248467 Fts5Data *pData;
248468 Fts5Buffer doclist;
248469 int bNewTerm = 1;
248470
248471 memset(&doclist, 0, sizeof(doclist));
248472
248473 /* If iIdx is non-zero, then it is the number of a prefix-index for
248474 ** prefixes 1 character longer than the prefix being queried for. That
248475 ** index contains all the doclists required, except for the one
248476 ** corresponding to the prefix itself. That one is extracted from the
248477 ** main term index here. */
248478 if( iIdx!=0 ){
248479 int dummy = 0;
248480 const int f2 = FTS5INDEX_QUERY_SKIPEMPTY|FTS5INDEX_QUERY_NOOUTPUT;
248481 pToken[0] = FTS5_MAIN_PREFIX;
248482 fts5MultiIterNew(p, pStruct, f2, pColset, pToken, nToken, -1, 0, &p1);
248483 fts5IterSetOutputCb(&p->rc, p1);
248484 for(;
248485 fts5MultiIterEof(p, p1)==0;
248486 fts5MultiIterNext2(p, p1, &dummy)
248487 ){
248488 Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ];
248489 p1->xSetOutputs(p1, pSeg);
248490 if( p1->base.nData ){
248491 xAppend(p, (u64)p1->base.iRowid-(u64)iLastRowid, p1, &doclist);
248492 iLastRowid = p1->base.iRowid;
248493 }
248494 }
248495 fts5MultiIterFree(p1);
248496 }
248497
248498 pToken[0] = FTS5_MAIN_PREFIX + iIdx;
248499 fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1);
248500 fts5IterSetOutputCb(&p->rc, p1);
248501
248502 for( /* no-op */ ;
248503 fts5MultiIterEof(p, p1)==0;
248504 fts5MultiIterNext2(p, p1, &bNewTerm)
248505 ){
248506 Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ];
248507 int nTerm = pSeg->term.n;
248508 const u8 *pTerm = pSeg->term.p;
248509 p1->xSetOutputs(p1, pSeg);
248510
248511 assert_nc( memcmp(pToken, pTerm, MIN(nToken, nTerm))<=0 );
248512 if( bNewTerm ){
248513 if( nTerm<nToken || memcmp(pToken, pTerm, nToken) ) break;
248514 }
248515
248516 if( p1->base.nData==0 ) continue;
248517 if( p1->base.iRowid<=iLastRowid && doclist.n>0 ){
248518 for(i=0; p->rc==SQLITE_OK && doclist.n; i++){
248519 int i1 = i*nMerge;
248520 int iStore;
248521 assert( i1+nMerge<=nBuf );
248522 for(iStore=i1; iStore<i1+nMerge; iStore++){
248523 if( aBuf[iStore].n==0 ){
248524 fts5BufferSwap(&doclist, &aBuf[iStore]);
248525 fts5BufferZero(&doclist);
248526 break;
248527 }
248528 }
248529 if( iStore==i1+nMerge ){
248530 xMerge(p, &doclist, nMerge, &aBuf[i1]);
248531 for(iStore=i1; iStore<i1+nMerge; iStore++){
248532 fts5BufferZero(&aBuf[iStore]);
248533 }
248534 }
248535 }
248536 iLastRowid = 0;
248537 }
248538
248539 xAppend(p, (u64)p1->base.iRowid-(u64)iLastRowid, p1, &doclist);
248540 iLastRowid = p1->base.iRowid;
248541 }
248542
248543 assert( (nBuf%nMerge)==0 );
248544 for(i=0; i<nBuf; i+=nMerge){
248545 int iFree;
248546 if( p->rc==SQLITE_OK ){
248547 xMerge(p, &doclist, nMerge, &aBuf[i]);
248548 }
248549 for(iFree=i; iFree<i+nMerge; iFree++){
248550 fts5BufferFree(&aBuf[iFree]);
248551 }
248552 }
248553 fts5MultiIterFree(p1);
248554
248555 pData = fts5IdxMalloc(p, sizeof(*pData)+doclist.n+FTS5_DATA_ZERO_PADDING);
 
248556 if( pData ){
248557 pData->p = (u8*)&pData[1];
248558 pData->nn = pData->szLeaf = doclist.n;
248559 if( doclist.n ) memcpy(pData->p, doclist.p, doclist.n);
248560 fts5MultiIterNew2(p, pData, bDesc, ppIter);
248561 }
248562 fts5BufferFree(&doclist);
 
 
 
 
 
 
248563 }
248564
 
 
248565 fts5StructureRelease(pStruct);
248566 sqlite3_free(aBuf);
248567 }
248568
248569
248570 /*
248571 ** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain
@@ -248815,42 +249356,10 @@
248815 static void fts5SegIterSetEOF(Fts5SegIter *pSeg){
248816 fts5DataRelease(pSeg->pLeaf);
248817 pSeg->pLeaf = 0;
248818 }
248819
248820 /*
248821 ** Usually, a tokendata=1 iterator (struct Fts5TokenDataIter) accumulates an
248822 ** array of these for each row it visits. Or, for an iterator used by an
248823 ** "ORDER BY rank" query, it accumulates an array of these for the entire
248824 ** query.
248825 **
248826 ** Each instance in the array indicates the iterator (and therefore term)
248827 ** associated with position iPos of rowid iRowid. This is used by the
248828 ** xInstToken() API.
248829 */
248830 struct Fts5TokenDataMap {
248831 i64 iRowid; /* Row this token is located in */
248832 i64 iPos; /* Position of token */
248833 int iIter; /* Iterator token was read from */
248834 };
248835
248836 /*
248837 ** An object used to supplement Fts5Iter for tokendata=1 iterators.
248838 */
248839 struct Fts5TokenDataIter {
248840 int nIter;
248841 int nIterAlloc;
248842
248843 int nMap;
248844 int nMapAlloc;
248845 Fts5TokenDataMap *aMap;
248846
248847 Fts5PoslistReader *aPoslistReader;
248848 int *aPoslistToIter;
248849 Fts5Iter *apIter[1];
248850 };
248851
248852 /*
248853 ** This function appends iterator pAppend to Fts5TokenDataIter pIn and
248854 ** returns the result.
248855 */
248856 static Fts5TokenDataIter *fts5AppendTokendataIter(
@@ -248883,58 +249392,10 @@
248883 assert( pRet==0 || pRet->nIter<=pRet->nIterAlloc );
248884
248885 return pRet;
248886 }
248887
248888 /*
248889 ** Delete an Fts5TokenDataIter structure and its contents.
248890 */
248891 static void fts5TokendataIterDelete(Fts5TokenDataIter *pSet){
248892 if( pSet ){
248893 int ii;
248894 for(ii=0; ii<pSet->nIter; ii++){
248895 fts5MultiIterFree(pSet->apIter[ii]);
248896 }
248897 sqlite3_free(pSet->aPoslistReader);
248898 sqlite3_free(pSet->aMap);
248899 sqlite3_free(pSet);
248900 }
248901 }
248902
248903 /*
248904 ** Append a mapping to the token-map belonging to object pT.
248905 */
248906 static void fts5TokendataIterAppendMap(
248907 Fts5Index *p,
248908 Fts5TokenDataIter *pT,
248909 int iIter,
248910 i64 iRowid,
248911 i64 iPos
248912 ){
248913 if( p->rc==SQLITE_OK ){
248914 if( pT->nMap==pT->nMapAlloc ){
248915 int nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64;
248916 int nByte = nNew * sizeof(Fts5TokenDataMap);
248917 Fts5TokenDataMap *aNew;
248918
248919 aNew = (Fts5TokenDataMap*)sqlite3_realloc(pT->aMap, nByte);
248920 if( aNew==0 ){
248921 p->rc = SQLITE_NOMEM;
248922 return;
248923 }
248924
248925 pT->aMap = aNew;
248926 pT->nMapAlloc = nNew;
248927 }
248928
248929 pT->aMap[pT->nMap].iRowid = iRowid;
248930 pT->aMap[pT->nMap].iPos = iPos;
248931 pT->aMap[pT->nMap].iIter = iIter;
248932 pT->nMap++;
248933 }
248934 }
248935
248936 /*
248937 ** The iterator passed as the only argument must be a tokendata=1 iterator
248938 ** (pIter->pTokenDataIter!=0). This function sets the iterator output
248939 ** variables (pIter->base.*) according to the contents of the current
248940 ** row.
@@ -248971,11 +249432,11 @@
248971 int eDetail = pIter->pIndex->pConfig->eDetail;
248972 pIter->base.bEof = 0;
248973 pIter->base.iRowid = iRowid;
248974
248975 if( nHit==1 && eDetail==FTS5_DETAIL_FULL ){
248976 fts5TokendataIterAppendMap(pIter->pIndex, pT, iMin, iRowid, -1);
248977 }else
248978 if( nHit>1 && eDetail!=FTS5_DETAIL_NONE ){
248979 int nReader = 0;
248980 int nByte = 0;
248981 i64 iPrev = 0;
@@ -249224,10 +249685,11 @@
249224
249225 if( p->rc==SQLITE_OK ){
249226 pRet = fts5MultiIterAlloc(p, 0);
249227 }
249228 if( pRet ){
 
249229 pRet->pTokenDataIter = pSet;
249230 if( pSet ){
249231 fts5IterSetOutputsTokendata(pRet);
249232 }else{
249233 pRet->base.bEof = 1;
@@ -249238,11 +249700,10 @@
249238
249239 fts5StructureRelease(pStruct);
249240 fts5BufferFree(&bSeek);
249241 return pRet;
249242 }
249243
249244
249245 /*
249246 ** Open a new iterator to iterate though all rowid that match the
249247 ** specified token or token prefix.
249248 */
@@ -249262,12 +249723,18 @@
249262
249263 if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){
249264 int iIdx = 0; /* Index to search */
249265 int iPrefixIdx = 0; /* +1 prefix index */
249266 int bTokendata = pConfig->bTokendata;
 
249267 if( nToken>0 ) memcpy(&buf.p[1], pToken, nToken);
249268
 
 
 
 
 
249269 if( flags & (FTS5INDEX_QUERY_NOTOKENDATA|FTS5INDEX_QUERY_SCAN) ){
249270 bTokendata = 0;
249271 }
249272
249273 /* Figure out which index to search and set iIdx accordingly. If this
@@ -249294,11 +249761,11 @@
249294 if( nIdxChar==nChar+1 ) iPrefixIdx = iIdx;
249295 }
249296 }
249297
249298 if( bTokendata && iIdx==0 ){
249299 buf.p[0] = '0';
249300 pRet = fts5SetupTokendataIter(p, buf.p, nToken+1, pColset);
249301 }else if( iIdx<=pConfig->nPrefix ){
249302 /* Straight index lookup */
249303 Fts5Structure *pStruct = fts5StructureRead(p);
249304 buf.p[0] = (u8)(FTS5_MAIN_PREFIX + iIdx);
@@ -249307,11 +249774,11 @@
249307 pColset, buf.p, nToken+1, -1, 0, &pRet
249308 );
249309 fts5StructureRelease(pStruct);
249310 }
249311 }else{
249312 /* Scan multiple terms in the main index */
249313 int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0;
249314 fts5SetupPrefixIter(p, bDesc, iPrefixIdx, buf.p, nToken+1, pColset,&pRet);
249315 if( pRet==0 ){
249316 assert( p->rc!=SQLITE_OK );
249317 }else{
@@ -249343,11 +249810,12 @@
249343 ** Move to the next matching rowid.
249344 */
249345 static int sqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){
249346 Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
249347 assert( pIter->pIndex->rc==SQLITE_OK );
249348 if( pIter->pTokenDataIter ){
 
249349 fts5TokendataIterNext(pIter, 0, 0);
249350 }else{
249351 fts5MultiIterNext(pIter->pIndex, pIter, 0, 0);
249352 }
249353 return fts5IndexReturn(pIter->pIndex);
@@ -249380,11 +249848,12 @@
249380 ** definition of "at or after" depends on whether this iterator iterates
249381 ** in ascending or descending rowid order.
249382 */
249383 static int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){
249384 Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
249385 if( pIter->pTokenDataIter ){
 
249386 fts5TokendataIterNext(pIter, 1, iMatch);
249387 }else{
249388 fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch);
249389 }
249390 return fts5IndexReturn(pIter->pIndex);
@@ -249398,32 +249867,88 @@
249398 const char *z = (const char*)fts5MultiIterTerm((Fts5Iter*)pIndexIter, &n);
249399 assert_nc( z || n<=1 );
249400 *pn = n-1;
249401 return (z ? &z[1] : 0);
249402 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249403
249404 /*
249405 ** This is used by xInstToken() to access the token at offset iOff, column
249406 ** iCol of row iRowid. The token is returned via output variables *ppOut
249407 ** and *pnOut. The iterator passed as the first argument must be a tokendata=1
249408 ** iterator (pIter->pTokenDataIter!=0).
 
 
249409 */
249410 static int sqlite3Fts5IterToken(
249411 Fts5IndexIter *pIndexIter,
 
249412 i64 iRowid,
249413 int iCol,
249414 int iOff,
249415 const char **ppOut, int *pnOut
249416 ){
249417 Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
249418 Fts5TokenDataIter *pT = pIter->pTokenDataIter;
249419 Fts5TokenDataMap *aMap = pT->aMap;
249420 i64 iPos = (((i64)iCol)<<32) + iOff;
249421
249422 int i1 = 0;
249423 int i2 = pT->nMap;
249424 int iTest = 0;
 
 
 
 
 
 
 
 
 
 
249425
249426 while( i2>i1 ){
249427 iTest = (i1 + i2) / 2;
249428
249429 if( aMap[iTest].iRowid<iRowid ){
@@ -249443,13 +249968,19 @@
249443 }
249444 }
249445 }
249446
249447 if( i2>i1 ){
249448 Fts5Iter *pMap = pT->apIter[aMap[iTest].iIter];
249449 *ppOut = (const char*)pMap->aSeg[0].term.p+1;
249450 *pnOut = pMap->aSeg[0].term.n-1;
 
 
 
 
 
 
249451 }
249452
249453 return SQLITE_OK;
249454 }
249455
@@ -249457,11 +249988,13 @@
249457 ** Clear any existing entries from the token-map associated with the
249458 ** iterator passed as the only argument.
249459 */
249460 static void sqlite3Fts5IndexIterClearTokendata(Fts5IndexIter *pIndexIter){
249461 Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
249462 if( pIter && pIter->pTokenDataIter ){
 
 
249463 pIter->pTokenDataIter->nMap = 0;
249464 }
249465 }
249466
249467 /*
@@ -249477,21 +250010,33 @@
249477 i64 iRowid, int iCol, int iOff
249478 ){
249479 Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
249480 Fts5TokenDataIter *pT = pIter->pTokenDataIter;
249481 Fts5Index *p = pIter->pIndex;
249482 int ii;
249483
249484 assert( p->pConfig->eDetail!=FTS5_DETAIL_FULL );
249485 assert( pIter->pTokenDataIter );
249486
249487 for(ii=0; ii<pT->nIter; ii++){
249488 Fts5Buffer *pTerm = &pT->apIter[ii]->aSeg[0].term;
249489 if( nToken==pTerm->n-1 && memcmp(pToken, pTerm->p+1, nToken)==0 ) break;
249490 }
249491 if( ii<pT->nIter ){
249492 fts5TokendataIterAppendMap(p, pT, ii, iRowid, (((i64)iCol)<<32) + iOff);
 
 
 
 
 
 
 
 
 
 
 
 
249493 }
249494 return fts5IndexReturn(p);
249495 }
249496
249497 /*
@@ -251392,10 +251937,11 @@
251392 ** containing a copy of the header from an Fts5Config pointer.
251393 */
251394 #define FTS5_LOCALE_HDR_SIZE ((int)sizeof( ((Fts5Global*)0)->aLocaleHdr ))
251395 #define FTS5_LOCALE_HDR(pConfig) ((const u8*)(pConfig->pGlobal->aLocaleHdr))
251396
 
251397
251398 /*
251399 ** Each auxiliary function registered with the FTS5 module is represented
251400 ** by an object of the following type. All such objects are stored as part
251401 ** of the Fts5Global.pAux list.
@@ -251931,10 +252477,11 @@
251931 ){
251932 /* A MATCH operator or equivalent */
251933 if( p->usable==0 || iCol<0 ){
251934 /* As there exists an unusable MATCH constraint this is an
251935 ** unusable plan. Return SQLITE_CONSTRAINT. */
 
251936 return SQLITE_CONSTRAINT;
251937 }else{
251938 if( iCol==nCol+1 ){
251939 if( bSeenRank ) continue;
251940 idxStr[iIdxStr++] = 'r';
@@ -252716,10 +253263,11 @@
252716 sqlite3_value *pRowidEq = 0; /* rowid = ? expression (or NULL) */
252717 sqlite3_value *pRowidLe = 0; /* rowid <= ? expression (or NULL) */
252718 sqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */
252719 int iCol; /* Column on LHS of MATCH operator */
252720 char **pzErrmsg = pConfig->pzErrmsg;
 
252721 int i;
252722 int iIdxStr = 0;
252723 Fts5Expr *pExpr = 0;
252724
252725 assert( pConfig->bLock==0 );
@@ -252751,10 +253299,13 @@
252751 int bInternal = 0;
252752
252753 rc = fts5ExtractExprText(pConfig, apVal[i], &zText, &bFreeAndReset);
252754 if( rc!=SQLITE_OK ) goto filter_out;
252755 if( zText==0 ) zText = "";
 
 
 
252756
252757 iCol = 0;
252758 do{
252759 iCol = iCol*10 + (idxStr[iIdxStr]-'0');
252760 iIdxStr++;
@@ -252891,10 +253442,11 @@
252891 }
252892
252893 filter_out:
252894 sqlite3Fts5ExprFree(pExpr);
252895 pConfig->pzErrmsg = pzErrmsg;
 
252896 return rc;
252897 }
252898
252899 /*
252900 ** This is the xEof method of the virtual table. SQLite calls this
@@ -254886,11 +255438,11 @@
254886 int nArg, /* Number of args */
254887 sqlite3_value **apUnused /* Function arguments */
254888 ){
254889 assert( nArg==0 );
254890 UNUSED_PARAM2(nArg, apUnused);
254891 sqlite3_result_text(pCtx, "fts5: 2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f28411c85dda156f82636e", -1, SQLITE_TRANSIENT);
254892 }
254893
254894 /*
254895 ** Implementation of fts5_locale(LOCALE, TEXT) function.
254896 **
@@ -254949,10 +255501,24 @@
254949 assert( &pCsr[nText]==&pBlob[nBlob] );
254950
254951 sqlite3_result_blob(pCtx, pBlob, nBlob, sqlite3_free);
254952 }
254953 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254954
254955 /*
254956 ** Return true if zName is the extension on one of the shadow tables used
254957 ** by this module.
254958 */
@@ -255079,13 +255645,20 @@
255079 );
255080 }
255081 if( rc==SQLITE_OK ){
255082 rc = sqlite3_create_function(
255083 db, "fts5_locale", 2,
255084 SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE,
255085 p, fts5LocaleFunc, 0, 0
255086 );
 
 
 
 
 
 
 
255087 }
255088 }
255089
255090 /* If SQLITE_FTS5_ENABLE_TEST_MI is defined, assume that the file
255091 ** fts5_test_mi.c is compiled and linked into the executable. And call
@@ -258007,22 +258580,22 @@
258007 int rc = SQLITE_OK;
258008 char aBuf[32];
258009 char *zOut = aBuf;
258010 int ii;
258011 const unsigned char *zIn = (const unsigned char*)pText;
258012 const unsigned char *zEof = &zIn[nText];
258013 u32 iCode;
258014 int aStart[3]; /* Input offset of each character in aBuf[] */
258015
258016 UNUSED_PARAM(unusedFlags);
258017
258018 /* Populate aBuf[] with the characters for the first trigram. */
258019 for(ii=0; ii<3; ii++){
258020 do {
258021 aStart[ii] = zIn - (const unsigned char*)pText;
 
258022 READ_UTF8(zIn, zEof, iCode);
258023 if( iCode==0 ) return SQLITE_OK;
258024 if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam);
258025 }while( iCode==0 );
258026 WRITE_UTF8(zOut, iCode);
258027 }
258028
@@ -258039,12 +258612,15 @@
258039 const char *z1;
258040
258041 /* Read characters from the input up until the first non-diacritic */
258042 do {
258043 iNext = zIn - (const unsigned char*)pText;
 
 
 
 
258044 READ_UTF8(zIn, zEof, iCode);
258045 if( iCode==0 ) break;
258046 if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam);
258047 }while( iCode==0 );
258048
258049 /* Pass the current trigram back to fts5 */
258050 rc = xToken(pCtx, 0, aBuf, zOut-aBuf, aStart[0], iNext);
@@ -260077,11 +260653,11 @@
260077
260078 return sqlite3_create_module_v2(db, "fts5vocab", &fts5Vocab, p, 0);
260079 }
260080
260081
260082
260083 #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) */
260084
260085 /************** End of fts5.c ************************************************/
260086 /************** Begin file stmt.c ********************************************/
260087 /*
@@ -260433,6 +261009,7 @@
260433 #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */
260434
260435 /************** End of stmt.c ************************************************/
260436 /* Return the source-id for this library */
260437 SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }
 
260438 /************************** End of sqlite3.c ******************************/
260439
--- extsrc/sqlite3.c
+++ extsrc/sqlite3.c
@@ -1,8 +1,8 @@
1 /******************************************************************************
2 ** This file is an amalgamation of many separate C source files from SQLite
3 ** version 3.48.0. By combining all the individual C code files into this
4 ** single large file, the entire code can be compiled as a single translation
5 ** unit. This allows many compilers to do optimizations that would not be
6 ** possible if the files were compiled separately. Performance improvements
7 ** of 5% or more are commonly seen when SQLite is compiled as a single
8 ** translation unit.
@@ -16,12 +16,15 @@
16 ** if you want a wrapper to interface SQLite with your choice of programming
17 ** language. The code for the "sqlite3" command-line shell is also in a
18 ** separate file. This file contains only code for the core SQLite library.
19 **
20 ** The content in this amalgamation comes from Fossil check-in
21 ** 2b17bc49655c577029919c2d409de994b0d2 with changes in files:
22 **
23 **
24 */
25 #ifndef SQLITE_AMALGAMATION
26 #define SQLITE_CORE 1
27 #define SQLITE_AMALGAMATION 1
28 #ifndef SQLITE_PRIVATE
29 # define SQLITE_PRIVATE static
30 #endif
@@ -460,13 +463,13 @@
463 **
464 ** See also: [sqlite3_libversion()],
465 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
466 ** [sqlite_version()] and [sqlite_source_id()].
467 */
468 #define SQLITE_VERSION "3.48.0"
469 #define SQLITE_VERSION_NUMBER 3048000
470 #define SQLITE_SOURCE_ID "2024-12-30 21:23:53 2b17bc49655c577029919c2d409de994b0d252f8efb5da1ba0913f2c96bee552"
471
472 /*
473 ** CAPI3REF: Run-Time Library Version Numbers
474 ** KEYWORDS: sqlite3_version sqlite3_sourceid
475 **
@@ -966,10 +969,17 @@
969 **
970 ** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying
971 ** filesystem supports doing multiple write operations atomically when those
972 ** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and
973 ** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE].
974 **
975 ** The SQLITE_IOCAP_SUBPAGE_READ property means that it is ok to read
976 ** from the database file in amounts that are not a multiple of the
977 ** page size and that do not begin at a page boundary. Without this
978 ** property, SQLite is careful to only do full-page reads and write
979 ** on aligned pages, with the one exception that it will do a sub-page
980 ** read of the first page to access the database header.
981 */
982 #define SQLITE_IOCAP_ATOMIC 0x00000001
983 #define SQLITE_IOCAP_ATOMIC512 0x00000002
984 #define SQLITE_IOCAP_ATOMIC1K 0x00000004
985 #define SQLITE_IOCAP_ATOMIC2K 0x00000008
@@ -982,10 +992,11 @@
992 #define SQLITE_IOCAP_SEQUENTIAL 0x00000400
993 #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800
994 #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000
995 #define SQLITE_IOCAP_IMMUTABLE 0x00002000
996 #define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000
997 #define SQLITE_IOCAP_SUBPAGE_READ 0x00008000
998
999 /*
1000 ** CAPI3REF: File Locking Levels
1001 **
1002 ** SQLite uses one of these integer values as the second
@@ -1128,10 +1139,11 @@
1139 ** <li> [SQLITE_IOCAP_SEQUENTIAL]
1140 ** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN]
1141 ** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE]
1142 ** <li> [SQLITE_IOCAP_IMMUTABLE]
1143 ** <li> [SQLITE_IOCAP_BATCH_ATOMIC]
1144 ** <li> [SQLITE_IOCAP_SUBPAGE_READ]
1145 ** </ul>
1146 **
1147 ** The SQLITE_IOCAP_ATOMIC property means that all writes of
1148 ** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values
1149 ** mean that writes of blocks that are nnn bytes in size and
@@ -1405,10 +1417,15 @@
1417 ** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This
1418 ** opcode causes the xFileControl method to swap the file handle with the one
1419 ** pointed to by the pArg argument. This capability is used during testing
1420 ** and only needs to be supported when SQLITE_TEST is defined.
1421 **
1422 ** <li>[[SQLITE_FCNTL_NULL_IO]]
1423 ** The [SQLITE_FCNTL_NULL_IO] opcode sets the low-level file descriptor
1424 ** or file handle for the [sqlite3_file] object such that it will no longer
1425 ** read or write to the database file.
1426 **
1427 ** <li>[[SQLITE_FCNTL_WAL_BLOCK]]
1428 ** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might
1429 ** be advantageous to block on the next WAL lock if the lock is not immediately
1430 ** available. The WAL subsystem issues this signal during rare
1431 ** circumstances in order to fix a problem with priority inversion.
@@ -1558,10 +1575,11 @@
1575 #define SQLITE_FCNTL_RESERVE_BYTES 38
1576 #define SQLITE_FCNTL_CKPT_START 39
1577 #define SQLITE_FCNTL_EXTERNAL_READER 40
1578 #define SQLITE_FCNTL_CKSM_FILE 41
1579 #define SQLITE_FCNTL_RESET_CACHE 42
1580 #define SQLITE_FCNTL_NULL_IO 43
1581
1582 /* deprecated names */
1583 #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
1584 #define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE
1585 #define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO
@@ -2936,14 +2954,18 @@
2954 **
2955 ** ^These functions return the number of rows modified, inserted or
2956 ** deleted by the most recently completed INSERT, UPDATE or DELETE
2957 ** statement on the database connection specified by the only parameter.
2958 ** The two functions are identical except for the type of the return value
2959 ** and that if the number of rows modified by the most recent INSERT, UPDATE,
2960 ** or DELETE is greater than the maximum value supported by type "int", then
2961 ** the return value of sqlite3_changes() is undefined. ^Executing any other
2962 ** type of SQL statement does not modify the value returned by these functions.
2963 ** For the purposes of this interface, a CREATE TABLE AS SELECT statement
2964 ** does not count as an INSERT, UPDATE or DELETE statement and hence the rows
2965 ** added to the new table by the CREATE TABLE AS SELECT statement are not
2966 ** counted.
2967 **
2968 ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are
2969 ** considered - auxiliary changes caused by [CREATE TRIGGER | triggers],
2970 ** [foreign key actions] or [REPLACE] constraint resolution are not counted.
2971 **
@@ -4499,15 +4521,26 @@
4521 **
4522 ** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt>
4523 ** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler
4524 ** to return an error (error code SQLITE_ERROR) if the statement uses
4525 ** any virtual tables.
4526 **
4527 ** [[SQLITE_PREPARE_DONT_LOG]] <dt>SQLITE_PREPARE_DONT_LOG</dt>
4528 ** <dd>The SQLITE_PREPARE_DONT_LOG flag prevents SQL compiler
4529 ** errors from being sent to the error log defined by
4530 ** [SQLITE_CONFIG_LOG]. This can be used, for example, to do test
4531 ** compiles to see if some SQL syntax is well-formed, without generating
4532 ** messages on the global error log when it is not. If the test compile
4533 ** fails, the sqlite3_prepare_v3() call returns the same error indications
4534 ** with or without this flag; it just omits the call to [sqlite3_log()] that
4535 ** logs the error.
4536 ** </dl>
4537 */
4538 #define SQLITE_PREPARE_PERSISTENT 0x01
4539 #define SQLITE_PREPARE_NORMALIZE 0x02
4540 #define SQLITE_PREPARE_NO_VTAB 0x04
4541 #define SQLITE_PREPARE_DONT_LOG 0x10
4542
4543 /*
4544 ** CAPI3REF: Compiling An SQL Statement
4545 ** KEYWORDS: {SQL statement compiler}
4546 ** METHOD: sqlite3
@@ -11194,11 +11227,11 @@
11227 #endif
11228
11229 #if 0
11230 } /* End of the 'extern "C"' block */
11231 #endif
11232 /* #endif for SQLITE3_H will be added by mksqlite3.tcl */
11233
11234 /******** Begin file sqlite3rtree.h *********/
11235 /*
11236 ** 2010 August 30
11237 **
@@ -13445,17 +13478,32 @@
13478 ** This is used to access token iToken of phrase hit iIdx within the
13479 ** current row. If iIdx is less than zero or greater than or equal to the
13480 ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise,
13481 ** output variable (*ppToken) is set to point to a buffer containing the
13482 ** matching document token, and (*pnToken) to the size of that buffer in
13483 ** bytes.
 
 
13484 **
13485 ** The output text is not a copy of the document text that was tokenized.
13486 ** It is the output of the tokenizer module. For tokendata=1 tables, this
13487 ** includes any embedded 0x00 and trailing data.
13488 **
13489 ** This API may be slow in some cases if the token identified by parameters
13490 ** iIdx and iToken matched a prefix token in the query. In most cases, the
13491 ** first call to this API for each prefix token in the query is forced
13492 ** to scan the portion of the full-text index that matches the prefix
13493 ** token to collect the extra data required by this API. If the prefix
13494 ** token matches a large number of token instances in the document set,
13495 ** this may be a performance problem.
13496 **
13497 ** If the user knows in advance that a query may use this API for a
13498 ** prefix token, FTS5 may be configured to collect all required data as part
13499 ** of the initial querying of the full-text index, avoiding the second scan
13500 ** entirely. This also causes prefix queries that do not use this API to
13501 ** run more slowly and use more memory. FTS5 may be configured in this way
13502 ** either on a per-table basis using the [FTS5 insttoken | 'insttoken']
13503 ** option, or on a per-query basis using the
13504 ** [fts5_insttoken | fts5_insttoken()] user function.
13505 **
13506 ** This API can be quite slow if used with an FTS5 table created with the
13507 ** "detail=none" or "detail=column" option.
13508 **
13509 ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale)
@@ -13886,10 +13934,11 @@
13934 #endif
13935
13936 #endif /* _FTS5_H */
13937
13938 /******** End of fts5.h *********/
13939 #endif /* SQLITE3_H */
13940
13941 /************** End of sqlite3.h *********************************************/
13942 /************** Continuing where we left off in sqliteInt.h ******************/
13943
13944 /*
@@ -13931,10 +13980,11 @@
13980 ** to count the size: 2^31-1 or 2147483647.
13981 */
13982 #ifndef SQLITE_MAX_LENGTH
13983 # define SQLITE_MAX_LENGTH 1000000000
13984 #endif
13985 #define SQLITE_MIN_LENGTH 30 /* Minimum value for the length limit */
13986
13987 /*
13988 ** This is the maximum number of
13989 **
13990 ** * Columns in a table
@@ -13996,13 +14046,17 @@
14046 # define SQLITE_MAX_VDBE_OP 250000000
14047 #endif
14048
14049 /*
14050 ** The maximum number of arguments to an SQL function.
14051 **
14052 ** This value has a hard upper limit of 32767 due to storage
14053 ** constraints (it needs to fit inside a i16). We keep it
14054 ** lower than that to prevent abuse.
14055 */
14056 #ifndef SQLITE_MAX_FUNCTION_ARG
14057 # define SQLITE_MAX_FUNCTION_ARG 1000
14058 #endif
14059
14060 /*
14061 ** The suggested maximum number of in-memory pages to use for
14062 ** the main database table and for temporary tables.
@@ -16000,10 +16054,26 @@
16054 #define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */
16055 #define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */
16056 #define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */
16057 #define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */
16058
16059 #define isWalMode(x) ((x)==PAGER_JOURNALMODE_WAL)
16060
16061 /*
16062 ** The argument to this macro is a file descriptor (type sqlite3_file*).
16063 ** Return 0 if it is not open, or non-zero (but not 1) if it is.
16064 **
16065 ** This is so that expressions can be written as:
16066 **
16067 ** if( isOpen(pPager->jfd) ){ ...
16068 **
16069 ** instead of
16070 **
16071 ** if( pPager->jfd->pMethods ){ ...
16072 */
16073 #define isOpen(pFd) ((pFd)->pMethods!=0)
16074
16075 /*
16076 ** Flags that make up the mask passed to sqlite3PagerGet().
16077 */
16078 #define PAGER_GET_NOCONTENT 0x01 /* Do not load data from disk */
16079 #define PAGER_GET_READONLY 0x02 /* Read-only page is acceptable */
@@ -17025,11 +17095,11 @@
17095
17096 /*
17097 ** Additional non-public SQLITE_PREPARE_* flags
17098 */
17099 #define SQLITE_PREPARE_SAVESQL 0x80 /* Preserve SQL text */
17100 #define SQLITE_PREPARE_MASK 0x1f /* Mask of public flags */
17101
17102 /*
17103 ** Prototypes for the VDBE interface. See comments on the implementation
17104 ** for a description of what each of these routines does.
17105 */
@@ -17740,51 +17810,15 @@
17810 struct FuncDefHash {
17811 FuncDef *a[SQLITE_FUNC_HASH_SZ]; /* Hash table for functions */
17812 };
17813 #define SQLITE_FUNC_HASH(C,L) (((C)+(L))%SQLITE_FUNC_HASH_SZ)
17814
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17815 /*
17816 ** typedef for the authorization callback function.
17817 */
17818 typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*,
17819 const char*);
 
 
 
 
 
17820
17821 #ifndef SQLITE_OMIT_DEPRECATED
17822 /* This is an extra SQLITE_TRACE macro that indicates "legacy" tracing
17823 ** in the style of sqlite3_trace()
17824 */
@@ -17941,13 +17975,10 @@
17975 sqlite3 *pUnlockConnection; /* Connection to watch for unlock */
17976 void *pUnlockArg; /* Argument to xUnlockNotify */
17977 void (*xUnlockNotify)(void **, int); /* Unlock notify callback */
17978 sqlite3 *pNextBlocked; /* Next in list of all blocked connections */
17979 #endif
 
 
 
17980 };
17981
17982 /*
17983 ** A macro to discover the encoding of a database.
17984 */
@@ -18102,11 +18133,11 @@
18133 **
18134 ** The u.pHash field is used by the global built-ins. The u.pDestructor
18135 ** field is used by per-connection app-def functions.
18136 */
18137 struct FuncDef {
18138 i16 nArg; /* Number of arguments. -1 means unlimited */
18139 u32 funcFlags; /* Some combination of SQLITE_FUNC_* */
18140 void *pUserData; /* User data parameter */
18141 FuncDef *pNext; /* Next function with same name */
18142 void (*xSFunc)(sqlite3_context*,int,sqlite3_value**); /* func or agg-step */
18143 void (*xFinalize)(sqlite3_context*); /* Agg finalizer */
@@ -22850,13 +22881,10 @@
22881 "UNLINK_AFTER_CLOSE",
22882 #endif
22883 #ifdef SQLITE_UNTESTABLE
22884 "UNTESTABLE",
22885 #endif
 
 
 
22886 #ifdef SQLITE_USE_ALLOCA
22887 "USE_ALLOCA",
22888 #endif
22889 #ifdef SQLITE_USE_FCNTL_TRACE
22890 "USE_FCNTL_TRACE",
@@ -23700,11 +23728,11 @@
23728 Vdbe *pVdbe; /* The VM that owns this context */
23729 int iOp; /* Instruction number of OP_Function */
23730 int isError; /* Error code returned by the function. */
23731 u8 enc; /* Encoding to use for results */
23732 u8 skipFlag; /* Skip accumulator loading if true */
23733 u16 argc; /* Number of arguments */
23734 sqlite3_value *argv[1]; /* Argument set */
23735 };
23736
23737 /* A bitfield type for use inside of structures. Always follow with :N where
23738 ** N is the number of bits.
@@ -23847,10 +23875,11 @@
23875 UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */
23876 int iNewReg; /* Register for new.* values */
23877 int iBlobWrite; /* Value returned by preupdate_blobwrite() */
23878 i64 iKey1; /* First key value passed to hook */
23879 i64 iKey2; /* Second key value passed to hook */
23880 Mem oldipk; /* Memory cell holding "old" IPK value */
23881 Mem *aNew; /* Array of new.* values */
23882 Table *pTab; /* Schema object being updated */
23883 Index *pPk; /* PK index if pTab is WITHOUT ROWID */
23884 sqlite3_value **apDflt; /* Array of default values, if required */
23885 };
@@ -32296,10 +32325,11 @@
32325 && (ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0)
32326 ){
32327 pExpr = pExpr->pLeft;
32328 }
32329 if( pExpr==0 ) return;
32330 if( ExprHasProperty(pExpr, EP_FromDDL) ) return;
32331 db->errByteOffset = pExpr->w.iOfst;
32332 }
32333
32334 /*
32335 ** Enlarge the memory allocation on a StrAccum object so that it is
@@ -33025,11 +33055,11 @@
33055 }
33056 if( pItem->fg.isCte ){
33057 sqlite3_str_appendf(&x, " CteUse=0x%p", pItem->u2.pCteUse);
33058 }
33059 if( pItem->fg.isOn || (pItem->fg.isUsing==0 && pItem->u3.pOn!=0) ){
33060 sqlite3_str_appendf(&x, " isOn");
33061 }
33062 if( pItem->fg.isTabFunc ) sqlite3_str_appendf(&x, " isTabFunc");
33063 if( pItem->fg.isCorrelated ) sqlite3_str_appendf(&x, " isCorrelated");
33064 if( pItem->fg.isMaterialized ) sqlite3_str_appendf(&x, " isMaterialized");
33065 if( pItem->fg.viaCoroutine ) sqlite3_str_appendf(&x, " viaCoroutine");
@@ -34109,10 +34139,14 @@
34139 **
34140 ** This routines are given external linkage so that they will always be
34141 ** accessible to the debugging, and to avoid warnings about unused
34142 ** functions. But these routines only exist in debugging builds, so they
34143 ** do not contaminate the interface.
34144 **
34145 ** See Also:
34146 **
34147 ** sqlite3ShowWhereTerm() in where.c
34148 */
34149 SQLITE_PRIVATE void sqlite3ShowExpr(const Expr *p){ sqlite3TreeViewExpr(0,p,0); }
34150 SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList *p){ sqlite3TreeViewExprList(0,p,0,0);}
34151 SQLITE_PRIVATE void sqlite3ShowIdList(const IdList *p){ sqlite3TreeViewIdList(0,p,0,0); }
34152 SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList *p){ sqlite3TreeViewSrcList(0,p); }
@@ -35685,12 +35719,12 @@
35719 int esign = 1; /* sign of exponent */
35720 int e = 0; /* exponent */
35721 int eValid = 1; /* True exponent is either not used or is well-formed */
35722 int nDigit = 0; /* Number of digits processed */
35723 int eType = 1; /* 1: pure integer, 2+: fractional -1 or less: bad UTF16 */
35724 u64 s2; /* round-tripped significand */
35725 double rr[2];
 
35726
35727 assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE );
35728 *pResult = 0.0; /* Default return value, in case of an error */
35729 if( length==0 ) return 0;
35730
@@ -35789,25 +35823,36 @@
35823
35824 /* adjust exponent by d, and update sign */
35825 e = (e*esign) + d;
35826
35827 /* Try to adjust the exponent to make it smaller */
35828 while( e>0 && s<((LARGEST_UINT64-0x7ff)/10) ){
35829 s *= 10;
35830 e--;
35831 }
35832 while( e<0 && (s%10)==0 ){
35833 s /= 10;
35834 e++;
35835 }
35836
35837 rr[0] = (double)s;
35838 assert( sizeof(s2)==sizeof(rr[0]) );
35839 #ifdef SQLITE_DEBUG
35840 rr[1] = 18446744073709549568.0;
35841 memcpy(&s2, &rr[1], sizeof(s2));
35842 assert( s2==0x43efffffffffffffLL );
35843 #endif
35844 /* Largest double that can be safely converted to u64
35845 ** vvvvvvvvvvvvvvvvvvvvvv */
35846 if( rr[0]<=18446744073709549568.0 ){
35847 s2 = (u64)rr[0];
35848 rr[1] = s>=s2 ? (double)(s - s2) : -(double)(s2 - s);
35849 }else{
35850 rr[1] = 0.0;
35851 }
35852 assert( rr[1]<=1.0e-10*rr[0] ); /* Equal only when rr[0]==0.0 */
35853
35854 if( e>0 ){
35855 while( e>=100 ){
35856 e -= 100;
35857 dekkerMul2(rr, 1.0e+100, -1.5902891109759918046e+83);
35858 }
@@ -38674,11 +38719,11 @@
38719 # define F_SETLKW 7
38720 # endif
38721 # endif
38722 #else /* !SQLITE_WASI */
38723 # ifndef HAVE_FCHMOD
38724 # define HAVE_FCHMOD 1
38725 # endif
38726 #endif /* SQLITE_WASI */
38727
38728 #ifdef SQLITE_WASI
38729 # define osGetpid(X) (pid_t)1
@@ -42448,10 +42493,15 @@
42493 int rc = osIoctl(pFile->h, F2FS_IOC_ABORT_VOLATILE_WRITE);
42494 return rc ? SQLITE_IOERR_ROLLBACK_ATOMIC : SQLITE_OK;
42495 }
42496 #endif /* __linux__ && SQLITE_ENABLE_BATCH_ATOMIC_WRITE */
42497
42498 case SQLITE_FCNTL_NULL_IO: {
42499 osClose(pFile->h);
42500 pFile->h = -1;
42501 return SQLITE_OK;
42502 }
42503 case SQLITE_FCNTL_LOCKSTATE: {
42504 *(int*)pArg = pFile->eFileLock;
42505 return SQLITE_OK;
42506 }
42507 case SQLITE_FCNTL_LAST_ERRNO: {
@@ -42589,10 +42639,11 @@
42639
42640 /* Set the POWERSAFE_OVERWRITE flag if requested. */
42641 if( pFd->ctrlFlags & UNIXFILE_PSOW ){
42642 pFd->deviceCharacteristics |= SQLITE_IOCAP_POWERSAFE_OVERWRITE;
42643 }
42644 pFd->deviceCharacteristics |= SQLITE_IOCAP_SUBPAGE_READ;
42645
42646 pFd->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE;
42647 }
42648 }
42649 #else
@@ -50328,10 +50379,15 @@
50379 OSTRACE(("FCNTL oldFile=%p, newFile=%p, rc=SQLITE_OK\n",
50380 hOldFile, pFile->h));
50381 return SQLITE_OK;
50382 }
50383 #endif
50384 case SQLITE_FCNTL_NULL_IO: {
50385 (void)osCloseHandle(pFile->h);
50386 pFile->h = NULL;
50387 return SQLITE_OK;
50388 }
50389 case SQLITE_FCNTL_TEMPFILENAME: {
50390 char *zTFile = 0;
50391 int rc = winGetTempname(pFile->pVfs, &zTFile);
50392 if( rc==SQLITE_OK ){
50393 *(char**)pArg = zTFile;
@@ -50389,11 +50445,11 @@
50445 /*
50446 ** Return a vector of device characteristics.
50447 */
50448 static int winDeviceCharacteristics(sqlite3_file *id){
50449 winFile *p = (winFile*)id;
50450 return SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN | SQLITE_IOCAP_SUBPAGE_READ |
50451 ((p->ctrlFlags & WINFILE_PSOW)?SQLITE_IOCAP_POWERSAFE_OVERWRITE:0);
50452 }
50453
50454 /*
50455 ** Windows will only let you create file view mappings
@@ -51777,11 +51833,11 @@
51833 */
51834 char *zTmpname = 0; /* For temporary filename, if necessary. */
51835
51836 int rc = SQLITE_OK; /* Function Return Code */
51837 #if !defined(NDEBUG) || SQLITE_OS_WINCE
51838 int eType = flags&0x0FFF00; /* Type of file to open */
51839 #endif
51840
51841 int isExclusive = (flags & SQLITE_OPEN_EXCLUSIVE);
51842 int isDelete = (flags & SQLITE_OPEN_DELETEONCLOSE);
51843 int isCreate = (flags & SQLITE_OPEN_CREATE);
@@ -57978,43 +58034,37 @@
58034 # define USEFETCH(x) ((x)->bUseFetch)
58035 #else
58036 # define USEFETCH(x) 0
58037 #endif
58038
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58039 #ifdef SQLITE_DIRECT_OVERFLOW_READ
58040 /*
58041 ** Return true if page pgno can be read directly from the database file
58042 ** by the b-tree layer. This is the case if:
58043 **
58044 ** (1) the database file is open
58045 ** (2) the VFS for the database is able to do unaligned sub-page reads
58046 ** (3) there are no dirty pages in the cache, and
58047 ** (4) the desired page is not currently in the wal file.
58048 */
58049 SQLITE_PRIVATE int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){
58050 assert( pPager!=0 );
58051 assert( pPager->fd!=0 );
58052 if( pPager->fd->pMethods==0 ) return 0; /* Case (1) */
58053 if( sqlite3PCacheIsDirty(pPager->pPCache) ) return 0; /* Failed (3) */
58054 #ifndef SQLITE_OMIT_WAL
58055 if( pPager->pWal ){
58056 u32 iRead = 0;
58057 (void)sqlite3WalFindFrame(pPager->pWal, pgno, &iRead);
58058 return iRead==0; /* Condition (4) */
58059 }
58060 #endif
58061 assert( pPager->fd->pMethods->xDeviceCharacteristics!=0 );
58062 if( (pPager->fd->pMethods->xDeviceCharacteristics(pPager->fd)
58063 & SQLITE_IOCAP_SUBPAGE_READ)==0 ){
58064 return 0; /* Case (2) */
58065 }
58066 return 1;
58067 }
58068 #endif
58069
58070 #ifndef SQLITE_OMIT_WAL
@@ -59269,11 +59319,11 @@
59319 rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags);
59320 }
59321 }
59322 pPager->journalOff = 0;
59323 }else if( pPager->journalMode==PAGER_JOURNALMODE_PERSIST
59324 || (pPager->exclusiveMode && pPager->journalMode<PAGER_JOURNALMODE_WAL)
59325 ){
59326 rc = zeroJournalHdr(pPager, hasSuper||pPager->tempFile);
59327 pPager->journalOff = 0;
59328 }else{
59329 /* This branch may be executed with Pager.journalMode==MEMORY if
@@ -67979,15 +68029,11 @@
68029 ** so it takes care to hold an exclusive lock on the corresponding
68030 ** WAL_READ_LOCK() while changing values.
68031 */
68032 static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){
68033 volatile WalCkptInfo *pInfo; /* Checkpoint information in wal-index */
 
 
 
68034 int rc = SQLITE_OK; /* Return code */
 
68035 #ifdef SQLITE_ENABLE_SETLK_TIMEOUT
68036 int nBlockTmout = 0;
68037 #endif
68038
68039 assert( pWal->readLock<0 ); /* Not currently locked */
@@ -68089,145 +68135,151 @@
68135
68136 assert( pWal->nWiData>0 );
68137 assert( pWal->apWiData[0]!=0 );
68138 pInfo = walCkptInfo(pWal);
68139 SEH_INJECT_FAULT;
68140 {
68141 u32 mxReadMark; /* Largest aReadMark[] value */
68142 int mxI; /* Index of largest aReadMark[] value */
68143 int i; /* Loop counter */
68144 u32 mxFrame; /* Wal frame to lock to */
68145 if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame
68146 #ifdef SQLITE_ENABLE_SNAPSHOT
68147 && ((pWal->bGetSnapshot==0 && pWal->pSnapshot==0) || pWal->hdr.mxFrame==0)
68148 #endif
68149 ){
68150 /* The WAL has been completely backfilled (or it is empty).
68151 ** and can be safely ignored.
68152 */
68153 rc = walLockShared(pWal, WAL_READ_LOCK(0));
68154 walShmBarrier(pWal);
68155 if( rc==SQLITE_OK ){
68156 if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr,sizeof(WalIndexHdr)) ){
68157 /* It is not safe to allow the reader to continue here if frames
68158 ** may have been appended to the log before READ_LOCK(0) was obtained.
68159 ** When holding READ_LOCK(0), the reader ignores the entire log file,
68160 ** which implies that the database file contains a trustworthy
68161 ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from
68162 ** happening, this is usually correct.
68163 **
68164 ** However, if frames have been appended to the log (or if the log
68165 ** is wrapped and written for that matter) before the READ_LOCK(0)
68166 ** is obtained, that is not necessarily true. A checkpointer may
68167 ** have started to backfill the appended frames but crashed before
68168 ** it finished. Leaving a corrupt image in the database file.
68169 */
68170 walUnlockShared(pWal, WAL_READ_LOCK(0));
68171 return WAL_RETRY;
68172 }
68173 pWal->readLock = 0;
68174 return SQLITE_OK;
68175 }else if( rc!=SQLITE_BUSY ){
68176 return rc;
68177 }
68178 }
68179
68180 /* If we get this far, it means that the reader will want to use
68181 ** the WAL to get at content from recent commits. The job now is
68182 ** to select one of the aReadMark[] entries that is closest to
68183 ** but not exceeding pWal->hdr.mxFrame and lock that entry.
68184 */
68185 mxReadMark = 0;
68186 mxI = 0;
68187 mxFrame = pWal->hdr.mxFrame;
68188 #ifdef SQLITE_ENABLE_SNAPSHOT
68189 if( pWal->pSnapshot && pWal->pSnapshot->mxFrame<mxFrame ){
68190 mxFrame = pWal->pSnapshot->mxFrame;
68191 }
68192 #endif
68193 for(i=1; i<WAL_NREADER; i++){
68194 u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT;
68195 if( mxReadMark<=thisMark && thisMark<=mxFrame ){
68196 assert( thisMark!=READMARK_NOT_USED );
68197 mxReadMark = thisMark;
68198 mxI = i;
68199 }
68200 }
68201 if( (pWal->readOnly & WAL_SHM_RDONLY)==0
68202 && (mxReadMark<mxFrame || mxI==0)
68203 ){
68204 for(i=1; i<WAL_NREADER; i++){
68205 rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
68206 if( rc==SQLITE_OK ){
68207 AtomicStore(pInfo->aReadMark+i,mxFrame);
68208 mxReadMark = mxFrame;
68209 mxI = i;
68210 walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1);
68211 break;
68212 }else if( rc!=SQLITE_BUSY ){
68213 return rc;
68214 }
68215 }
68216 }
68217 if( mxI==0 ){
68218 assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 );
68219 return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT;
68220 }
68221
68222 (void)walEnableBlockingMs(pWal, nBlockTmout);
68223 rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
68224 walDisableBlocking(pWal);
68225 if( rc ){
68226 #ifdef SQLITE_ENABLE_SETLK_TIMEOUT
68227 if( rc==SQLITE_BUSY_TIMEOUT ){
68228 *pCnt |= WAL_RETRY_BLOCKED_MASK;
68229 }
68230 #else
68231 assert( rc!=SQLITE_BUSY_TIMEOUT );
68232 #endif
68233 assert((rc&0xFF)!=SQLITE_BUSY||rc==SQLITE_BUSY||rc==SQLITE_BUSY_TIMEOUT);
68234 return (rc&0xFF)==SQLITE_BUSY ? WAL_RETRY : rc;
68235 }
68236 /* Now that the read-lock has been obtained, check that neither the
68237 ** value in the aReadMark[] array or the contents of the wal-index
68238 ** header have changed.
68239 **
68240 ** It is necessary to check that the wal-index header did not change
68241 ** between the time it was read and when the shared-lock was obtained
68242 ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility
68243 ** that the log file may have been wrapped by a writer, or that frames
68244 ** that occur later in the log than pWal->hdr.mxFrame may have been
68245 ** copied into the database by a checkpointer. If either of these things
68246 ** happened, then reading the database with the current value of
68247 ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry
68248 ** instead.
68249 **
68250 ** Before checking that the live wal-index header has not changed
68251 ** since it was read, set Wal.minFrame to the first frame in the wal
68252 ** file that has not yet been checkpointed. This client will not need
68253 ** to read any frames earlier than minFrame from the wal file - they
68254 ** can be safely read directly from the database file.
68255 **
68256 ** Because a ShmBarrier() call is made between taking the copy of
68257 ** nBackfill and checking that the wal-header in shared-memory still
68258 ** matches the one cached in pWal->hdr, it is guaranteed that the
68259 ** checkpointer that set nBackfill was not working with a wal-index
68260 ** header newer than that cached in pWal->hdr. If it were, that could
68261 ** cause a problem. The checkpointer could omit to checkpoint
68262 ** a version of page X that lies before pWal->minFrame (call that version
68263 ** A) on the basis that there is a newer version (version B) of the same
68264 ** page later in the wal file. But if version B happens to like past
68265 ** frame pWal->hdr.mxFrame - then the client would incorrectly assume
68266 ** that it can read version A from the database file. However, since
68267 ** we can guarantee that the checkpointer that set nBackfill could not
68268 ** see any pages past pWal->hdr.mxFrame, this problem does not come up.
68269 */
68270 pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT;
68271 walShmBarrier(pWal);
68272 if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark
68273 || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr))
68274 ){
68275 walUnlockShared(pWal, WAL_READ_LOCK(mxI));
68276 return WAL_RETRY;
68277 }else{
68278 assert( mxReadMark<=pWal->hdr.mxFrame );
68279 pWal->readLock = (i16)mxI;
68280 }
68281 }
68282 return rc;
68283 }
68284
68285 #ifdef SQLITE_ENABLE_SNAPSHOT
@@ -87103,10 +87155,11 @@
87155 **
87156 ** All other fields of Mem can safely remain uninitialized for now. They
87157 ** will be initialized before use.
87158 */
87159 static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){
87160 assert( db!=0 );
87161 if( N>0 ){
87162 do{
87163 p->flags = flags;
87164 p->db = db;
87165 p->szMalloc = 0;
@@ -87128,10 +87181,11 @@
87181 */
87182 static void releaseMemArray(Mem *p, int N){
87183 if( p && N ){
87184 Mem *pEnd = &p[N];
87185 sqlite3 *db = p->db;
87186 assert( db!=0 );
87187 if( db->pnBytesFreed ){
87188 do{
87189 if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc);
87190 }while( (++p)<pEnd );
87191 return;
@@ -87608,10 +87662,11 @@
87662 assert( p!=0 );
87663 assert( p->nOp>0 );
87664 assert( pParse!=0 );
87665 assert( p->eVdbeState==VDBE_INIT_STATE );
87666 assert( pParse==p->pParse );
87667 assert( pParse->db==p->db );
87668 p->pVList = pParse->pVList;
87669 pParse->pVList = 0;
87670 db = p->db;
87671 assert( db->mallocFailed==0 );
87672 nVar = pParse->nVar;
@@ -90488,10 +90543,11 @@
90543 db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2);
90544 db->pPreUpdate = 0;
90545 sqlite3DbFree(db, preupdate.aRecord);
90546 vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pUnpacked);
90547 vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pNewUnpacked);
90548 sqlite3VdbeMemRelease(&preupdate.oldipk);
90549 if( preupdate.aNew ){
90550 int i;
90551 for(i=0; i<pCsr->nField; i++){
90552 sqlite3VdbeMemRelease(&preupdate.aNew[i]);
90553 }
@@ -91844,11 +91900,11 @@
91900 **
91901 ** sqlite3_column_int()
91902 ** sqlite3_column_int64()
91903 ** sqlite3_column_text()
91904 ** sqlite3_column_text16()
91905 ** sqlite3_column_double()
91906 ** sqlite3_column_bytes()
91907 ** sqlite3_column_bytes16()
91908 ** sqlite3_column_blob()
91909 */
91910 static void columnMallocFailure(sqlite3_stmt *pStmt)
@@ -92706,64 +92762,68 @@
92762 if( iIdx>=p->pCsr->nField || iIdx<0 ){
92763 rc = SQLITE_RANGE;
92764 goto preupdate_old_out;
92765 }
92766
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92767 if( iIdx==p->pTab->iPKey ){
92768 *ppValue = pMem = &p->oldipk;
92769 sqlite3VdbeMemSetInt64(pMem, p->iKey1);
92770 }else{
92771
92772 /* If the old.* record has not yet been loaded into memory, do so now. */
92773 if( p->pUnpacked==0 ){
92774 u32 nRec;
92775 u8 *aRec;
92776
92777 assert( p->pCsr->eCurType==CURTYPE_BTREE );
92778 nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor);
92779 aRec = sqlite3DbMallocRaw(db, nRec);
92780 if( !aRec ) goto preupdate_old_out;
92781 rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec);
92782 if( rc==SQLITE_OK ){
92783 p->pUnpacked = vdbeUnpackRecord(&p->keyinfo, nRec, aRec);
92784 if( !p->pUnpacked ) rc = SQLITE_NOMEM;
92785 }
92786 if( rc!=SQLITE_OK ){
92787 sqlite3DbFree(db, aRec);
92788 goto preupdate_old_out;
92789 }
92790 p->aRecord = aRec;
92791 }
92792
92793 pMem = *ppValue = &p->pUnpacked->aMem[iIdx];
92794 if( iIdx>=p->pUnpacked->nField ){
92795 /* This occurs when the table has been extended using ALTER TABLE
92796 ** ADD COLUMN. The value to return is the default value of the column. */
92797 Column *pCol = &p->pTab->aCol[iIdx];
92798 if( pCol->iDflt>0 ){
92799 if( p->apDflt==0 ){
92800 int nByte = sizeof(sqlite3_value*)*p->pTab->nCol;
92801 p->apDflt = (sqlite3_value**)sqlite3DbMallocZero(db, nByte);
92802 if( p->apDflt==0 ) goto preupdate_old_out;
92803 }
92804 if( p->apDflt[iIdx]==0 ){
92805 sqlite3_value *pVal = 0;
92806 Expr *pDflt;
92807 assert( p->pTab!=0 && IsOrdinaryTable(p->pTab) );
92808 pDflt = p->pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr;
92809 rc = sqlite3ValueFromExpr(db, pDflt, ENC(db), pCol->affinity, &pVal);
92810 if( rc==SQLITE_OK && pVal==0 ){
92811 rc = SQLITE_CORRUPT_BKPT;
92812 }
92813 p->apDflt[iIdx] = pVal;
92814 }
92815 *ppValue = p->apDflt[iIdx];
92816 }else{
92817 *ppValue = (sqlite3_value *)columnNullValue();
92818 }
92819 }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){
92820 if( pMem->flags & (MEM_Int|MEM_IntReal) ){
92821 testcase( pMem->flags & MEM_Int );
92822 testcase( pMem->flags & MEM_IntReal );
92823 sqlite3VdbeMemRealify(pMem);
92824 }
92825 }
92826 }
92827
92828 preupdate_old_out:
92829 sqlite3Error(db, rc);
@@ -97913,13 +97973,15 @@
97973 0, pCx->uc.pCursor);
97974 pCx->isTable = 1;
97975 }
97976 }
97977 pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED);
97978 assert( p->apCsr[pOp->p1]==pCx );
97979 if( rc ){
97980 assert( !sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) );
97981 sqlite3BtreeClose(pCx->ub.pBtx);
97982 p->apCsr[pOp->p1] = 0; /* Not required; helps with static analysis */
97983 }else{
97984 assert( sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) );
97985 }
97986 }
97987 }
@@ -109845,11 +109907,11 @@
109907 p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight);
109908 }
109909 p5 = binaryCompareP5(pLeft, pRight, jumpIfNull);
109910 addr = sqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1,
109911 (void*)p4, P4_COLLSEQ);
109912 sqlite3VdbeChangeP5(pParse->pVdbe, (u16)p5);
109913 return addr;
109914 }
109915
109916 /*
109917 ** Return true if expression pExpr is a vector, or false otherwise.
@@ -112012,11 +112074,11 @@
112074 **
112075 ** (4) If pSrc is the right operand of a LEFT JOIN, then...
112076 ** (4a) pExpr must come from an ON clause..
112077 ** (4b) and specifically the ON clause associated with the LEFT JOIN.
112078 **
112079 ** (5) If pSrc is the right operand of a LEFT JOIN or the left
112080 ** operand of a RIGHT JOIN, then pExpr must be from the WHERE
112081 ** clause, not an ON clause.
112082 **
112083 ** (6) Either:
112084 **
@@ -115546,35 +115608,41 @@
115608 **
115609 ** Additionally, if pExpr is a simple SQL value and the value is the
115610 ** same as that currently bound to variable pVar, non-zero is returned.
115611 ** Otherwise, if the values are not the same or if pExpr is not a simple
115612 ** SQL value, zero is returned.
115613 **
115614 ** If the SQLITE_EnableQPSG flag is set on the database connection, then
115615 ** this routine always returns false.
115616 */
115617 static SQLITE_NOINLINE int exprCompareVariable(
115618 const Parse *pParse,
115619 const Expr *pVar,
115620 const Expr *pExpr
115621 ){
115622 int res = 2;
115623 int iVar;
115624 sqlite3_value *pL, *pR = 0;
115625
115626 if( pExpr->op==TK_VARIABLE && pVar->iColumn==pExpr->iColumn ){
115627 return 0;
115628 }
115629 if( (pParse->db->flags & SQLITE_EnableQPSG)!=0 ) return 2;
115630 sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR);
115631 if( pR ){
115632 iVar = pVar->iColumn;
115633 sqlite3VdbeSetVarmask(pParse->pVdbe, iVar);
115634 pL = sqlite3VdbeGetBoundValue(pParse->pReprepare, iVar, SQLITE_AFF_BLOB);
115635 if( pL ){
115636 if( sqlite3_value_type(pL)==SQLITE_TEXT ){
115637 sqlite3_value_text(pL); /* Make sure the encoding is UTF-8 */
115638 }
115639 res = sqlite3MemCompare(pL, pR, 0) ? 2 : 0;
115640 }
115641 sqlite3ValueFree(pR);
115642 sqlite3ValueFree(pL);
115643 }
 
115644 return res;
115645 }
115646
115647 /*
115648 ** Do a deep comparison of two expression trees. Return 0 if the two
@@ -115596,16 +115664,14 @@
115664 ** can be sure the expressions are the same. In the places where
115665 ** this routine is used, it does not hurt to get an extra 2 - that
115666 ** just might result in some slightly slower code. But returning
115667 ** an incorrect 0 or 1 could lead to a malfunction.
115668 **
115669 ** If pParse is not NULL and SQLITE_EnableQPSG is off then TK_VARIABLE
115670 ** terms in pA with bindings in pParse->pReprepare can be matched against
115671 ** literals in pB. The pParse->pVdbe->expmask bitmask is updated for
115672 ** each variable referenced.
 
 
115673 */
115674 SQLITE_PRIVATE int sqlite3ExprCompare(
115675 const Parse *pParse,
115676 const Expr *pA,
115677 const Expr *pB,
@@ -115613,12 +115679,12 @@
115679 ){
115680 u32 combinedFlags;
115681 if( pA==0 || pB==0 ){
115682 return pB==pA ? 0 : 2;
115683 }
115684 if( pParse && pA->op==TK_VARIABLE ){
115685 return exprCompareVariable(pParse, pA, pB);
115686 }
115687 combinedFlags = pA->flags | pB->flags;
115688 if( combinedFlags & EP_IntValue ){
115689 if( (pA->flags&pB->flags&EP_IntValue)!=0 && pA->u.iValue==pB->u.iValue ){
115690 return 0;
@@ -115808,23 +115874,75 @@
115874 return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1);
115875 }
115876 }
115877 return 0;
115878 }
115879
115880 /*
115881 ** Return true if the boolean value of the expression is always either
115882 ** FALSE or NULL.
115883 */
115884 static int sqlite3ExprIsNotTrue(Expr *pExpr){
115885 int v;
115886 if( pExpr->op==TK_NULL ) return 1;
115887 if( pExpr->op==TK_TRUEFALSE && sqlite3ExprTruthValue(pExpr)==0 ) return 1;
115888 v = 1;
115889 if( sqlite3ExprIsInteger(pExpr, &v, 0) && v==0 ) return 1;
115890 return 0;
115891 }
115892
115893 /*
115894 ** Return true if the expression is one of the following:
115895 **
115896 ** CASE WHEN x THEN y END
115897 ** CASE WHEN x THEN y ELSE NULL END
115898 ** CASE WHEN x THEN y ELSE false END
115899 ** iif(x,y)
115900 ** iif(x,y,NULL)
115901 ** iif(x,y,false)
115902 */
115903 static int sqlite3ExprIsIIF(sqlite3 *db, const Expr *pExpr){
115904 ExprList *pList;
115905 if( pExpr->op==TK_FUNCTION ){
115906 const char *z = pExpr->u.zToken;
115907 FuncDef *pDef;
115908 if( (z[0]!='i' && z[0]!='I') ) return 0;
115909 if( pExpr->x.pList==0 ) return 0;
115910 pDef = sqlite3FindFunction(db, z, pExpr->x.pList->nExpr, ENC(db), 0);
115911 #ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION
115912 if( pDef==0 ) return 0;
115913 #else
115914 if( NEVER(pDef==0) ) return 0;
115915 #endif
115916 if( (pDef->funcFlags & SQLITE_FUNC_INLINE)==0 ) return 0;
115917 if( SQLITE_PTR_TO_INT(pDef->pUserData)!=INLINEFUNC_iif ) return 0;
115918 }else if( pExpr->op==TK_CASE ){
115919 if( pExpr->pLeft!=0 ) return 0;
115920 }else{
115921 return 0;
115922 }
115923 pList = pExpr->x.pList;
115924 assert( pList!=0 );
115925 if( pList->nExpr==2 ) return 1;
115926 if( pList->nExpr==3 && sqlite3ExprIsNotTrue(pList->a[2].pExpr) ) return 1;
115927 return 0;
115928 }
115929
115930 /*
115931 ** Return true if we can prove the pE2 will always be true if pE1 is
115932 ** true. Return false if we cannot complete the proof or if pE2 might
115933 ** be false. Examples:
115934 **
115935 ** pE1: x==5 pE2: x==5 Result: true
115936 ** pE1: x>0 pE2: x==5 Result: false
115937 ** pE1: x=21 pE2: x=21 OR y=43 Result: true
115938 ** pE1: x!=123 pE2: x IS NOT NULL Result: true
115939 ** pE1: x!=?1 pE2: x IS NOT NULL Result: true
115940 ** pE1: x IS NULL pE2: x IS NOT NULL Result: false
115941 ** pE1: x IS ?2 pE2: x IS NOT NULL Result: false
115942 ** pE1: iif(x,y) pE2: x Result: true
115943 ** PE1: iif(x,y,0) pE2: x Result: true
115944 **
115945 ** When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has
115946 ** Expr.iTable<0 then assume a table number given by iTab.
115947 **
115948 ** If pParse is not NULL, then the values of bound variables in pE1 are
@@ -115854,10 +115972,13 @@
115972 if( pE2->op==TK_NOTNULL
115973 && exprImpliesNotNull(pParse, pE1, pE2->pLeft, iTab, 0)
115974 ){
115975 return 1;
115976 }
115977 if( sqlite3ExprIsIIF(pParse->db, pE1) ){
115978 return sqlite3ExprImpliesExpr(pParse,pE1->x.pList->a[0].pExpr,pE2,iTab);
115979 }
115980 return 0;
115981 }
115982
115983 /* This is a helper function to impliesNotNullRow(). In this routine,
115984 ** set pWalker->eCode to one only if *both* of the input expressions
@@ -121265,19 +121386,10 @@
121386 rc = sqlite3Init(db, &zErrDyn);
121387 }
121388 sqlite3BtreeLeaveAll(db);
121389 assert( zErrDyn==0 || rc!=SQLITE_OK );
121390 }
 
 
 
 
 
 
 
 
 
121391 if( rc ){
121392 if( ALWAYS(!REOPEN_AS_MEMDB(db)) ){
121393 int iDb = db->nDb - 1;
121394 assert( iDb>=2 );
121395 if( db->aDb[iDb].pBt ){
@@ -121771,15 +121883,11 @@
121883 sqlite3 *db = pParse->db; /* Database handle */
121884 char *zDb = db->aDb[iDb].zDbSName; /* Schema name of attached database */
121885 int rc; /* Auth callback return code */
121886
121887 if( db->init.busy ) return SQLITE_OK;
121888 rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext);
 
 
 
 
121889 if( rc==SQLITE_DENY ){
121890 char *z = sqlite3_mprintf("%s.%s", zTab, zCol);
121891 if( db->nDb>2 || iDb!=0 ) z = sqlite3_mprintf("%s.%z", zDb, z);
121892 sqlite3ErrorMsg(pParse, "access to %z is prohibited", z);
121893 pParse->rc = SQLITE_AUTH;
@@ -121882,15 +121990,11 @@
121990 testcase( zArg1==0 );
121991 testcase( zArg2==0 );
121992 testcase( zArg3==0 );
121993 testcase( pParse->zAuthContext==0 );
121994
121995 rc = db->xAuth(db->pAuthArg,code,zArg1,zArg2,zArg3,pParse->zAuthContext);
 
 
 
 
121996 if( rc==SQLITE_DENY ){
121997 sqlite3ErrorMsg(pParse, "not authorized");
121998 pParse->rc = SQLITE_AUTH;
121999 }else if( rc!=SQLITE_OK && rc!=SQLITE_IGNORE ){
122000 rc = SQLITE_DENY;
@@ -122119,21 +122223,10 @@
122223 sqlite3VdbeJumpHere(v, addrRewind);
122224 }
122225 }
122226 sqlite3VdbeAddOp0(v, OP_Halt);
122227
 
 
 
 
 
 
 
 
 
 
 
122228 /* The cookie mask contains one bit for each database file open.
122229 ** (Bit 0 is for main, bit 1 is for temp, and so forth.) Bits are
122230 ** set for each database that is used. Generate code to start a
122231 ** transaction on each used database and to verify the schema cookie
122232 ** on each used database.
@@ -122258,20 +122351,10 @@
122351 sqlite3DbFree(db, zSql);
122352 memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ);
122353 pParse->nested--;
122354 }
122355
 
 
 
 
 
 
 
 
 
 
122356 /*
122357 ** Locate the in-memory structure that describes a particular database
122358 ** table given the name of that table and (optionally) the name of the
122359 ** database containing the table. Return NULL if not found.
122360 **
@@ -122286,17 +122369,10 @@
122369 Table *p = 0;
122370 int i;
122371
122372 /* All mutexes are required for schema access. Make sure we hold them. */
122373 assert( zDatabase!=0 || sqlite3BtreeHoldsAllMutexes(db) );
 
 
 
 
 
 
 
122374 if( zDatabase ){
122375 for(i=0; i<db->nDb; i++){
122376 if( sqlite3StrICmp(zDatabase, db->aDb[i].zDbSName)==0 ) break;
122377 }
122378 if( i>=db->nDb ){
@@ -125951,13 +126027,10 @@
126027
126028 assert( pTab!=0 );
126029 if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0
126030 && db->init.busy==0
126031 && pTblName!=0
 
 
 
126032 ){
126033 sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName);
126034 goto exit_create_index;
126035 }
126036 #ifndef SQLITE_OMIT_VIEW
@@ -129661,20 +129734,19 @@
129734 const unsigned char *z;
129735 const unsigned char *z2;
129736 int len;
129737 int p0type;
129738 i64 p1, p2;
 
129739
129740 assert( argc==3 || argc==2 );
129741 if( sqlite3_value_type(argv[1])==SQLITE_NULL
129742 || (argc==3 && sqlite3_value_type(argv[2])==SQLITE_NULL)
129743 ){
129744 return;
129745 }
129746 p0type = sqlite3_value_type(argv[0]);
129747 p1 = sqlite3_value_int64(argv[1]);
129748 if( p0type==SQLITE_BLOB ){
129749 len = sqlite3_value_bytes(argv[0]);
129750 z = sqlite3_value_blob(argv[0]);
129751 if( z==0 ) return;
129752 assert( len==sqlite3_value_bytes(argv[0]) );
@@ -129695,36 +129767,36 @@
129767 ** from 2009-02-02 for compatibility of applications that exploited the
129768 ** old buggy behavior. */
129769 if( p1==0 ) p1 = 1; /* <rdar://problem/6778339> */
129770 #endif
129771 if( argc==3 ){
129772 p2 = sqlite3_value_int64(argv[2]);
 
 
 
 
129773 }else{
129774 p2 = sqlite3_context_db_handle(context)->aLimit[SQLITE_LIMIT_LENGTH];
129775 }
129776 if( p1<0 ){
129777 p1 += len;
129778 if( p1<0 ){
129779 if( p2<0 ){
129780 p2 = 0;
129781 }else{
129782 p2 += p1;
129783 }
129784 p1 = 0;
129785 }
129786 }else if( p1>0 ){
129787 p1--;
129788 }else if( p2>0 ){
129789 p2--;
129790 }
129791 if( p2<0 ){
129792 if( p2<-p1 ){
129793 p2 = p1;
129794 }else{
129795 p2 = -p2;
129796 }
129797 p1 -= p2;
 
 
 
 
129798 }
129799 assert( p1>=0 && p2>=0 );
129800 if( p0type!=SQLITE_BLOB ){
129801 while( *z && p1 ){
129802 SQLITE_SKIP_UTF8(z);
@@ -129734,13 +129806,15 @@
129806 SQLITE_SKIP_UTF8(z2);
129807 }
129808 sqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT,
129809 SQLITE_UTF8);
129810 }else{
129811 if( p1>=len ){
129812 p1 = p2 = 0;
129813 }else if( p2>len-p1 ){
129814 p2 = len-p1;
129815 assert( p2>0 );
129816 }
129817 sqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT);
129818 }
129819 }
129820
@@ -129747,17 +129821,17 @@
129821 /*
129822 ** Implementation of the round() function
129823 */
129824 #ifndef SQLITE_OMIT_FLOATING_POINT
129825 static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
129826 i64 n = 0;
129827 double r;
129828 char *zBuf;
129829 assert( argc==1 || argc==2 );
129830 if( argc==2 ){
129831 if( SQLITE_NULL==sqlite3_value_type(argv[1]) ) return;
129832 n = sqlite3_value_int64(argv[1]);
129833 if( n>30 ) n = 30;
129834 if( n<0 ) n = 0;
129835 }
129836 if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
129837 r = sqlite3_value_double(argv[0]);
@@ -129768,11 +129842,11 @@
129842 if( r<-4503599627370496.0 || r>+4503599627370496.0 ){
129843 /* The value has no fractional part so there is nothing to round */
129844 }else if( n==0 ){
129845 r = (double)((sqlite_int64)(r+(r<0?-0.5:+0.5)));
129846 }else{
129847 zBuf = sqlite3_mprintf("%!.*f",(int)n,r);
129848 if( zBuf==0 ){
129849 sqlite3_result_error_nomem(context);
129850 return;
129851 }
129852 sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8);
@@ -131985,13 +132059,10 @@
132059 #endif
132060 #ifndef SQLITE_OMIT_LOAD_EXTENSION
132061 SFUNCTION(load_extension, 1, 0, 0, loadExt ),
132062 SFUNCTION(load_extension, 2, 0, 0, loadExt ),
132063 #endif
 
 
 
132064 #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS
132065 DFUNCTION(sqlite_compileoption_used,1, 0, 0, compileoptionusedFunc ),
132066 DFUNCTION(sqlite_compileoption_get, 1, 0, 0, compileoptiongetFunc ),
132067 #endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */
132068 INLINE_FUNC(unlikely, 1, INLINEFUNC_unlikely, SQLITE_FUNC_UNLIKELY),
@@ -132124,11 +132195,14 @@
132195 MFUNCTION(degrees, 1, radToDeg, math1Func ),
132196 MFUNCTION(pi, 0, 0, piFunc ),
132197 #endif /* SQLITE_ENABLE_MATH_FUNCTIONS */
132198 FUNCTION(sign, 1, 0, 0, signFunc ),
132199 INLINE_FUNC(coalesce, -1, INLINEFUNC_coalesce, 0 ),
132200 INLINE_FUNC(iif, 2, INLINEFUNC_iif, 0 ),
132201 INLINE_FUNC(iif, 3, INLINEFUNC_iif, 0 ),
132202 INLINE_FUNC(if, 2, INLINEFUNC_iif, 0 ),
132203 INLINE_FUNC(if, 3, INLINEFUNC_iif, 0 ),
132204 };
132205 #ifndef SQLITE_OMIT_ALTERTABLE
132206 sqlite3AlterFunctions();
132207 #endif
132208 sqlite3WindowFunctions();
@@ -140637,16 +140711,10 @@
140711 if( db->autoCommit==0 ){
140712 /* Foreign key support may not be enabled or disabled while not
140713 ** in auto-commit mode. */
140714 mask &= ~(SQLITE_ForeignKeys);
140715 }
 
 
 
 
 
 
140716
140717 if( sqlite3GetBoolean(zRight, 0) ){
140718 if( (mask & SQLITE_WriteSchema)==0
140719 || (db->flags & SQLITE_Defensive)==0
140720 ){
@@ -140778,11 +140846,12 @@
140846 pTab = sqliteHashData(k);
140847 if( pTab->nCol==0 ){
140848 char *zSql = sqlite3MPrintf(db, "SELECT*FROM\"%w\"", pTab->zName);
140849 if( zSql ){
140850 sqlite3_stmt *pDummy = 0;
140851 (void)sqlite3_prepare_v3(db, zSql, -1, SQLITE_PREPARE_DONT_LOG,
140852 &pDummy, 0);
140853 (void)sqlite3_finalize(pDummy);
140854 sqlite3DbFree(db, zSql);
140855 }
140856 if( db->mallocFailed ){
140857 sqlite3ErrorMsg(db->pParse, "out of memory");
@@ -141259,11 +141328,11 @@
141328 sqlite3VdbeAddOp3(v, OP_Null, 0, 8, 8+cnt);
141329 sqlite3ClearTempRegCache(pParse);
141330
141331 /* Do the b-tree integrity checks */
141332 sqlite3VdbeAddOp4(v, OP_IntegrityCk, 1, cnt, 8, (char*)aRoot,P4_INTARRAY);
141333 sqlite3VdbeChangeP5(v, (u16)i);
141334 addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v);
141335 sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0,
141336 sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName),
141337 P4_DYNAMIC);
141338 sqlite3VdbeAddOp3(v, OP_Concat, 2, 3, 3);
@@ -142879,18 +142948,11 @@
142948 encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3;
142949 if( encoding==0 ) encoding = SQLITE_UTF8;
142950 #else
142951 encoding = SQLITE_UTF8;
142952 #endif
142953 sqlite3SetTextEncoding(db, encoding);
 
 
 
 
 
 
 
142954 }else{
142955 /* If opening an attached database, the encoding much match ENC(db) */
142956 if( (meta[BTREE_TEXT_ENCODING-1] & 3)!=ENC(db) ){
142957 sqlite3SetString(pzErrMsg, db, "attached databases must use the same"
142958 " text encoding as main database");
@@ -147584,36 +147646,36 @@
147646 return pExpr;
147647 }
147648 if( pSubst->isOuterJoin ){
147649 ExprSetProperty(pNew, EP_CanBeNull);
147650 }
147651 if( pNew->op==TK_TRUEFALSE ){
147652 pNew->u.iValue = sqlite3ExprTruthValue(pNew);
147653 pNew->op = TK_INTEGER;
147654 ExprSetProperty(pNew, EP_IntValue);
147655 }
147656
147657 /* Ensure that the expression now has an implicit collation sequence,
147658 ** just as it did when it was a column of a view or sub-query. */
147659 {
147660 CollSeq *pNat = sqlite3ExprCollSeq(pSubst->pParse, pNew);
147661 CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse,
147662 pSubst->pCList->a[iColumn].pExpr
147663 );
147664 if( pNat!=pColl || (pNew->op!=TK_COLUMN && pNew->op!=TK_COLLATE) ){
147665 pNew = sqlite3ExprAddCollateString(pSubst->pParse, pNew,
147666 (pColl ? pColl->zName : "BINARY")
147667 );
147668 }
147669 }
147670 ExprClearProperty(pNew, EP_Collate);
147671 if( ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) ){
147672 sqlite3SetJoinExpr(pNew, pExpr->w.iJoin,
147673 pExpr->flags & (EP_OuterON|EP_InnerON));
147674 }
147675 sqlite3ExprDelete(db, pExpr);
147676 pExpr = pNew;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147677 }
147678 }
147679 }else{
147680 if( pExpr->op==TK_IF_NULL_ROW && pExpr->iTable==pSubst->iTable ){
147681 pExpr->iTable = pSubst->iNewTable;
@@ -148346,20 +148408,20 @@
148408 }
148409
148410 /* Transfer the FROM clause terms from the subquery into the
148411 ** outer query.
148412 */
148413 iNewParent = pSubSrc->a[0].iCursor;
148414 for(i=0; i<nSubSrc; i++){
148415 SrcItem *pItem = &pSrc->a[i+iFrom];
148416 assert( pItem->fg.isTabFunc==0 );
148417 assert( pItem->fg.isSubquery
148418 || pItem->fg.fixedSchema
148419 || pItem->u4.zDatabase==0 );
148420 if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing);
148421 *pItem = pSubSrc->a[i];
148422 pItem->fg.jointype |= ltorj;
 
148423 memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i]));
148424 }
148425 pSrc->a[iFrom].fg.jointype &= JT_LTORJ;
148426 pSrc->a[iFrom].fg.jointype |= jointype | ltorj;
148427
@@ -148395,10 +148457,11 @@
148457 pSub->pOrderBy = 0;
148458 }
148459 pWhere = pSub->pWhere;
148460 pSub->pWhere = 0;
148461 if( isOuterJoin>0 ){
148462 assert( pSubSrc->nSrc==1 );
148463 sqlite3SetJoinExpr(pWhere, iNewParent, EP_OuterON);
148464 }
148465 if( pWhere ){
148466 if( pParent->pWhere ){
148467 pParent->pWhere = sqlite3PExpr(pParse, TK_AND, pWhere, pParent->pWhere);
@@ -150498,11 +150561,11 @@
150561 }
150562 sqlite3ReleaseTempReg(pParse, regSubtype);
150563 }
150564 sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
150565 sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
150566 sqlite3VdbeChangeP5(v, (u16)nArg);
150567 sqlite3VdbeAddOp2(v, OP_Next, pF->iOBTab, iTop+1); VdbeCoverage(v);
150568 sqlite3VdbeJumpHere(v, iTop);
150569 sqlite3ReleaseTempRange(pParse, regAgg, nArg);
150570 }
150571 sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i),
@@ -150661,11 +150724,11 @@
150724 sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0,
150725 (char *)pColl, P4_COLLSEQ);
150726 }
150727 sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
150728 sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
150729 sqlite3VdbeChangeP5(v, (u16)nArg);
150730 sqlite3ReleaseTempRange(pParse, regAgg, nArg);
150731 }
150732 if( addrNext ){
150733 sqlite3VdbeResolveLabel(v, addrNext);
150734 }
@@ -151494,11 +151557,11 @@
151557 sqlite3TreeViewSelect(0, p, 0);
151558 }
151559 #endif
151560 assert( pSubq->pSelect && (pSub->selFlags & SF_PushDown)!=0 );
151561 }else{
151562 TREETRACE(0x4000,pParse,p,("WHERE-clause push-down not possible\n"));
151563 }
151564
151565 /* Convert unused result columns of the subquery into simple NULL
151566 ** expressions, to avoid unneeded searching and computation.
151567 ** tag-select-0440
@@ -154055,11 +154118,11 @@
154118 /* Set the P5 operand of the OP_Program instruction to non-zero if
154119 ** recursive invocation of this trigger program is disallowed. Recursive
154120 ** invocation is disallowed if (a) the sub-program is really a trigger,
154121 ** not a foreign key action, and (b) the flag to enable recursive triggers
154122 ** is clear. */
154123 sqlite3VdbeChangeP5(v, (u16)bRecursive);
154124 }
154125 }
154126
154127 /*
154128 ** This is called to code the required FOR EACH ROW triggers for an operation
@@ -158268,13 +158331,21 @@
158331 SQLITE_PRIVATE int sqlite3WhereExplainBloomFilter(
158332 const Parse *pParse, /* Parse context */
158333 const WhereInfo *pWInfo, /* WHERE clause */
158334 const WhereLevel *pLevel /* Bloom filter on this level */
158335 );
158336 SQLITE_PRIVATE void sqlite3WhereAddExplainText(
158337 Parse *pParse, /* Parse context */
158338 int addr,
158339 SrcList *pTabList, /* Table list this loop refers to */
158340 WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */
158341 u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */
158342 );
158343 #else
158344 # define sqlite3WhereExplainOneScan(u,v,w,x) 0
158345 # define sqlite3WhereExplainBloomFilter(u,v,w) 0
158346 # define sqlite3WhereAddExplainText(u,v,w,x,y)
158347 #endif /* SQLITE_OMIT_EXPLAIN */
158348 #ifdef SQLITE_ENABLE_STMT_SCANSTATUS
158349 SQLITE_PRIVATE void sqlite3WhereAddScanStatus(
158350 Vdbe *v, /* Vdbe to add scanstatus entry to */
158351 SrcList *pSrclist, /* FROM clause pLvl reads data from */
@@ -158472,42 +158543,42 @@
158543 }
158544 sqlite3_str_append(pStr, ")", 1);
158545 }
158546
158547 /*
158548 ** This function sets the P4 value of an existing OP_Explain opcode to
158549 ** text describing the loop in pLevel. If the OP_Explain opcode already has
158550 ** a P4 value, it is freed before it is overwritten.
 
 
 
 
158551 */
158552 SQLITE_PRIVATE void sqlite3WhereAddExplainText(
158553 Parse *pParse, /* Parse context */
158554 int addr, /* Address of OP_Explain opcode */
158555 SrcList *pTabList, /* Table list this loop refers to */
158556 WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */
158557 u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */
158558 ){
 
158559 #if !defined(SQLITE_DEBUG)
158560 if( sqlite3ParseToplevel(pParse)->explain==2 || IS_STMT_SCANSTATUS(pParse->db) )
158561 #endif
158562 {
158563 VdbeOp *pOp = sqlite3VdbeGetOp(pParse->pVdbe, addr);
158564
158565 SrcItem *pItem = &pTabList->a[pLevel->iFrom];
 
158566 sqlite3 *db = pParse->db; /* Database handle */
158567 int isSearch; /* True for a SEARCH. False for SCAN. */
158568 WhereLoop *pLoop; /* The controlling WhereLoop object */
158569 u32 flags; /* Flags that describe this loop */
158570 #if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN)
158571 char *zMsg; /* Text to add to EQP output */
158572 #endif
158573 StrAccum str; /* EQP output string */
158574 char zBuf[100]; /* Initial space for EQP output string */
158575
158576 if( db->mallocFailed ) return;
158577
158578 pLoop = pLevel->pWLoop;
158579 flags = pLoop->wsFlags;
 
158580
158581 isSearch = (flags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0
158582 || ((flags&WHERE_VIRTUALTABLE)==0 && (pLoop->u.btree.nEq>0))
158583 || (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX));
158584
@@ -158527,11 +158598,11 @@
158598 }
158599 }else if( flags & WHERE_PARTIALIDX ){
158600 zFmt = "AUTOMATIC PARTIAL COVERING INDEX";
158601 }else if( flags & WHERE_AUTO_INDEX ){
158602 zFmt = "AUTOMATIC COVERING INDEX";
158603 }else if( flags & (WHERE_IDX_ONLY|WHERE_EXPRIDX) ){
158604 zFmt = "COVERING INDEX %s";
158605 }else{
158606 zFmt = "INDEX %s";
158607 }
158608 if( zFmt ){
@@ -158579,15 +158650,54 @@
158650 sqlite3LogEstToInt(pLoop->nOut));
158651 }else{
158652 sqlite3_str_append(&str, " (~1 row)", 9);
158653 }
158654 #endif
158655 #if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN)
158656 zMsg = sqlite3StrAccumFinish(&str);
158657 sqlite3ExplainBreakpoint("",zMsg);
158658 #endif
158659
158660 assert( pOp->opcode==OP_Explain );
158661 assert( pOp->p4type==P4_DYNAMIC || pOp->p4.z==0 );
158662 sqlite3DbFree(db, pOp->p4.z);
158663 pOp->p4type = P4_DYNAMIC;
158664 pOp->p4.z = sqlite3StrAccumFinish(&str);
158665 }
158666 }
158667
158668
158669 /*
158670 ** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN
158671 ** command, or if stmt_scanstatus_v2() stats are enabled, or if SQLITE_DEBUG
158672 ** was defined at compile-time. If it is not a no-op, a single OP_Explain
158673 ** opcode is added to the output to describe the table scan strategy in pLevel.
158674 **
158675 ** If an OP_Explain opcode is added to the VM, its address is returned.
158676 ** Otherwise, if no OP_Explain is coded, zero is returned.
158677 */
158678 SQLITE_PRIVATE int sqlite3WhereExplainOneScan(
158679 Parse *pParse, /* Parse context */
158680 SrcList *pTabList, /* Table list this loop refers to */
158681 WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */
158682 u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */
158683 ){
158684 int ret = 0;
158685 #if !defined(SQLITE_DEBUG)
158686 if( sqlite3ParseToplevel(pParse)->explain==2 || IS_STMT_SCANSTATUS(pParse->db) )
158687 #endif
158688 {
158689 if( (pLevel->pWLoop->wsFlags & WHERE_MULTI_OR)==0
158690 && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0
158691 ){
158692 Vdbe *v = pParse->pVdbe;
158693 int addr = sqlite3VdbeCurrentAddr(v);
158694 ret = sqlite3VdbeAddOp3(
158695 v, OP_Explain, addr, pParse->addrExplain, pLevel->pWLoop->rRun
158696 );
158697 sqlite3WhereAddExplainText(pParse, addr, pTabList, pLevel, wctrlFlags);
158698 }
158699 }
158700 return ret;
158701 }
158702
158703 /*
@@ -158682,13 +158792,14 @@
158792 if( wsFlags & WHERE_INDEXED ){
158793 sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur);
158794 }
158795 }else{
158796 int addr;
158797 VdbeOp *pOp;
158798 assert( pSrclist->a[pLvl->iFrom].fg.isSubquery );
158799 addr = pSrclist->a[pLvl->iFrom].u4.pSubq->addrFillSub;
158800 pOp = sqlite3VdbeGetOp(v, addr-1);
158801 assert( sqlite3VdbeDb(v)->mallocFailed || pOp->opcode==OP_InitCoroutine );
158802 assert( sqlite3VdbeDb(v)->mallocFailed || pOp->p2>addr );
158803 sqlite3VdbeScanStatusRange(v, addrExplain, addr, pOp->p2-1);
158804 }
158805 }
@@ -158937,10 +159048,11 @@
159048 if( pOrigLhs ){
159049 sqlite3ExprListDelete(db, pOrigLhs);
159050 pNew->pLeft->x.pList = pLhs;
159051 }
159052 pSelect->pEList = pRhs;
159053 pSelect->selId = ++pParse->nSelect; /* Req'd for SubrtnSig validity */
159054 if( pLhs && pLhs->nExpr==1 ){
159055 /* Take care here not to generate a TK_VECTOR containing only a
159056 ** single value. Since the parser never creates such a vector, some
159057 ** of the subroutines do not handle this case. */
159058 Expr *p = pLhs->a[0].pExpr;
@@ -164009,11 +164121,11 @@
164121 || pTerm->pExpr->w.iJoin != pSrc->iCursor
164122 ){
164123 return 0;
164124 }
164125 if( (pSrc->fg.jointype & (JT_LEFT|JT_RIGHT))!=0
164126 && NEVER(ExprHasProperty(pTerm->pExpr, EP_InnerON))
164127 ){
164128 return 0;
164129 }
164130 return 1;
164131 }
@@ -165502,11 +165614,11 @@
165614 return rc;
165615 }
165616 #endif /* SQLITE_ENABLE_STAT4 */
165617
165618
165619 #if defined(WHERETRACE_ENABLED) || defined(SQLITE_DEBUG)
165620 /*
165621 ** Print the content of a WhereTerm object
165622 */
165623 SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){
165624 if( pTerm==0 ){
@@ -165545,10 +165657,13 @@
165657 sqlite3DebugPrintf(" iParent=%d", pTerm->iParent);
165658 }
165659 sqlite3DebugPrintf("\n");
165660 sqlite3TreeViewExpr(0, pTerm->pExpr, 0);
165661 }
165662 }
165663 SQLITE_PRIVATE void sqlite3ShowWhereTerm(WhereTerm *pTerm){
165664 sqlite3WhereTermPrint(pTerm, 0);
165665 }
165666 #endif
165667
165668 #ifdef WHERETRACE_ENABLED
165669 /*
@@ -166731,11 +166846,10 @@
166846 pParse = pWC->pWInfo->pParse;
166847 while( pWhere->op==TK_AND ){
166848 if( !whereUsablePartialIndex(iTab,jointype,pWC,pWhere->pLeft) ) return 0;
166849 pWhere = pWhere->pRight;
166850 }
 
166851 for(i=0, pTerm=pWC->a; i<pWC->nTerm; i++, pTerm++){
166852 Expr *pExpr;
166853 pExpr = pTerm->pExpr;
166854 if( (!ExprHasProperty(pExpr, EP_OuterON) || pExpr->w.iJoin==iTab)
166855 && ((jointype & JT_OUTER)==0 || ExprHasProperty(pExpr, EP_OuterON))
@@ -169392,11 +169506,11 @@
169506 break;
169507 }
169508 }
169509 if( hasRightJoin
169510 && ExprHasProperty(pTerm->pExpr, EP_InnerON)
169511 && NEVER(pTerm->pExpr->w.iJoin==pItem->iCursor)
169512 ){
169513 break; /* restriction (5) */
169514 }
169515 }
169516 if( pTerm<pEnd ) continue;
@@ -170312,10 +170426,11 @@
170426 int pc,
170427 VdbeOp *pOp
170428 ){
170429 if( (db->flags & SQLITE_VdbeAddopTrace)==0 ) return;
170430 sqlite3VdbePrintOp(0, pc, pOp);
170431 sqlite3ShowWhereTerm(0); /* So compiler won't complain about unused func */
170432 }
170433 #endif
170434
170435 /*
170436 ** Generate the end of the WHERE loop. See comments on
@@ -170611,18 +170726,32 @@
170726 x = sqlite3TableColumnToIndex(pIdx, x);
170727 if( x>=0 ){
170728 pOp->p2 = x;
170729 pOp->p1 = pLevel->iIdxCur;
170730 OpcodeRewriteTrace(db, k, pOp);
170731 }else if( pLoop->wsFlags & (WHERE_IDX_ONLY|WHERE_EXPRIDX) ){
 
 
 
 
170732 if( pLoop->wsFlags & WHERE_IDX_ONLY ){
170733 /* An error. pLoop is supposed to be a covering index loop,
170734 ** and yet the VM code refers to a column of the table that
170735 ** is not part of the index. */
170736 sqlite3ErrorMsg(pParse, "internal query planner error");
170737 pParse->rc = SQLITE_INTERNAL;
170738 }else{
170739 /* The WHERE_EXPRIDX flag is set by the planner when it is likely
170740 ** that pLoop is a covering index loop, but it is not possible
170741 ** to be 100% sure. In this case, any OP_Explain opcode
170742 ** corresponding to this loop describes the index as a "COVERING
170743 ** INDEX". But, pOp proves that pLoop is not actually a covering
170744 ** index loop. So clear the WHERE_EXPRIDX flag and rewrite the
170745 ** text that accompanies the OP_Explain opcode, if any. */
170746 pLoop->wsFlags &= ~WHERE_EXPRIDX;
170747 sqlite3WhereAddExplainText(pParse,
170748 pLevel->addrBody-1,
170749 pTabList,
170750 pLevel,
170751 pWInfo->wctrlFlags
170752 );
170753 }
170754 }
170755 }else if( pOp->opcode==OP_Rowid ){
170756 pOp->p1 = pLevel->iIdxCur;
170757 pOp->opcode = OP_IdxRowid;
@@ -172326,10 +172455,11 @@
172455 for(pWin=pMWin; pWin; pWin=pWin->pNextWin){
172456 FuncDef *pFunc = pWin->pWFunc;
172457 int regArg;
172458 int nArg = pWin->bExprArgs ? 0 : windowArgCount(pWin);
172459 int i;
172460 int addrIf = 0;
172461
172462 assert( bInverse==0 || pWin->eStart!=TK_UNBOUNDED );
172463
172464 /* All OVER clauses in the same window function aggregate step must
172465 ** be the same. */
@@ -172341,10 +172471,22 @@
172471 }else{
172472 sqlite3VdbeAddOp3(v, OP_Column, pMWin->iEphCsr, pWin->iArgCol+i, reg+i);
172473 }
172474 }
172475 regArg = reg;
172476
172477 if( pWin->pFilter ){
172478 int regTmp;
172479 assert( ExprUseXList(pWin->pOwner) );
172480 assert( pWin->bExprArgs || !nArg ||nArg==pWin->pOwner->x.pList->nExpr );
172481 assert( pWin->bExprArgs || nArg ||pWin->pOwner->x.pList==0 );
172482 regTmp = sqlite3GetTempReg(pParse);
172483 sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol+nArg,regTmp);
172484 addrIf = sqlite3VdbeAddOp3(v, OP_IfNot, regTmp, 0, 1);
172485 VdbeCoverage(v);
172486 sqlite3ReleaseTempReg(pParse, regTmp);
172487 }
172488
172489 if( pMWin->regStartRowid==0
172490 && (pFunc->funcFlags & SQLITE_FUNC_MINMAX)
172491 && (pWin->eStart!=TK_UNBOUNDED)
172492 ){
@@ -172361,29 +172503,17 @@
172503 sqlite3VdbeAddOp1(v, OP_Delete, pWin->csrApp);
172504 sqlite3VdbeJumpHere(v, sqlite3VdbeCurrentAddr(v)-2);
172505 }
172506 sqlite3VdbeJumpHere(v, addrIsNull);
172507 }else if( pWin->regApp ){
172508 assert( pWin->pFilter==0 );
172509 assert( pFunc->zName==nth_valueName
172510 || pFunc->zName==first_valueName
172511 );
172512 assert( bInverse==0 || bInverse==1 );
172513 sqlite3VdbeAddOp2(v, OP_AddImm, pWin->regApp+1-bInverse, 1);
172514 }else if( pFunc->xSFunc!=noopStepFunc ){
 
 
 
 
 
 
 
 
 
 
 
 
 
172515 if( pWin->bExprArgs ){
172516 int iOp = sqlite3VdbeCurrentAddr(v);
172517 int iEnd;
172518
172519 assert( ExprUseXList(pWin->pOwner) );
@@ -172406,16 +172536,17 @@
172536 sqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ);
172537 }
172538 sqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep,
172539 bInverse, regArg, pWin->regAccum);
172540 sqlite3VdbeAppendP4(v, pFunc, P4_FUNCDEF);
172541 sqlite3VdbeChangeP5(v, (u16)nArg);
172542 if( pWin->bExprArgs ){
172543 sqlite3ReleaseTempRange(pParse, regArg, nArg);
172544 }
 
172545 }
172546
172547 if( addrIf ) sqlite3VdbeJumpHere(v, addrIf);
172548 }
172549 }
172550
172551 /*
172552 ** Values that may be passed as the second argument to windowCodeOp().
@@ -173837,10 +173968,17 @@
173968 ** Then the "b" IdList records the list "a,b,c".
173969 */
173970 struct TrigEvent { int a; IdList * b; };
173971
173972 struct FrameBound { int eType; Expr *pExpr; };
173973
173974 /*
173975 ** Generate a syntax error
173976 */
173977 static void parserSyntaxError(Parse *pParse, Token *p){
173978 sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", p);
173979 }
173980
173981 /*
173982 ** Disable lookaside memory allocation for objects that might be
173983 ** shared across database connections.
173984 */
@@ -177730,11 +177868,15 @@
177868 }
177869 break;
177870 case 84: /* cmd ::= select */
177871 {
177872 SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0, 0};
177873 if( (pParse->db->mDbFlags & DBFLAG_EncodingFixed)!=0
177874 || sqlite3ReadSchema(pParse)==SQLITE_OK
177875 ){
177876 sqlite3Select(pParse, yymsp[0].minor.yy555, &dest);
177877 }
177878 sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy555);
177879 }
177880 break;
177881 case 85: /* select ::= WITH wqlist selectnowith */
177882 {yymsp[-2].minor.yy555 = attachWithToSelect(pParse,yymsp[0].minor.yy555,yymsp[-1].minor.yy59);}
@@ -178201,11 +178343,11 @@
178343 ** that look like this: #1 #2 ... These terms refer to registers
178344 ** in the virtual machine. #N is the N-th register. */
178345 Token t = yymsp[0].minor.yy0; /*A-overwrites-X*/
178346 assert( t.n>=2 );
178347 if( pParse->nested==0 ){
178348 parserSyntaxError(pParse, &t);
178349 yymsp[0].minor.yy454 = 0;
178350 }else{
178351 yymsp[0].minor.yy454 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0);
178352 if( yymsp[0].minor.yy454 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy454->iTable);
178353 }
@@ -179049,11 +179191,11 @@
179191 #define TOKEN yyminor
179192 /************ Begin %syntax_error code ****************************************/
179193
179194 UNUSED_PARAMETER(yymajor); /* Silence some compiler warnings */
179195 if( TOKEN.z[0] ){
179196 parserSyntaxError(pParse, &TOKEN);
179197 }else{
179198 sqlite3ErrorMsg(pParse, "incomplete input");
179199 }
179200 /************ End %syntax_error code ******************************************/
179201 sqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument variable */
@@ -180540,11 +180682,13 @@
180682 }
180683 if( pParse->zErrMsg || (pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE) ){
180684 if( pParse->zErrMsg==0 ){
180685 pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc));
180686 }
180687 if( (pParse->prepFlags & SQLITE_PREPARE_DONT_LOG)==0 ){
180688 sqlite3_log(pParse->rc, "%s in \"%s\"", pParse->zErrMsg, pParse->zTail);
180689 }
180690 nErr++;
180691 }
180692 pParse->zTail = zSql;
180693 #ifndef SQLITE_OMIT_VIRTUALTABLE
180694 sqlite3_free(pParse->apVtabLock);
@@ -182513,14 +182657,10 @@
182657 #endif
182658
182659 sqlite3Error(db, SQLITE_OK); /* Deallocates any cached error strings. */
182660 sqlite3ValueFree(db->pErr);
182661 sqlite3CloseExtensions(db);
 
 
 
 
182662
182663 db->eOpenState = SQLITE_STATE_ERROR;
182664
182665 /* The temp-database schema is allocated differently from the other schema
182666 ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()).
@@ -183951,12 +184091,12 @@
184091 # error SQLITE_MAX_COMPOUND_SELECT must be at least 2
184092 #endif
184093 #if SQLITE_MAX_VDBE_OP<40
184094 # error SQLITE_MAX_VDBE_OP must be at least 40
184095 #endif
184096 #if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>32767
184097 # error SQLITE_MAX_FUNCTION_ARG must be between 0 and 32767
184098 #endif
184099 #if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>125
184100 # error SQLITE_MAX_ATTACHED must be between 0 and 125
184101 #endif
184102 #if SQLITE_MAX_LIKE_PATTERN_LENGTH<1
@@ -184019,12 +184159,12 @@
184159 }
184160 oldLimit = db->aLimit[limitId];
184161 if( newLimit>=0 ){ /* IMP: R-52476-28732 */
184162 if( newLimit>aHardLimit[limitId] ){
184163 newLimit = aHardLimit[limitId]; /* IMP: R-51463-25634 */
184164 }else if( newLimit<SQLITE_MIN_LENGTH && limitId==SQLITE_LIMIT_LENGTH ){
184165 newLimit = SQLITE_MIN_LENGTH;
184166 }
184167 db->aLimit[limitId] = newLimit;
184168 }
184169 return oldLimit; /* IMP: R-53341-35419 */
184170 }
@@ -185364,11 +185504,10 @@
185504 rc = x;
185505 #if defined(SQLITE_DEBUG)
185506 /* Invoke these debugging routines so that the compiler does not
185507 ** issue "defined but not used" warnings. */
185508 if( x==9999 ){
 
185509 sqlite3ShowExpr(0);
185510 sqlite3ShowExprList(0);
185511 sqlite3ShowIdList(0);
185512 sqlite3ShowSrcList(0);
185513 sqlite3ShowWith(0);
@@ -189796,14 +189935,19 @@
189935
189936 assert_fts3_nc( p!=0 && *p1!=0 && *p2!=0 );
189937 if( *p1==POS_COLUMN ){
189938 p1++;
189939 p1 += fts3GetVarint32(p1, &iCol1);
189940 /* iCol1==0 indicates corruption. Column 0 does not have a POS_COLUMN
189941 ** entry, so this is actually end-of-doclist. */
189942 if( iCol1==0 ) return 0;
189943 }
189944 if( *p2==POS_COLUMN ){
189945 p2++;
189946 p2 += fts3GetVarint32(p2, &iCol2);
189947 /* As above, iCol2==0 indicates corruption. */
189948 if( iCol2==0 ) return 0;
189949 }
189950
189951 while( 1 ){
189952 if( iCol1==iCol2 ){
189953 char *pSave = p;
@@ -192970,11 +193114,11 @@
193114 for(p=pExpr; p->pLeft; p=p->pLeft){
193115 assert( p->pRight->pPhrase->doclist.nList>0 );
193116 nTmp += p->pRight->pPhrase->doclist.nList;
193117 }
193118 nTmp += p->pPhrase->doclist.nList;
193119 aTmp = sqlite3_malloc64(nTmp*2 + FTS3_VARINT_MAX);
193120 if( !aTmp ){
193121 *pRc = SQLITE_NOMEM;
193122 res = 0;
193123 }else{
193124 char *aPoslist = p->pPhrase->doclist.pList;
@@ -193621,11 +193765,11 @@
193765 SQLITE_PRIVATE int sqlite3Fts3Corrupt(){
193766 return SQLITE_CORRUPT_VTAB;
193767 }
193768 #endif
193769
193770 #if !defined(SQLITE_CORE)
193771 /*
193772 ** Initialize API pointer table, if required.
193773 */
193774 #ifdef _WIN32
193775 __declspec(dllexport)
@@ -194523,14 +194667,15 @@
194667 rc = pModule->xNext(pCursor, &zByte, &nByte, &iBegin, &iEnd, &iPos);
194668 if( rc==SQLITE_OK ){
194669 Fts3PhraseToken *pToken;
194670
194671 p = fts3ReallocOrFree(p, nSpace + ii*sizeof(Fts3PhraseToken));
 
 
194672 zTemp = fts3ReallocOrFree(zTemp, nTemp + nByte);
194673 if( !zTemp || !p ){
194674 rc = SQLITE_NOMEM;
194675 goto getnextstring_out;
194676 }
194677
194678 assert( nToken==ii );
194679 pToken = &((Fts3Phrase *)(&p[1]))->aToken[ii];
194680 memset(pToken, 0, sizeof(Fts3PhraseToken));
194681
@@ -194541,53 +194686,51 @@
194686 pToken->isPrefix = (iEnd<nInput && zInput[iEnd]=='*');
194687 pToken->bFirst = (iBegin>0 && zInput[iBegin-1]=='^');
194688 nToken = ii+1;
194689 }
194690 }
 
 
 
194691 }
194692
194693 if( rc==SQLITE_DONE ){
194694 int jj;
194695 char *zBuf = 0;
194696
194697 p = fts3ReallocOrFree(p, nSpace + nToken*sizeof(Fts3PhraseToken) + nTemp);
194698 if( !p ){
194699 rc = SQLITE_NOMEM;
194700 goto getnextstring_out;
194701 }
194702 memset(p, 0, (char *)&(((Fts3Phrase *)&p[1])->aToken[0])-(char *)p);
194703 p->eType = FTSQUERY_PHRASE;
194704 p->pPhrase = (Fts3Phrase *)&p[1];
194705 p->pPhrase->iColumn = pParse->iDefaultCol;
194706 p->pPhrase->nToken = nToken;
194707
194708 zBuf = (char *)&p->pPhrase->aToken[nToken];
194709 assert( nTemp==0 || zTemp );
194710 if( zTemp ){
194711 memcpy(zBuf, zTemp, nTemp);
 
 
 
194712 }
194713
194714 for(jj=0; jj<p->pPhrase->nToken; jj++){
194715 p->pPhrase->aToken[jj].z = zBuf;
194716 zBuf += p->pPhrase->aToken[jj].n;
194717 }
194718 rc = SQLITE_OK;
194719 }
194720
194721 getnextstring_out:
 
 
 
194722 if( pCursor ){
194723 pModule->xClose(pCursor);
194724 }
194725 sqlite3_free(zTemp);
194726 if( rc!=SQLITE_OK ){
194727 sqlite3_free(p);
194728 p = 0;
194729 }
194730 *ppExpr = p;
194731 return rc;
194732 }
194733
194734 /*
194735 ** The output variable *ppExpr is populated with an allocated Fts3Expr
194736 ** structure, or set to 0 if the end of the input buffer is reached.
@@ -215395,12 +215538,12 @@
215538 #endif
215539 }
215540 sqlite3_str_append(pOut, "}", 1);
215541 }
215542 errCode = sqlite3_str_errcode(pOut);
 
215543 sqlite3_result_error_code(ctx, errCode);
215544 sqlite3_result_text(ctx, sqlite3_str_finish(pOut), -1, sqlite3_free);
215545 }
215546
215547 /* This routine implements an SQL function that returns the "depth" parameter
215548 ** from the front of a blob that is an r-tree node. For example:
215549 **
@@ -217912,11 +218055,11 @@
218055 return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY,
218056 (void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback
218057 );
218058 }
218059
218060 #ifndef SQLITE_CORE
218061 #ifdef _WIN32
218062 __declspec(dllexport)
218063 #endif
218064 SQLITE_API int sqlite3_rtree_init(
218065 sqlite3 *db,
@@ -218503,11 +218646,11 @@
218646 }
218647
218648 return rc;
218649 }
218650
218651 #ifndef SQLITE_CORE
218652 #ifdef _WIN32
218653 __declspec(dllexport)
218654 #endif
218655 SQLITE_API int sqlite3_icu_init(
218656 sqlite3 *db,
@@ -226177,10 +226320,12 @@
226320 if( (rc = sqlite3PagerWrite(pDbPage))==SQLITE_OK && pData ){
226321 unsigned char *aPage = sqlite3PagerGetData(pDbPage);
226322 memcpy(aPage, pData, szPage);
226323 pTab->pgnoTrunc = 0;
226324 }
226325 }else{
226326 pTab->pgnoTrunc = 0;
226327 }
226328 sqlite3PagerUnref(pDbPage);
226329 return rc;
226330
226331 update_fail:
@@ -226210,11 +226355,15 @@
226355 static int dbpageSync(sqlite3_vtab *pVtab){
226356 DbpageTable *pTab = (DbpageTable *)pVtab;
226357 if( pTab->pgnoTrunc>0 ){
226358 Btree *pBt = pTab->db->aDb[pTab->iDbTrunc].pBt;
226359 Pager *pPager = sqlite3BtreePager(pBt);
226360 sqlite3BtreeEnter(pBt);
226361 if( pTab->pgnoTrunc<sqlite3BtreeLastPage(pBt) ){
226362 sqlite3PagerTruncateImage(pPager, pTab->pgnoTrunc);
226363 }
226364 sqlite3BtreeLeave(pBt);
226365 }
226366 pTab->pgnoTrunc = 0;
226367 return SQLITE_OK;
226368 }
226369
@@ -232804,20 +232953,46 @@
232953 #endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
232954
232955 /************** End of sqlite3session.c **************************************/
232956 /************** Begin file fts5.c ********************************************/
232957
232958 /*
232959 ** This, the "fts5.c" source file, is a composite file that is itself
232960 ** assembled from the following files:
232961 **
232962 ** fts5.h
232963 ** fts5Int.h
232964 ** fts5parse.h <--- Generated from fts5parse.y by Lemon
232965 ** fts5parse.c <--- Generated from fts5parse.y by Lemon
232966 ** fts5_aux.c
232967 ** fts5_buffer.c
232968 ** fts5_config.c
232969 ** fts5_expr.c
232970 ** fts5_hash.c
232971 ** fts5_index.c
232972 ** fts5_main.c
232973 ** fts5_storage.c
232974 ** fts5_tokenize.c
232975 ** fts5_unicode2.c
232976 ** fts5_varint.c
232977 ** fts5_vocab.c
232978 */
232979 #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5)
232980
232981 #if !defined(NDEBUG) && !defined(SQLITE_DEBUG)
232982 # define NDEBUG 1
232983 #endif
232984 #if defined(NDEBUG) && defined(SQLITE_DEBUG)
232985 # undef NDEBUG
232986 #endif
232987
232988 #ifdef HAVE_STDINT_H
232989 /* #include <stdint.h> */
232990 #endif
232991 #ifdef HAVE_INTTYPES_H
232992 /* #include <inttypes.h> */
232993 #endif
232994 /*
232995 ** 2014 May 31
232996 **
232997 ** The author disclaims copyright to this source code. In place of
232998 ** a legal notice, here is a blessing:
@@ -233114,17 +233289,32 @@
233289 ** This is used to access token iToken of phrase hit iIdx within the
233290 ** current row. If iIdx is less than zero or greater than or equal to the
233291 ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise,
233292 ** output variable (*ppToken) is set to point to a buffer containing the
233293 ** matching document token, and (*pnToken) to the size of that buffer in
233294 ** bytes.
 
 
233295 **
233296 ** The output text is not a copy of the document text that was tokenized.
233297 ** It is the output of the tokenizer module. For tokendata=1 tables, this
233298 ** includes any embedded 0x00 and trailing data.
233299 **
233300 ** This API may be slow in some cases if the token identified by parameters
233301 ** iIdx and iToken matched a prefix token in the query. In most cases, the
233302 ** first call to this API for each prefix token in the query is forced
233303 ** to scan the portion of the full-text index that matches the prefix
233304 ** token to collect the extra data required by this API. If the prefix
233305 ** token matches a large number of token instances in the document set,
233306 ** this may be a performance problem.
233307 **
233308 ** If the user knows in advance that a query may use this API for a
233309 ** prefix token, FTS5 may be configured to collect all required data as part
233310 ** of the initial querying of the full-text index, avoiding the second scan
233311 ** entirely. This also causes prefix queries that do not use this API to
233312 ** run more slowly and use more memory. FTS5 may be configured in this way
233313 ** either on a per-table basis using the [FTS5 insttoken | 'insttoken']
233314 ** option, or on a per-query basis using the
233315 ** [fts5_insttoken | fts5_insttoken()] user function.
233316 **
233317 ** This API can be quite slow if used with an FTS5 table created with the
233318 ** "detail=none" or "detail=column" option.
233319 **
233320 ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale)
@@ -233803,11 +233993,12 @@
233993 int nUsermerge; /* 'usermerge' setting */
233994 int nHashSize; /* Bytes of memory for in-memory hash */
233995 char *zRank; /* Name of rank function */
233996 char *zRankArgs; /* Arguments to rank function */
233997 int bSecureDelete; /* 'secure-delete' */
233998 int nDeleteMerge; /* 'deletemerge' */
233999 int bPrefixInsttoken; /* 'prefix-insttoken' */
234000
234001 /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */
234002 char **pzErrmsg;
234003
234004 #ifdef SQLITE_DEBUG
@@ -234060,11 +234251,18 @@
234251 static int sqlite3Fts5StructureTest(Fts5Index*, void*);
234252
234253 /*
234254 ** Used by xInstToken():
234255 */
234256 static int sqlite3Fts5IterToken(
234257 Fts5IndexIter *pIndexIter,
234258 const char *pToken, int nToken,
234259 i64 iRowid,
234260 int iCol,
234261 int iOff,
234262 const char **ppOut, int *pnOut
234263 );
234264
234265 /*
234266 ** Insert or remove data to or from the index. Each time a document is
234267 ** added to or removed from the index, this function is called one or more
234268 ** times.
@@ -238274,10 +238472,23 @@
238472 if( bVal<0 ){
238473 *pbBadkey = 1;
238474 }else{
238475 pConfig->bSecureDelete = (bVal ? 1 : 0);
238476 }
238477 }
238478
238479 else if( 0==sqlite3_stricmp(zKey, "insttoken") ){
238480 int bVal = -1;
238481 if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){
238482 bVal = sqlite3_value_int(pVal);
238483 }
238484 if( bVal<0 ){
238485 *pbBadkey = 1;
238486 }else{
238487 pConfig->bPrefixInsttoken = (bVal ? 1 : 0);
238488 }
238489
238490 }else{
238491 *pbBadkey = 1;
238492 }
238493 return rc;
238494 }
@@ -241409,11 +241620,11 @@
241620 && memcmp(pT->pTerm, pToken, pT->nQueryTerm)==0
241621 ){
241622 int rc = sqlite3Fts5PoslistWriterAppend(
241623 &pExpr->apExprPhrase[i]->poslist, &p->aPopulator[i].writer, p->iOff
241624 );
241625 if( rc==SQLITE_OK && (pExpr->pConfig->bTokendata || pT->bPrefix) ){
241626 int iCol = p->iOff>>32;
241627 int iTokOff = p->iOff & 0x7FFFFFFF;
241628 rc = sqlite3Fts5IndexIterWriteTokendata(
241629 pT->pIter, pToken, nToken, iRowid, iCol, iTokOff
241630 );
@@ -241602,19 +241813,18 @@
241813 pPhrase = pExpr->apExprPhrase[iPhrase];
241814 if( iToken<0 || iToken>=pPhrase->nTerm ){
241815 return SQLITE_RANGE;
241816 }
241817 pTerm = &pPhrase->aTerm[iToken];
241818 if( pExpr->pConfig->bTokendata || pTerm->bPrefix ){
241819 rc = sqlite3Fts5IterToken(
241820 pTerm->pIter, pTerm->pTerm, pTerm->nQueryTerm,
241821 iRowid, iCol, iOff+iToken, ppOut, pnOut
241822 );
241823 }else{
241824 *ppOut = pTerm->pTerm;
241825 *pnOut = pTerm->nFullTerm;
 
241826 }
241827 return rc;
241828 }
241829
241830 /*
@@ -248425,10 +248635,387 @@
248635 fts5BufferFree(&tmp);
248636 memset(&out.p[out.n], 0, FTS5_DATA_ZERO_PADDING);
248637 *p1 = out;
248638 }
248639
248640
248641 /*
248642 ** Iterate through a range of entries in the FTS index, invoking the xVisit
248643 ** callback for each of them.
248644 **
248645 ** Parameter pToken points to an nToken buffer containing an FTS index term
248646 ** (i.e. a document term with the preceding 1 byte index identifier -
248647 ** FTS5_MAIN_PREFIX or similar). If bPrefix is true, then the call visits
248648 ** all entries for terms that have pToken/nToken as a prefix. If bPrefix
248649 ** is false, then only entries with pToken/nToken as the entire key are
248650 ** visited.
248651 **
248652 ** If the current table is a tokendata=1 table, then if bPrefix is true then
248653 ** each index term is treated separately. However, if bPrefix is false, then
248654 ** all index terms corresponding to pToken/nToken are collapsed into a single
248655 ** term before the callback is invoked.
248656 **
248657 ** The callback invoked for each entry visited is specified by paramter xVisit.
248658 ** Each time it is invoked, it is passed a pointer to the Fts5Index object,
248659 ** a copy of the 7th paramter to this function (pCtx) and a pointer to the
248660 ** iterator that indicates the current entry. If the current entry is the
248661 ** first with a new term (i.e. different from that of the previous entry,
248662 ** including the very first term), then the final two parameters are passed
248663 ** a pointer to the term and its size in bytes, respectively. If the current
248664 ** entry is not the first associated with its term, these two parameters
248665 ** are passed 0.
248666 **
248667 ** If parameter pColset is not NULL, then it is used to filter entries before
248668 ** the callback is invoked.
248669 */
248670 static int fts5VisitEntries(
248671 Fts5Index *p, /* Fts5 index object */
248672 Fts5Colset *pColset, /* Columns filter to apply, or NULL */
248673 u8 *pToken, /* Buffer containing token */
248674 int nToken, /* Size of buffer pToken in bytes */
248675 int bPrefix, /* True for a prefix scan */
248676 void (*xVisit)(Fts5Index*, void *pCtx, Fts5Iter *pIter, const u8*, int),
248677 void *pCtx /* Passed as second argument to xVisit() */
248678 ){
248679 const int flags = (bPrefix ? FTS5INDEX_QUERY_SCAN : 0)
248680 | FTS5INDEX_QUERY_SKIPEMPTY
248681 | FTS5INDEX_QUERY_NOOUTPUT;
248682 Fts5Iter *p1 = 0; /* Iterator used to gather data from index */
248683 int bNewTerm = 1;
248684 Fts5Structure *pStruct = fts5StructureRead(p);
248685
248686 fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1);
248687 fts5IterSetOutputCb(&p->rc, p1);
248688 for( /* no-op */ ;
248689 fts5MultiIterEof(p, p1)==0;
248690 fts5MultiIterNext2(p, p1, &bNewTerm)
248691 ){
248692 Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ];
248693 int nNew = 0;
248694 const u8 *pNew = 0;
248695
248696 p1->xSetOutputs(p1, pSeg);
248697 if( p->rc ) break;
248698
248699 if( bNewTerm ){
248700 nNew = pSeg->term.n;
248701 pNew = pSeg->term.p;
248702 if( nNew<nToken || memcmp(pToken, pNew, nToken) ) break;
248703 }
248704
248705 xVisit(p, pCtx, p1, pNew, nNew);
248706 }
248707 fts5MultiIterFree(p1);
248708
248709 fts5StructureRelease(pStruct);
248710 return p->rc;
248711 }
248712
248713
248714 /*
248715 ** Usually, a tokendata=1 iterator (struct Fts5TokenDataIter) accumulates an
248716 ** array of these for each row it visits (so all iRowid fields are the same).
248717 ** Or, for an iterator used by an "ORDER BY rank" query, it accumulates an
248718 ** array of these for the entire query (in which case iRowid fields may take
248719 ** a variety of values).
248720 **
248721 ** Each instance in the array indicates the iterator (and therefore term)
248722 ** associated with position iPos of rowid iRowid. This is used by the
248723 ** xInstToken() API.
248724 **
248725 ** iRowid:
248726 ** Rowid for the current entry.
248727 **
248728 ** iPos:
248729 ** Position of current entry within row. In the usual ((iCol<<32)+iOff)
248730 ** format (e.g. see macros FTS5_POS2COLUMN() and FTS5_POS2OFFSET()).
248731 **
248732 ** iIter:
248733 ** If the Fts5TokenDataIter iterator that the entry is part of is
248734 ** actually an iterator (i.e. with nIter>0, not just a container for
248735 ** Fts5TokenDataMap structures), then this variable is an index into
248736 ** the apIter[] array. The corresponding term is that which the iterator
248737 ** at apIter[iIter] currently points to.
248738 **
248739 ** Or, if the Fts5TokenDataIter iterator is just a container object
248740 ** (nIter==0), then iIter is an index into the term.p[] buffer where
248741 ** the term is stored.
248742 **
248743 ** nByte:
248744 ** In the case where iIter is an index into term.p[], this variable
248745 ** is the size of the term in bytes. If iIter is an index into apIter[],
248746 ** this variable is unused.
248747 */
248748 struct Fts5TokenDataMap {
248749 i64 iRowid; /* Row this token is located in */
248750 i64 iPos; /* Position of token */
248751 int iIter; /* Iterator token was read from */
248752 int nByte; /* Length of token in bytes (or 0) */
248753 };
248754
248755 /*
248756 ** An object used to supplement Fts5Iter for tokendata=1 iterators.
248757 **
248758 ** This object serves two purposes. The first is as a container for an array
248759 ** of Fts5TokenDataMap structures, which are used to find the token required
248760 ** when the xInstToken() API is used. This is done by the nMapAlloc, nMap and
248761 ** aMap[] variables.
248762 */
248763 struct Fts5TokenDataIter {
248764 int nMapAlloc; /* Allocated size of aMap[] in entries */
248765 int nMap; /* Number of valid entries in aMap[] */
248766 Fts5TokenDataMap *aMap; /* Array of (rowid+pos -> token) mappings */
248767
248768 /* The following are used for prefix-queries only. */
248769 Fts5Buffer terms;
248770
248771 /* The following are used for other full-token tokendata queries only. */
248772 int nIter;
248773 int nIterAlloc;
248774 Fts5PoslistReader *aPoslistReader;
248775 int *aPoslistToIter;
248776 Fts5Iter *apIter[1];
248777 };
248778
248779 /*
248780 ** The two input arrays - a1[] and a2[] - are in sorted order. This function
248781 ** merges the two arrays together and writes the result to output array
248782 ** aOut[]. aOut[] is guaranteed to be large enough to hold the result.
248783 **
248784 ** Duplicate entries are copied into the output. So the size of the output
248785 ** array is always (n1+n2) entries.
248786 */
248787 static void fts5TokendataMerge(
248788 Fts5TokenDataMap *a1, int n1, /* Input array 1 */
248789 Fts5TokenDataMap *a2, int n2, /* Input array 2 */
248790 Fts5TokenDataMap *aOut /* Output array */
248791 ){
248792 int i1 = 0;
248793 int i2 = 0;
248794
248795 assert( n1>=0 && n2>=0 );
248796 while( i1<n1 || i2<n2 ){
248797 Fts5TokenDataMap *pOut = &aOut[i1+i2];
248798 if( i2>=n2 || (i1<n1 && (
248799 a1[i1].iRowid<a2[i2].iRowid
248800 || (a1[i1].iRowid==a2[i2].iRowid && a1[i1].iPos<=a2[i2].iPos)
248801 ))){
248802 memcpy(pOut, &a1[i1], sizeof(Fts5TokenDataMap));
248803 i1++;
248804 }else{
248805 memcpy(pOut, &a2[i2], sizeof(Fts5TokenDataMap));
248806 i2++;
248807 }
248808 }
248809 }
248810
248811
248812 /*
248813 ** Append a mapping to the token-map belonging to object pT.
248814 */
248815 static void fts5TokendataIterAppendMap(
248816 Fts5Index *p,
248817 Fts5TokenDataIter *pT,
248818 int iIter,
248819 int nByte,
248820 i64 iRowid,
248821 i64 iPos
248822 ){
248823 if( p->rc==SQLITE_OK ){
248824 if( pT->nMap==pT->nMapAlloc ){
248825 int nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64;
248826 int nAlloc = nNew * sizeof(Fts5TokenDataMap);
248827 Fts5TokenDataMap *aNew;
248828
248829 aNew = (Fts5TokenDataMap*)sqlite3_realloc(pT->aMap, nAlloc);
248830 if( aNew==0 ){
248831 p->rc = SQLITE_NOMEM;
248832 return;
248833 }
248834
248835 pT->aMap = aNew;
248836 pT->nMapAlloc = nNew;
248837 }
248838
248839 pT->aMap[pT->nMap].iRowid = iRowid;
248840 pT->aMap[pT->nMap].iPos = iPos;
248841 pT->aMap[pT->nMap].iIter = iIter;
248842 pT->aMap[pT->nMap].nByte = nByte;
248843 pT->nMap++;
248844 }
248845 }
248846
248847 /*
248848 ** Sort the contents of the pT->aMap[] array.
248849 **
248850 ** The sorting algorithm requries a malloc(). If this fails, an error code
248851 ** is left in Fts5Index.rc before returning.
248852 */
248853 static void fts5TokendataIterSortMap(Fts5Index *p, Fts5TokenDataIter *pT){
248854 Fts5TokenDataMap *aTmp = 0;
248855 int nByte = pT->nMap * sizeof(Fts5TokenDataMap);
248856
248857 aTmp = (Fts5TokenDataMap*)sqlite3Fts5MallocZero(&p->rc, nByte);
248858 if( aTmp ){
248859 Fts5TokenDataMap *a1 = pT->aMap;
248860 Fts5TokenDataMap *a2 = aTmp;
248861 i64 nHalf;
248862
248863 for(nHalf=1; nHalf<pT->nMap; nHalf=nHalf*2){
248864 int i1;
248865 for(i1=0; i1<pT->nMap; i1+=(nHalf*2)){
248866 int n1 = MIN(nHalf, pT->nMap-i1);
248867 int n2 = MIN(nHalf, pT->nMap-i1-n1);
248868 fts5TokendataMerge(&a1[i1], n1, &a1[i1+n1], n2, &a2[i1]);
248869 }
248870 SWAPVAL(Fts5TokenDataMap*, a1, a2);
248871 }
248872
248873 if( a1!=pT->aMap ){
248874 memcpy(pT->aMap, a1, pT->nMap*sizeof(Fts5TokenDataMap));
248875 }
248876 sqlite3_free(aTmp);
248877
248878 #ifdef SQLITE_DEBUG
248879 {
248880 int ii;
248881 for(ii=1; ii<pT->nMap; ii++){
248882 Fts5TokenDataMap *p1 = &pT->aMap[ii-1];
248883 Fts5TokenDataMap *p2 = &pT->aMap[ii];
248884 assert( p1->iRowid<p2->iRowid
248885 || (p1->iRowid==p2->iRowid && p1->iPos<=p2->iPos)
248886 );
248887 }
248888 }
248889 #endif
248890 }
248891 }
248892
248893 /*
248894 ** Delete an Fts5TokenDataIter structure and its contents.
248895 */
248896 static void fts5TokendataIterDelete(Fts5TokenDataIter *pSet){
248897 if( pSet ){
248898 int ii;
248899 for(ii=0; ii<pSet->nIter; ii++){
248900 fts5MultiIterFree(pSet->apIter[ii]);
248901 }
248902 fts5BufferFree(&pSet->terms);
248903 sqlite3_free(pSet->aPoslistReader);
248904 sqlite3_free(pSet->aMap);
248905 sqlite3_free(pSet);
248906 }
248907 }
248908
248909
248910 /*
248911 ** fts5VisitEntries() context object used by fts5SetupPrefixIterTokendata()
248912 ** to pass data to prefixIterSetupTokendataCb().
248913 */
248914 typedef struct TokendataSetupCtx TokendataSetupCtx;
248915 struct TokendataSetupCtx {
248916 Fts5TokenDataIter *pT; /* Object being populated with mappings */
248917 int iTermOff; /* Offset of current term in terms.p[] */
248918 int nTermByte; /* Size of current term in bytes */
248919 };
248920
248921 /*
248922 ** fts5VisitEntries() callback used by fts5SetupPrefixIterTokendata(). This
248923 ** callback adds an entry to the Fts5TokenDataIter.aMap[] array for each
248924 ** position in the current position-list. It doesn't matter that some of
248925 ** these may be out of order - they will be sorted later.
248926 */
248927 static void prefixIterSetupTokendataCb(
248928 Fts5Index *p,
248929 void *pCtx,
248930 Fts5Iter *p1,
248931 const u8 *pNew,
248932 int nNew
248933 ){
248934 TokendataSetupCtx *pSetup = (TokendataSetupCtx*)pCtx;
248935 int iPosOff = 0;
248936 i64 iPos = 0;
248937
248938 if( pNew ){
248939 pSetup->nTermByte = nNew-1;
248940 pSetup->iTermOff = pSetup->pT->terms.n;
248941 fts5BufferAppendBlob(&p->rc, &pSetup->pT->terms, nNew-1, pNew+1);
248942 }
248943
248944 while( 0==sqlite3Fts5PoslistNext64(
248945 p1->base.pData, p1->base.nData, &iPosOff, &iPos
248946 ) ){
248947 fts5TokendataIterAppendMap(p,
248948 pSetup->pT, pSetup->iTermOff, pSetup->nTermByte, p1->base.iRowid, iPos
248949 );
248950 }
248951 }
248952
248953
248954 /*
248955 ** Context object passed by fts5SetupPrefixIter() to fts5VisitEntries().
248956 */
248957 typedef struct PrefixSetupCtx PrefixSetupCtx;
248958 struct PrefixSetupCtx {
248959 void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*);
248960 void (*xAppend)(Fts5Index*, u64, Fts5Iter*, Fts5Buffer*);
248961 i64 iLastRowid;
248962 int nMerge;
248963 Fts5Buffer *aBuf;
248964 int nBuf;
248965 Fts5Buffer doclist;
248966 TokendataSetupCtx *pTokendata;
248967 };
248968
248969 /*
248970 ** fts5VisitEntries() callback used by fts5SetupPrefixIter()
248971 */
248972 static void prefixIterSetupCb(
248973 Fts5Index *p,
248974 void *pCtx,
248975 Fts5Iter *p1,
248976 const u8 *pNew,
248977 int nNew
248978 ){
248979 PrefixSetupCtx *pSetup = (PrefixSetupCtx*)pCtx;
248980 const int nMerge = pSetup->nMerge;
248981
248982 if( p1->base.nData>0 ){
248983 if( p1->base.iRowid<=pSetup->iLastRowid && pSetup->doclist.n>0 ){
248984 int i;
248985 for(i=0; p->rc==SQLITE_OK && pSetup->doclist.n; i++){
248986 int i1 = i*nMerge;
248987 int iStore;
248988 assert( i1+nMerge<=pSetup->nBuf );
248989 for(iStore=i1; iStore<i1+nMerge; iStore++){
248990 if( pSetup->aBuf[iStore].n==0 ){
248991 fts5BufferSwap(&pSetup->doclist, &pSetup->aBuf[iStore]);
248992 fts5BufferZero(&pSetup->doclist);
248993 break;
248994 }
248995 }
248996 if( iStore==i1+nMerge ){
248997 pSetup->xMerge(p, &pSetup->doclist, nMerge, &pSetup->aBuf[i1]);
248998 for(iStore=i1; iStore<i1+nMerge; iStore++){
248999 fts5BufferZero(&pSetup->aBuf[iStore]);
249000 }
249001 }
249002 }
249003 pSetup->iLastRowid = 0;
249004 }
249005
249006 pSetup->xAppend(
249007 p, (u64)p1->base.iRowid-(u64)pSetup->iLastRowid, p1, &pSetup->doclist
249008 );
249009 pSetup->iLastRowid = p1->base.iRowid;
249010 }
249011
249012 if( pSetup->pTokendata ){
249013 prefixIterSetupTokendataCb(p, (void*)pSetup->pTokendata, p1, pNew, nNew);
249014 }
249015 }
249016
249017 static void fts5SetupPrefixIter(
249018 Fts5Index *p, /* Index to read from */
249019 int bDesc, /* True for "ORDER BY rowid DESC" */
249020 int iIdx, /* Index to scan for data */
249021 u8 *pToken, /* Buffer containing prefix to match */
@@ -248435,137 +249022,91 @@
249022 int nToken, /* Size of buffer pToken in bytes */
249023 Fts5Colset *pColset, /* Restrict matches to these columns */
249024 Fts5Iter **ppIter /* OUT: New iterator */
249025 ){
249026 Fts5Structure *pStruct;
249027 PrefixSetupCtx s;
249028 TokendataSetupCtx s2;
 
249029
249030 memset(&s, 0, sizeof(s));
249031 memset(&s2, 0, sizeof(s2));
249032
249033 s.nMerge = 1;
249034 s.iLastRowid = 0;
249035 s.nBuf = 32;
249036 if( iIdx==0
249037 && p->pConfig->eDetail==FTS5_DETAIL_FULL
249038 && p->pConfig->bPrefixInsttoken
249039 ){
249040 s.pTokendata = &s2;
249041 s2.pT = (Fts5TokenDataIter*)fts5IdxMalloc(p, sizeof(*s2.pT));
249042 }
249043
249044 if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){
249045 s.xMerge = fts5MergeRowidLists;
249046 s.xAppend = fts5AppendRowid;
249047 }else{
249048 s.nMerge = FTS5_MERGE_NLIST-1;
249049 s.nBuf = s.nMerge*8; /* Sufficient to merge (16^8)==(2^32) lists */
249050 s.xMerge = fts5MergePrefixLists;
249051 s.xAppend = fts5AppendPoslist;
249052 }
249053
249054 s.aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*s.nBuf);
249055 pStruct = fts5StructureRead(p);
249056 assert( p->rc!=SQLITE_OK || (s.aBuf && pStruct) );
249057
249058 if( p->rc==SQLITE_OK ){
249059 void *pCtx = (void*)&s;
 
 
249060 int i;
 
 
249061 Fts5Data *pData;
 
 
 
 
249062
249063 /* If iIdx is non-zero, then it is the number of a prefix-index for
249064 ** prefixes 1 character longer than the prefix being queried for. That
249065 ** index contains all the doclists required, except for the one
249066 ** corresponding to the prefix itself. That one is extracted from the
249067 ** main term index here. */
249068 if( iIdx!=0 ){
 
 
249069 pToken[0] = FTS5_MAIN_PREFIX;
249070 fts5VisitEntries(p, pColset, pToken, nToken, 0, prefixIterSetupCb, pCtx);
 
 
 
 
 
 
 
 
 
 
 
 
 
249071 }
249072
249073 pToken[0] = FTS5_MAIN_PREFIX + iIdx;
249074 fts5VisitEntries(p, pColset, pToken, nToken, 1, prefixIterSetupCb, pCtx);
249075
249076 assert( (s.nBuf%s.nMerge)==0 );
249077 for(i=0; i<s.nBuf; i+=s.nMerge){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249078 int iFree;
249079 if( p->rc==SQLITE_OK ){
249080 s.xMerge(p, &s.doclist, s.nMerge, &s.aBuf[i]);
249081 }
249082 for(iFree=i; iFree<i+s.nMerge; iFree++){
249083 fts5BufferFree(&s.aBuf[iFree]);
249084 }
249085 }
 
249086
249087 pData = fts5IdxMalloc(p, sizeof(*pData)+s.doclist.n+FTS5_DATA_ZERO_PADDING);
249088 assert( pData!=0 || p->rc!=SQLITE_OK );
249089 if( pData ){
249090 pData->p = (u8*)&pData[1];
249091 pData->nn = pData->szLeaf = s.doclist.n;
249092 if( s.doclist.n ) memcpy(pData->p, s.doclist.p, s.doclist.n);
249093 fts5MultiIterNew2(p, pData, bDesc, ppIter);
249094 }
249095
249096 assert( (*ppIter)!=0 || p->rc!=SQLITE_OK );
249097 if( p->rc==SQLITE_OK && s.pTokendata ){
249098 fts5TokendataIterSortMap(p, s2.pT);
249099 (*ppIter)->pTokenDataIter = s2.pT;
249100 s2.pT = 0;
249101 }
249102 }
249103
249104 fts5TokendataIterDelete(s2.pT);
249105 fts5BufferFree(&s.doclist);
249106 fts5StructureRelease(pStruct);
249107 sqlite3_free(s.aBuf);
249108 }
249109
249110
249111 /*
249112 ** Indicate that all subsequent calls to sqlite3Fts5IndexWrite() pertain
@@ -248815,42 +249356,10 @@
249356 static void fts5SegIterSetEOF(Fts5SegIter *pSeg){
249357 fts5DataRelease(pSeg->pLeaf);
249358 pSeg->pLeaf = 0;
249359 }
249360
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249361 /*
249362 ** This function appends iterator pAppend to Fts5TokenDataIter pIn and
249363 ** returns the result.
249364 */
249365 static Fts5TokenDataIter *fts5AppendTokendataIter(
@@ -248883,58 +249392,10 @@
249392 assert( pRet==0 || pRet->nIter<=pRet->nIterAlloc );
249393
249394 return pRet;
249395 }
249396
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249397 /*
249398 ** The iterator passed as the only argument must be a tokendata=1 iterator
249399 ** (pIter->pTokenDataIter!=0). This function sets the iterator output
249400 ** variables (pIter->base.*) according to the contents of the current
249401 ** row.
@@ -248971,11 +249432,11 @@
249432 int eDetail = pIter->pIndex->pConfig->eDetail;
249433 pIter->base.bEof = 0;
249434 pIter->base.iRowid = iRowid;
249435
249436 if( nHit==1 && eDetail==FTS5_DETAIL_FULL ){
249437 fts5TokendataIterAppendMap(pIter->pIndex, pT, iMin, 0, iRowid, -1);
249438 }else
249439 if( nHit>1 && eDetail!=FTS5_DETAIL_NONE ){
249440 int nReader = 0;
249441 int nByte = 0;
249442 i64 iPrev = 0;
@@ -249224,10 +249685,11 @@
249685
249686 if( p->rc==SQLITE_OK ){
249687 pRet = fts5MultiIterAlloc(p, 0);
249688 }
249689 if( pRet ){
249690 pRet->nSeg = 0;
249691 pRet->pTokenDataIter = pSet;
249692 if( pSet ){
249693 fts5IterSetOutputsTokendata(pRet);
249694 }else{
249695 pRet->base.bEof = 1;
@@ -249238,11 +249700,10 @@
249700
249701 fts5StructureRelease(pStruct);
249702 fts5BufferFree(&bSeek);
249703 return pRet;
249704 }
 
249705
249706 /*
249707 ** Open a new iterator to iterate though all rowid that match the
249708 ** specified token or token prefix.
249709 */
@@ -249262,12 +249723,18 @@
249723
249724 if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){
249725 int iIdx = 0; /* Index to search */
249726 int iPrefixIdx = 0; /* +1 prefix index */
249727 int bTokendata = pConfig->bTokendata;
249728 assert( buf.p!=0 );
249729 if( nToken>0 ) memcpy(&buf.p[1], pToken, nToken);
249730
249731 /* The NOTOKENDATA flag is set when each token in a tokendata=1 table
249732 ** should be treated individually, instead of merging all those with
249733 ** a common prefix into a single entry. This is used, for example, by
249734 ** queries performed as part of an integrity-check, or by the fts5vocab
249735 ** module. */
249736 if( flags & (FTS5INDEX_QUERY_NOTOKENDATA|FTS5INDEX_QUERY_SCAN) ){
249737 bTokendata = 0;
249738 }
249739
249740 /* Figure out which index to search and set iIdx accordingly. If this
@@ -249294,11 +249761,11 @@
249761 if( nIdxChar==nChar+1 ) iPrefixIdx = iIdx;
249762 }
249763 }
249764
249765 if( bTokendata && iIdx==0 ){
249766 buf.p[0] = FTS5_MAIN_PREFIX;
249767 pRet = fts5SetupTokendataIter(p, buf.p, nToken+1, pColset);
249768 }else if( iIdx<=pConfig->nPrefix ){
249769 /* Straight index lookup */
249770 Fts5Structure *pStruct = fts5StructureRead(p);
249771 buf.p[0] = (u8)(FTS5_MAIN_PREFIX + iIdx);
@@ -249307,11 +249774,11 @@
249774 pColset, buf.p, nToken+1, -1, 0, &pRet
249775 );
249776 fts5StructureRelease(pStruct);
249777 }
249778 }else{
249779 /* Scan multiple terms in the main index for a prefix query. */
249780 int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0;
249781 fts5SetupPrefixIter(p, bDesc, iPrefixIdx, buf.p, nToken+1, pColset,&pRet);
249782 if( pRet==0 ){
249783 assert( p->rc!=SQLITE_OK );
249784 }else{
@@ -249343,11 +249810,12 @@
249810 ** Move to the next matching rowid.
249811 */
249812 static int sqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){
249813 Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
249814 assert( pIter->pIndex->rc==SQLITE_OK );
249815 if( pIter->nSeg==0 ){
249816 assert( pIter->pTokenDataIter );
249817 fts5TokendataIterNext(pIter, 0, 0);
249818 }else{
249819 fts5MultiIterNext(pIter->pIndex, pIter, 0, 0);
249820 }
249821 return fts5IndexReturn(pIter->pIndex);
@@ -249380,11 +249848,12 @@
249848 ** definition of "at or after" depends on whether this iterator iterates
249849 ** in ascending or descending rowid order.
249850 */
249851 static int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){
249852 Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
249853 if( pIter->nSeg==0 ){
249854 assert( pIter->pTokenDataIter );
249855 fts5TokendataIterNext(pIter, 1, iMatch);
249856 }else{
249857 fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch);
249858 }
249859 return fts5IndexReturn(pIter->pIndex);
@@ -249398,32 +249867,88 @@
249867 const char *z = (const char*)fts5MultiIterTerm((Fts5Iter*)pIndexIter, &n);
249868 assert_nc( z || n<=1 );
249869 *pn = n-1;
249870 return (z ? &z[1] : 0);
249871 }
249872
249873 /*
249874 ** pIter is a prefix query. This function populates pIter->pTokenDataIter
249875 ** with an Fts5TokenDataIter object containing mappings for all rows
249876 ** matched by the query.
249877 */
249878 static int fts5SetupPrefixIterTokendata(
249879 Fts5Iter *pIter,
249880 const char *pToken, /* Token prefix to search for */
249881 int nToken /* Size of pToken in bytes */
249882 ){
249883 Fts5Index *p = pIter->pIndex;
249884 Fts5Buffer token = {0, 0, 0};
249885 TokendataSetupCtx ctx;
249886
249887 memset(&ctx, 0, sizeof(ctx));
249888
249889 fts5BufferGrow(&p->rc, &token, nToken+1);
249890 assert( token.p!=0 || p->rc!=SQLITE_OK );
249891 ctx.pT = (Fts5TokenDataIter*)sqlite3Fts5MallocZero(&p->rc, sizeof(*ctx.pT));
249892
249893 if( p->rc==SQLITE_OK ){
249894
249895 /* Fill in the token prefix to search for */
249896 token.p[0] = FTS5_MAIN_PREFIX;
249897 memcpy(&token.p[1], pToken, nToken);
249898 token.n = nToken+1;
249899
249900 fts5VisitEntries(
249901 p, 0, token.p, token.n, 1, prefixIterSetupTokendataCb, (void*)&ctx
249902 );
249903
249904 fts5TokendataIterSortMap(p, ctx.pT);
249905 }
249906
249907 if( p->rc==SQLITE_OK ){
249908 pIter->pTokenDataIter = ctx.pT;
249909 }else{
249910 fts5TokendataIterDelete(ctx.pT);
249911 }
249912 fts5BufferFree(&token);
249913
249914 return fts5IndexReturn(p);
249915 }
249916
249917 /*
249918 ** This is used by xInstToken() to access the token at offset iOff, column
249919 ** iCol of row iRowid. The token is returned via output variables *ppOut
249920 ** and *pnOut. The iterator passed as the first argument must be a tokendata=1
249921 ** iterator (pIter->pTokenDataIter!=0).
249922 **
249923 ** pToken/nToken:
249924 */
249925 static int sqlite3Fts5IterToken(
249926 Fts5IndexIter *pIndexIter,
249927 const char *pToken, int nToken,
249928 i64 iRowid,
249929 int iCol,
249930 int iOff,
249931 const char **ppOut, int *pnOut
249932 ){
249933 Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
249934 Fts5TokenDataIter *pT = pIter->pTokenDataIter;
 
249935 i64 iPos = (((i64)iCol)<<32) + iOff;
249936 Fts5TokenDataMap *aMap = 0;
249937 int i1 = 0;
249938 int i2 = 0;
249939 int iTest = 0;
249940
249941 assert( pT || (pToken && pIter->nSeg>0) );
249942 if( pT==0 ){
249943 int rc = fts5SetupPrefixIterTokendata(pIter, pToken, nToken);
249944 if( rc!=SQLITE_OK ) return rc;
249945 pT = pIter->pTokenDataIter;
249946 }
249947
249948 i2 = pT->nMap;
249949 aMap = pT->aMap;
249950
249951 while( i2>i1 ){
249952 iTest = (i1 + i2) / 2;
249953
249954 if( aMap[iTest].iRowid<iRowid ){
@@ -249443,13 +249968,19 @@
249968 }
249969 }
249970 }
249971
249972 if( i2>i1 ){
249973 if( pIter->nSeg==0 ){
249974 Fts5Iter *pMap = pT->apIter[aMap[iTest].iIter];
249975 *ppOut = (const char*)pMap->aSeg[0].term.p+1;
249976 *pnOut = pMap->aSeg[0].term.n-1;
249977 }else{
249978 Fts5TokenDataMap *p = &aMap[iTest];
249979 *ppOut = (const char*)&pT->terms.p[p->iIter];
249980 *pnOut = aMap[iTest].nByte;
249981 }
249982 }
249983
249984 return SQLITE_OK;
249985 }
249986
@@ -249457,11 +249988,13 @@
249988 ** Clear any existing entries from the token-map associated with the
249989 ** iterator passed as the only argument.
249990 */
249991 static void sqlite3Fts5IndexIterClearTokendata(Fts5IndexIter *pIndexIter){
249992 Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
249993 if( pIter && pIter->pTokenDataIter
249994 && (pIter->nSeg==0 || pIter->pIndex->pConfig->eDetail!=FTS5_DETAIL_FULL)
249995 ){
249996 pIter->pTokenDataIter->nMap = 0;
249997 }
249998 }
249999
250000 /*
@@ -249477,21 +250010,33 @@
250010 i64 iRowid, int iCol, int iOff
250011 ){
250012 Fts5Iter *pIter = (Fts5Iter*)pIndexIter;
250013 Fts5TokenDataIter *pT = pIter->pTokenDataIter;
250014 Fts5Index *p = pIter->pIndex;
250015 i64 iPos = (((i64)iCol)<<32) + iOff;
250016
250017 assert( p->pConfig->eDetail!=FTS5_DETAIL_FULL );
250018 assert( pIter->pTokenDataIter || pIter->nSeg>0 );
250019 if( pIter->nSeg>0 ){
250020 /* This is a prefix term iterator. */
250021 if( pT==0 ){
250022 pT = (Fts5TokenDataIter*)sqlite3Fts5MallocZero(&p->rc, sizeof(*pT));
250023 pIter->pTokenDataIter = pT;
250024 }
250025 if( pT ){
250026 fts5TokendataIterAppendMap(p, pT, pT->terms.n, nToken, iRowid, iPos);
250027 fts5BufferAppendBlob(&p->rc, &pT->terms, nToken, (const u8*)pToken);
250028 }
250029 }else{
250030 int ii;
250031 for(ii=0; ii<pT->nIter; ii++){
250032 Fts5Buffer *pTerm = &pT->apIter[ii]->aSeg[0].term;
250033 if( nToken==pTerm->n-1 && memcmp(pToken, pTerm->p+1, nToken)==0 ) break;
250034 }
250035 if( ii<pT->nIter ){
250036 fts5TokendataIterAppendMap(p, pT, ii, 0, iRowid, iPos);
250037 }
250038 }
250039 return fts5IndexReturn(p);
250040 }
250041
250042 /*
@@ -251392,10 +251937,11 @@
251937 ** containing a copy of the header from an Fts5Config pointer.
251938 */
251939 #define FTS5_LOCALE_HDR_SIZE ((int)sizeof( ((Fts5Global*)0)->aLocaleHdr ))
251940 #define FTS5_LOCALE_HDR(pConfig) ((const u8*)(pConfig->pGlobal->aLocaleHdr))
251941
251942 #define FTS5_INSTTOKEN_SUBTYPE 73
251943
251944 /*
251945 ** Each auxiliary function registered with the FTS5 module is represented
251946 ** by an object of the following type. All such objects are stored as part
251947 ** of the Fts5Global.pAux list.
@@ -251931,10 +252477,11 @@
252477 ){
252478 /* A MATCH operator or equivalent */
252479 if( p->usable==0 || iCol<0 ){
252480 /* As there exists an unusable MATCH constraint this is an
252481 ** unusable plan. Return SQLITE_CONSTRAINT. */
252482 idxStr[iIdxStr] = 0;
252483 return SQLITE_CONSTRAINT;
252484 }else{
252485 if( iCol==nCol+1 ){
252486 if( bSeenRank ) continue;
252487 idxStr[iIdxStr++] = 'r';
@@ -252716,10 +253263,11 @@
253263 sqlite3_value *pRowidEq = 0; /* rowid = ? expression (or NULL) */
253264 sqlite3_value *pRowidLe = 0; /* rowid <= ? expression (or NULL) */
253265 sqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */
253266 int iCol; /* Column on LHS of MATCH operator */
253267 char **pzErrmsg = pConfig->pzErrmsg;
253268 int bPrefixInsttoken = pConfig->bPrefixInsttoken;
253269 int i;
253270 int iIdxStr = 0;
253271 Fts5Expr *pExpr = 0;
253272
253273 assert( pConfig->bLock==0 );
@@ -252751,10 +253299,13 @@
253299 int bInternal = 0;
253300
253301 rc = fts5ExtractExprText(pConfig, apVal[i], &zText, &bFreeAndReset);
253302 if( rc!=SQLITE_OK ) goto filter_out;
253303 if( zText==0 ) zText = "";
253304 if( sqlite3_value_subtype(apVal[i])==FTS5_INSTTOKEN_SUBTYPE ){
253305 pConfig->bPrefixInsttoken = 1;
253306 }
253307
253308 iCol = 0;
253309 do{
253310 iCol = iCol*10 + (idxStr[iIdxStr]-'0');
253311 iIdxStr++;
@@ -252891,10 +253442,11 @@
253442 }
253443
253444 filter_out:
253445 sqlite3Fts5ExprFree(pExpr);
253446 pConfig->pzErrmsg = pzErrmsg;
253447 pConfig->bPrefixInsttoken = bPrefixInsttoken;
253448 return rc;
253449 }
253450
253451 /*
253452 ** This is the xEof method of the virtual table. SQLite calls this
@@ -254886,11 +255438,11 @@
255438 int nArg, /* Number of args */
255439 sqlite3_value **apUnused /* Function arguments */
255440 ){
255441 assert( nArg==0 );
255442 UNUSED_PARAM2(nArg, apUnused);
255443 sqlite3_result_text(pCtx, "fts5: 2024-12-30 21:23:53 2b17bc49655c577029919c2d409de994b0d252f8efb5da1ba0913f2c96bee552", -1, SQLITE_TRANSIENT);
255444 }
255445
255446 /*
255447 ** Implementation of fts5_locale(LOCALE, TEXT) function.
255448 **
@@ -254949,10 +255501,24 @@
255501 assert( &pCsr[nText]==&pBlob[nBlob] );
255502
255503 sqlite3_result_blob(pCtx, pBlob, nBlob, sqlite3_free);
255504 }
255505 }
255506
255507 /*
255508 ** Implementation of fts5_insttoken() function.
255509 */
255510 static void fts5InsttokenFunc(
255511 sqlite3_context *pCtx, /* Function call context */
255512 int nArg, /* Number of args */
255513 sqlite3_value **apArg /* Function arguments */
255514 ){
255515 assert( nArg==1 );
255516 (void)nArg;
255517 sqlite3_result_value(pCtx, apArg[0]);
255518 sqlite3_result_subtype(pCtx, FTS5_INSTTOKEN_SUBTYPE);
255519 }
255520
255521 /*
255522 ** Return true if zName is the extension on one of the shadow tables used
255523 ** by this module.
255524 */
@@ -255079,13 +255645,20 @@
255645 );
255646 }
255647 if( rc==SQLITE_OK ){
255648 rc = sqlite3_create_function(
255649 db, "fts5_locale", 2,
255650 SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE|SQLITE_SUBTYPE,
255651 p, fts5LocaleFunc, 0, 0
255652 );
255653 }
255654 if( rc==SQLITE_OK ){
255655 rc = sqlite3_create_function(
255656 db, "fts5_insttoken", 1,
255657 SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE,
255658 p, fts5InsttokenFunc, 0, 0
255659 );
255660 }
255661 }
255662
255663 /* If SQLITE_FTS5_ENABLE_TEST_MI is defined, assume that the file
255664 ** fts5_test_mi.c is compiled and linked into the executable. And call
@@ -258007,22 +258580,22 @@
258580 int rc = SQLITE_OK;
258581 char aBuf[32];
258582 char *zOut = aBuf;
258583 int ii;
258584 const unsigned char *zIn = (const unsigned char*)pText;
258585 const unsigned char *zEof = (zIn ? &zIn[nText] : 0);
258586 u32 iCode = 0;
258587 int aStart[3]; /* Input offset of each character in aBuf[] */
258588
258589 UNUSED_PARAM(unusedFlags);
258590
258591 /* Populate aBuf[] with the characters for the first trigram. */
258592 for(ii=0; ii<3; ii++){
258593 do {
258594 aStart[ii] = zIn - (const unsigned char*)pText;
258595 if( zIn>=zEof ) return SQLITE_OK;
258596 READ_UTF8(zIn, zEof, iCode);
 
258597 if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam);
258598 }while( iCode==0 );
258599 WRITE_UTF8(zOut, iCode);
258600 }
258601
@@ -258039,12 +258612,15 @@
258612 const char *z1;
258613
258614 /* Read characters from the input up until the first non-diacritic */
258615 do {
258616 iNext = zIn - (const unsigned char*)pText;
258617 if( zIn>=zEof ){
258618 iCode = 0;
258619 break;
258620 }
258621 READ_UTF8(zIn, zEof, iCode);
 
258622 if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam);
258623 }while( iCode==0 );
258624
258625 /* Pass the current trigram back to fts5 */
258626 rc = xToken(pCtx, 0, aBuf, zOut-aBuf, aStart[0], iNext);
@@ -260077,11 +260653,11 @@
260653
260654 return sqlite3_create_module_v2(db, "fts5vocab", &fts5Vocab, p, 0);
260655 }
260656
260657
260658 /* Here ends the fts5.c composite file. */
260659 #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) */
260660
260661 /************** End of fts5.c ************************************************/
260662 /************** Begin file stmt.c ********************************************/
260663 /*
@@ -260433,6 +261009,7 @@
261009 #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */
261010
261011 /************** End of stmt.c ************************************************/
261012 /* Return the source-id for this library */
261013 SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; }
261014 #endif /* SQLITE_AMALGAMATION */
261015 /************************** End of sqlite3.c ******************************/
261016
+54 -8
--- extsrc/sqlite3.h
+++ extsrc/sqlite3.h
@@ -144,13 +144,13 @@
144144
**
145145
** See also: [sqlite3_libversion()],
146146
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
147147
** [sqlite_version()] and [sqlite_source_id()].
148148
*/
149
-#define SQLITE_VERSION "3.47.0"
150
-#define SQLITE_VERSION_NUMBER 3047000
151
-#define SQLITE_SOURCE_ID "2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f28411c85dda156f82636e"
149
+#define SQLITE_VERSION "3.48.0"
150
+#define SQLITE_VERSION_NUMBER 3048000
151
+#define SQLITE_SOURCE_ID "2024-12-30 21:23:53 2b17bc49655c577029919c2d409de994b0d252f8efb5da1ba0913f2c96bee552"
152152
153153
/*
154154
** CAPI3REF: Run-Time Library Version Numbers
155155
** KEYWORDS: sqlite3_version sqlite3_sourceid
156156
**
@@ -650,10 +650,17 @@
650650
**
651651
** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying
652652
** filesystem supports doing multiple write operations atomically when those
653653
** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and
654654
** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE].
655
+**
656
+** The SQLITE_IOCAP_SUBPAGE_READ property means that it is ok to read
657
+** from the database file in amounts that are not a multiple of the
658
+** page size and that do not begin at a page boundary. Without this
659
+** property, SQLite is careful to only do full-page reads and write
660
+** on aligned pages, with the one exception that it will do a sub-page
661
+** read of the first page to access the database header.
655662
*/
656663
#define SQLITE_IOCAP_ATOMIC 0x00000001
657664
#define SQLITE_IOCAP_ATOMIC512 0x00000002
658665
#define SQLITE_IOCAP_ATOMIC1K 0x00000004
659666
#define SQLITE_IOCAP_ATOMIC2K 0x00000008
@@ -666,10 +673,11 @@
666673
#define SQLITE_IOCAP_SEQUENTIAL 0x00000400
667674
#define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800
668675
#define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000
669676
#define SQLITE_IOCAP_IMMUTABLE 0x00002000
670677
#define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000
678
+#define SQLITE_IOCAP_SUBPAGE_READ 0x00008000
671679
672680
/*
673681
** CAPI3REF: File Locking Levels
674682
**
675683
** SQLite uses one of these integer values as the second
@@ -812,10 +820,11 @@
812820
** <li> [SQLITE_IOCAP_SEQUENTIAL]
813821
** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN]
814822
** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE]
815823
** <li> [SQLITE_IOCAP_IMMUTABLE]
816824
** <li> [SQLITE_IOCAP_BATCH_ATOMIC]
825
+** <li> [SQLITE_IOCAP_SUBPAGE_READ]
817826
** </ul>
818827
**
819828
** The SQLITE_IOCAP_ATOMIC property means that all writes of
820829
** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values
821830
** mean that writes of blocks that are nnn bytes in size and
@@ -1089,10 +1098,15 @@
10891098
** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This
10901099
** opcode causes the xFileControl method to swap the file handle with the one
10911100
** pointed to by the pArg argument. This capability is used during testing
10921101
** and only needs to be supported when SQLITE_TEST is defined.
10931102
**
1103
+** <li>[[SQLITE_FCNTL_NULL_IO]]
1104
+** The [SQLITE_FCNTL_NULL_IO] opcode sets the low-level file descriptor
1105
+** or file handle for the [sqlite3_file] object such that it will no longer
1106
+** read or write to the database file.
1107
+**
10941108
** <li>[[SQLITE_FCNTL_WAL_BLOCK]]
10951109
** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might
10961110
** be advantageous to block on the next WAL lock if the lock is not immediately
10971111
** available. The WAL subsystem issues this signal during rare
10981112
** circumstances in order to fix a problem with priority inversion.
@@ -1242,10 +1256,11 @@
12421256
#define SQLITE_FCNTL_RESERVE_BYTES 38
12431257
#define SQLITE_FCNTL_CKPT_START 39
12441258
#define SQLITE_FCNTL_EXTERNAL_READER 40
12451259
#define SQLITE_FCNTL_CKSM_FILE 41
12461260
#define SQLITE_FCNTL_RESET_CACHE 42
1261
+#define SQLITE_FCNTL_NULL_IO 43
12471262
12481263
/* deprecated names */
12491264
#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
12501265
#define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE
12511266
#define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO
@@ -2620,14 +2635,18 @@
26202635
**
26212636
** ^These functions return the number of rows modified, inserted or
26222637
** deleted by the most recently completed INSERT, UPDATE or DELETE
26232638
** statement on the database connection specified by the only parameter.
26242639
** The two functions are identical except for the type of the return value
2625
-** and that if the number of rows modified by the most recent INSERT, UPDATE
2640
+** and that if the number of rows modified by the most recent INSERT, UPDATE,
26262641
** or DELETE is greater than the maximum value supported by type "int", then
26272642
** the return value of sqlite3_changes() is undefined. ^Executing any other
26282643
** type of SQL statement does not modify the value returned by these functions.
2644
+** For the purposes of this interface, a CREATE TABLE AS SELECT statement
2645
+** does not count as an INSERT, UPDATE or DELETE statement and hence the rows
2646
+** added to the new table by the CREATE TABLE AS SELECT statement are not
2647
+** counted.
26292648
**
26302649
** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are
26312650
** considered - auxiliary changes caused by [CREATE TRIGGER | triggers],
26322651
** [foreign key actions] or [REPLACE] constraint resolution are not counted.
26332652
**
@@ -4183,15 +4202,26 @@
41834202
**
41844203
** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt>
41854204
** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler
41864205
** to return an error (error code SQLITE_ERROR) if the statement uses
41874206
** any virtual tables.
4207
+**
4208
+** [[SQLITE_PREPARE_DONT_LOG]] <dt>SQLITE_PREPARE_DONT_LOG</dt>
4209
+** <dd>The SQLITE_PREPARE_DONT_LOG flag prevents SQL compiler
4210
+** errors from being sent to the error log defined by
4211
+** [SQLITE_CONFIG_LOG]. This can be used, for example, to do test
4212
+** compiles to see if some SQL syntax is well-formed, without generating
4213
+** messages on the global error log when it is not. If the test compile
4214
+** fails, the sqlite3_prepare_v3() call returns the same error indications
4215
+** with or without this flag; it just omits the call to [sqlite3_log()] that
4216
+** logs the error.
41884217
** </dl>
41894218
*/
41904219
#define SQLITE_PREPARE_PERSISTENT 0x01
41914220
#define SQLITE_PREPARE_NORMALIZE 0x02
41924221
#define SQLITE_PREPARE_NO_VTAB 0x04
4222
+#define SQLITE_PREPARE_DONT_LOG 0x10
41934223
41944224
/*
41954225
** CAPI3REF: Compiling An SQL Statement
41964226
** KEYWORDS: {SQL statement compiler}
41974227
** METHOD: sqlite3
@@ -10878,11 +10908,11 @@
1087810908
#endif
1087910909
1088010910
#ifdef __cplusplus
1088110911
} /* End of the 'extern "C"' block */
1088210912
#endif
10883
-#endif /* SQLITE3_H */
10913
+/* #endif for SQLITE3_H will be added by mksqlite3.tcl */
1088410914
1088510915
/******** Begin file sqlite3rtree.h *********/
1088610916
/*
1088710917
** 2010 August 30
1088810918
**
@@ -13129,17 +13159,32 @@
1312913159
** This is used to access token iToken of phrase hit iIdx within the
1313013160
** current row. If iIdx is less than zero or greater than or equal to the
1313113161
** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise,
1313213162
** output variable (*ppToken) is set to point to a buffer containing the
1313313163
** matching document token, and (*pnToken) to the size of that buffer in
13134
-** bytes. This API is not available if the specified token matches a
13135
-** prefix query term. In that case both output variables are always set
13136
-** to 0.
13164
+** bytes.
1313713165
**
1313813166
** The output text is not a copy of the document text that was tokenized.
1313913167
** It is the output of the tokenizer module. For tokendata=1 tables, this
1314013168
** includes any embedded 0x00 and trailing data.
13169
+**
13170
+** This API may be slow in some cases if the token identified by parameters
13171
+** iIdx and iToken matched a prefix token in the query. In most cases, the
13172
+** first call to this API for each prefix token in the query is forced
13173
+** to scan the portion of the full-text index that matches the prefix
13174
+** token to collect the extra data required by this API. If the prefix
13175
+** token matches a large number of token instances in the document set,
13176
+** this may be a performance problem.
13177
+**
13178
+** If the user knows in advance that a query may use this API for a
13179
+** prefix token, FTS5 may be configured to collect all required data as part
13180
+** of the initial querying of the full-text index, avoiding the second scan
13181
+** entirely. This also causes prefix queries that do not use this API to
13182
+** run more slowly and use more memory. FTS5 may be configured in this way
13183
+** either on a per-table basis using the [FTS5 insttoken | 'insttoken']
13184
+** option, or on a per-query basis using the
13185
+** [fts5_insttoken | fts5_insttoken()] user function.
1314113186
**
1314213187
** This API can be quite slow if used with an FTS5 table created with the
1314313188
** "detail=none" or "detail=column" option.
1314413189
**
1314513190
** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale)
@@ -13570,5 +13615,6 @@
1357013615
#endif
1357113616
1357213617
#endif /* _FTS5_H */
1357313618
1357413619
/******** End of fts5.h *********/
13620
+#endif /* SQLITE3_H */
1357513621
--- extsrc/sqlite3.h
+++ extsrc/sqlite3.h
@@ -144,13 +144,13 @@
144 **
145 ** See also: [sqlite3_libversion()],
146 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
147 ** [sqlite_version()] and [sqlite_source_id()].
148 */
149 #define SQLITE_VERSION "3.47.0"
150 #define SQLITE_VERSION_NUMBER 3047000
151 #define SQLITE_SOURCE_ID "2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f28411c85dda156f82636e"
152
153 /*
154 ** CAPI3REF: Run-Time Library Version Numbers
155 ** KEYWORDS: sqlite3_version sqlite3_sourceid
156 **
@@ -650,10 +650,17 @@
650 **
651 ** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying
652 ** filesystem supports doing multiple write operations atomically when those
653 ** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and
654 ** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE].
 
 
 
 
 
 
 
655 */
656 #define SQLITE_IOCAP_ATOMIC 0x00000001
657 #define SQLITE_IOCAP_ATOMIC512 0x00000002
658 #define SQLITE_IOCAP_ATOMIC1K 0x00000004
659 #define SQLITE_IOCAP_ATOMIC2K 0x00000008
@@ -666,10 +673,11 @@
666 #define SQLITE_IOCAP_SEQUENTIAL 0x00000400
667 #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800
668 #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000
669 #define SQLITE_IOCAP_IMMUTABLE 0x00002000
670 #define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000
 
671
672 /*
673 ** CAPI3REF: File Locking Levels
674 **
675 ** SQLite uses one of these integer values as the second
@@ -812,10 +820,11 @@
812 ** <li> [SQLITE_IOCAP_SEQUENTIAL]
813 ** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN]
814 ** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE]
815 ** <li> [SQLITE_IOCAP_IMMUTABLE]
816 ** <li> [SQLITE_IOCAP_BATCH_ATOMIC]
 
817 ** </ul>
818 **
819 ** The SQLITE_IOCAP_ATOMIC property means that all writes of
820 ** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values
821 ** mean that writes of blocks that are nnn bytes in size and
@@ -1089,10 +1098,15 @@
1089 ** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This
1090 ** opcode causes the xFileControl method to swap the file handle with the one
1091 ** pointed to by the pArg argument. This capability is used during testing
1092 ** and only needs to be supported when SQLITE_TEST is defined.
1093 **
 
 
 
 
 
1094 ** <li>[[SQLITE_FCNTL_WAL_BLOCK]]
1095 ** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might
1096 ** be advantageous to block on the next WAL lock if the lock is not immediately
1097 ** available. The WAL subsystem issues this signal during rare
1098 ** circumstances in order to fix a problem with priority inversion.
@@ -1242,10 +1256,11 @@
1242 #define SQLITE_FCNTL_RESERVE_BYTES 38
1243 #define SQLITE_FCNTL_CKPT_START 39
1244 #define SQLITE_FCNTL_EXTERNAL_READER 40
1245 #define SQLITE_FCNTL_CKSM_FILE 41
1246 #define SQLITE_FCNTL_RESET_CACHE 42
 
1247
1248 /* deprecated names */
1249 #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
1250 #define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE
1251 #define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO
@@ -2620,14 +2635,18 @@
2620 **
2621 ** ^These functions return the number of rows modified, inserted or
2622 ** deleted by the most recently completed INSERT, UPDATE or DELETE
2623 ** statement on the database connection specified by the only parameter.
2624 ** The two functions are identical except for the type of the return value
2625 ** and that if the number of rows modified by the most recent INSERT, UPDATE
2626 ** or DELETE is greater than the maximum value supported by type "int", then
2627 ** the return value of sqlite3_changes() is undefined. ^Executing any other
2628 ** type of SQL statement does not modify the value returned by these functions.
 
 
 
 
2629 **
2630 ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are
2631 ** considered - auxiliary changes caused by [CREATE TRIGGER | triggers],
2632 ** [foreign key actions] or [REPLACE] constraint resolution are not counted.
2633 **
@@ -4183,15 +4202,26 @@
4183 **
4184 ** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt>
4185 ** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler
4186 ** to return an error (error code SQLITE_ERROR) if the statement uses
4187 ** any virtual tables.
 
 
 
 
 
 
 
 
 
 
4188 ** </dl>
4189 */
4190 #define SQLITE_PREPARE_PERSISTENT 0x01
4191 #define SQLITE_PREPARE_NORMALIZE 0x02
4192 #define SQLITE_PREPARE_NO_VTAB 0x04
 
4193
4194 /*
4195 ** CAPI3REF: Compiling An SQL Statement
4196 ** KEYWORDS: {SQL statement compiler}
4197 ** METHOD: sqlite3
@@ -10878,11 +10908,11 @@
10878 #endif
10879
10880 #ifdef __cplusplus
10881 } /* End of the 'extern "C"' block */
10882 #endif
10883 #endif /* SQLITE3_H */
10884
10885 /******** Begin file sqlite3rtree.h *********/
10886 /*
10887 ** 2010 August 30
10888 **
@@ -13129,17 +13159,32 @@
13129 ** This is used to access token iToken of phrase hit iIdx within the
13130 ** current row. If iIdx is less than zero or greater than or equal to the
13131 ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise,
13132 ** output variable (*ppToken) is set to point to a buffer containing the
13133 ** matching document token, and (*pnToken) to the size of that buffer in
13134 ** bytes. This API is not available if the specified token matches a
13135 ** prefix query term. In that case both output variables are always set
13136 ** to 0.
13137 **
13138 ** The output text is not a copy of the document text that was tokenized.
13139 ** It is the output of the tokenizer module. For tokendata=1 tables, this
13140 ** includes any embedded 0x00 and trailing data.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13141 **
13142 ** This API can be quite slow if used with an FTS5 table created with the
13143 ** "detail=none" or "detail=column" option.
13144 **
13145 ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale)
@@ -13570,5 +13615,6 @@
13570 #endif
13571
13572 #endif /* _FTS5_H */
13573
13574 /******** End of fts5.h *********/
 
13575
--- extsrc/sqlite3.h
+++ extsrc/sqlite3.h
@@ -144,13 +144,13 @@
144 **
145 ** See also: [sqlite3_libversion()],
146 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
147 ** [sqlite_version()] and [sqlite_source_id()].
148 */
149 #define SQLITE_VERSION "3.48.0"
150 #define SQLITE_VERSION_NUMBER 3048000
151 #define SQLITE_SOURCE_ID "2024-12-30 21:23:53 2b17bc49655c577029919c2d409de994b0d252f8efb5da1ba0913f2c96bee552"
152
153 /*
154 ** CAPI3REF: Run-Time Library Version Numbers
155 ** KEYWORDS: sqlite3_version sqlite3_sourceid
156 **
@@ -650,10 +650,17 @@
650 **
651 ** The SQLITE_IOCAP_BATCH_ATOMIC property means that the underlying
652 ** filesystem supports doing multiple write operations atomically when those
653 ** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and
654 ** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE].
655 **
656 ** The SQLITE_IOCAP_SUBPAGE_READ property means that it is ok to read
657 ** from the database file in amounts that are not a multiple of the
658 ** page size and that do not begin at a page boundary. Without this
659 ** property, SQLite is careful to only do full-page reads and write
660 ** on aligned pages, with the one exception that it will do a sub-page
661 ** read of the first page to access the database header.
662 */
663 #define SQLITE_IOCAP_ATOMIC 0x00000001
664 #define SQLITE_IOCAP_ATOMIC512 0x00000002
665 #define SQLITE_IOCAP_ATOMIC1K 0x00000004
666 #define SQLITE_IOCAP_ATOMIC2K 0x00000008
@@ -666,10 +673,11 @@
673 #define SQLITE_IOCAP_SEQUENTIAL 0x00000400
674 #define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800
675 #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000
676 #define SQLITE_IOCAP_IMMUTABLE 0x00002000
677 #define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000
678 #define SQLITE_IOCAP_SUBPAGE_READ 0x00008000
679
680 /*
681 ** CAPI3REF: File Locking Levels
682 **
683 ** SQLite uses one of these integer values as the second
@@ -812,10 +820,11 @@
820 ** <li> [SQLITE_IOCAP_SEQUENTIAL]
821 ** <li> [SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN]
822 ** <li> [SQLITE_IOCAP_POWERSAFE_OVERWRITE]
823 ** <li> [SQLITE_IOCAP_IMMUTABLE]
824 ** <li> [SQLITE_IOCAP_BATCH_ATOMIC]
825 ** <li> [SQLITE_IOCAP_SUBPAGE_READ]
826 ** </ul>
827 **
828 ** The SQLITE_IOCAP_ATOMIC property means that all writes of
829 ** any size are atomic. The SQLITE_IOCAP_ATOMICnnn values
830 ** mean that writes of blocks that are nnn bytes in size and
@@ -1089,10 +1098,15 @@
1098 ** The [SQLITE_FCNTL_WIN32_SET_HANDLE] opcode is used for debugging. This
1099 ** opcode causes the xFileControl method to swap the file handle with the one
1100 ** pointed to by the pArg argument. This capability is used during testing
1101 ** and only needs to be supported when SQLITE_TEST is defined.
1102 **
1103 ** <li>[[SQLITE_FCNTL_NULL_IO]]
1104 ** The [SQLITE_FCNTL_NULL_IO] opcode sets the low-level file descriptor
1105 ** or file handle for the [sqlite3_file] object such that it will no longer
1106 ** read or write to the database file.
1107 **
1108 ** <li>[[SQLITE_FCNTL_WAL_BLOCK]]
1109 ** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might
1110 ** be advantageous to block on the next WAL lock if the lock is not immediately
1111 ** available. The WAL subsystem issues this signal during rare
1112 ** circumstances in order to fix a problem with priority inversion.
@@ -1242,10 +1256,11 @@
1256 #define SQLITE_FCNTL_RESERVE_BYTES 38
1257 #define SQLITE_FCNTL_CKPT_START 39
1258 #define SQLITE_FCNTL_EXTERNAL_READER 40
1259 #define SQLITE_FCNTL_CKSM_FILE 41
1260 #define SQLITE_FCNTL_RESET_CACHE 42
1261 #define SQLITE_FCNTL_NULL_IO 43
1262
1263 /* deprecated names */
1264 #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE
1265 #define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE
1266 #define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO
@@ -2620,14 +2635,18 @@
2635 **
2636 ** ^These functions return the number of rows modified, inserted or
2637 ** deleted by the most recently completed INSERT, UPDATE or DELETE
2638 ** statement on the database connection specified by the only parameter.
2639 ** The two functions are identical except for the type of the return value
2640 ** and that if the number of rows modified by the most recent INSERT, UPDATE,
2641 ** or DELETE is greater than the maximum value supported by type "int", then
2642 ** the return value of sqlite3_changes() is undefined. ^Executing any other
2643 ** type of SQL statement does not modify the value returned by these functions.
2644 ** For the purposes of this interface, a CREATE TABLE AS SELECT statement
2645 ** does not count as an INSERT, UPDATE or DELETE statement and hence the rows
2646 ** added to the new table by the CREATE TABLE AS SELECT statement are not
2647 ** counted.
2648 **
2649 ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are
2650 ** considered - auxiliary changes caused by [CREATE TRIGGER | triggers],
2651 ** [foreign key actions] or [REPLACE] constraint resolution are not counted.
2652 **
@@ -4183,15 +4202,26 @@
4202 **
4203 ** [[SQLITE_PREPARE_NO_VTAB]] <dt>SQLITE_PREPARE_NO_VTAB</dt>
4204 ** <dd>The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler
4205 ** to return an error (error code SQLITE_ERROR) if the statement uses
4206 ** any virtual tables.
4207 **
4208 ** [[SQLITE_PREPARE_DONT_LOG]] <dt>SQLITE_PREPARE_DONT_LOG</dt>
4209 ** <dd>The SQLITE_PREPARE_DONT_LOG flag prevents SQL compiler
4210 ** errors from being sent to the error log defined by
4211 ** [SQLITE_CONFIG_LOG]. This can be used, for example, to do test
4212 ** compiles to see if some SQL syntax is well-formed, without generating
4213 ** messages on the global error log when it is not. If the test compile
4214 ** fails, the sqlite3_prepare_v3() call returns the same error indications
4215 ** with or without this flag; it just omits the call to [sqlite3_log()] that
4216 ** logs the error.
4217 ** </dl>
4218 */
4219 #define SQLITE_PREPARE_PERSISTENT 0x01
4220 #define SQLITE_PREPARE_NORMALIZE 0x02
4221 #define SQLITE_PREPARE_NO_VTAB 0x04
4222 #define SQLITE_PREPARE_DONT_LOG 0x10
4223
4224 /*
4225 ** CAPI3REF: Compiling An SQL Statement
4226 ** KEYWORDS: {SQL statement compiler}
4227 ** METHOD: sqlite3
@@ -10878,11 +10908,11 @@
10908 #endif
10909
10910 #ifdef __cplusplus
10911 } /* End of the 'extern "C"' block */
10912 #endif
10913 /* #endif for SQLITE3_H will be added by mksqlite3.tcl */
10914
10915 /******** Begin file sqlite3rtree.h *********/
10916 /*
10917 ** 2010 August 30
10918 **
@@ -13129,17 +13159,32 @@
13159 ** This is used to access token iToken of phrase hit iIdx within the
13160 ** current row. If iIdx is less than zero or greater than or equal to the
13161 ** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise,
13162 ** output variable (*ppToken) is set to point to a buffer containing the
13163 ** matching document token, and (*pnToken) to the size of that buffer in
13164 ** bytes.
 
 
13165 **
13166 ** The output text is not a copy of the document text that was tokenized.
13167 ** It is the output of the tokenizer module. For tokendata=1 tables, this
13168 ** includes any embedded 0x00 and trailing data.
13169 **
13170 ** This API may be slow in some cases if the token identified by parameters
13171 ** iIdx and iToken matched a prefix token in the query. In most cases, the
13172 ** first call to this API for each prefix token in the query is forced
13173 ** to scan the portion of the full-text index that matches the prefix
13174 ** token to collect the extra data required by this API. If the prefix
13175 ** token matches a large number of token instances in the document set,
13176 ** this may be a performance problem.
13177 **
13178 ** If the user knows in advance that a query may use this API for a
13179 ** prefix token, FTS5 may be configured to collect all required data as part
13180 ** of the initial querying of the full-text index, avoiding the second scan
13181 ** entirely. This also causes prefix queries that do not use this API to
13182 ** run more slowly and use more memory. FTS5 may be configured in this way
13183 ** either on a per-table basis using the [FTS5 insttoken | 'insttoken']
13184 ** option, or on a per-query basis using the
13185 ** [fts5_insttoken | fts5_insttoken()] user function.
13186 **
13187 ** This API can be quite slow if used with an FTS5 table created with the
13188 ** "detail=none" or "detail=column" option.
13189 **
13190 ** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale)
@@ -13570,5 +13615,6 @@
13615 #endif
13616
13617 #endif /* _FTS5_H */
13618
13619 /******** End of fts5.h *********/
13620 #endif /* SQLITE3_H */
13621
+22 -17
--- fossil.1
+++ fossil.1
@@ -1,6 +1,6 @@
1
-.TH FOSSIL "1" "July 2021" "https://fossil-scm.org" "User Commands"
1
+.TH FOSSIL "1" "Oct 2024" "https://fossil-scm.org" "User Commands"
22
.SH NAME
33
fossil \- Distributed Version Control System
44
.SH SYNOPSIS
55
.B fossil
66
\fIhelp\fR
@@ -14,26 +14,31 @@
1414
Fossil is a distributed version control system (DVCS) with built-in
1515
forum, wiki, ticket tracker, CGI/HTTP interface, and HTTP server.
1616
1717
.SH Common COMMANDs:
1818
19
-add cat diff ls revert timeline
20
-.br
21
-addremove changes extras merge rm ui
22
-.br
23
-all chat finfo mv settings undo
24
-.br
25
-amend clean gdiff open sql unversioned
26
-.br
27
-annotate clone grep pull stash update
28
-.br
29
-bisect commit help push status version
30
-.br
31
-blame dbstat info rebuild sync
32
-.br
33
-branch delete init remote tag
34
-.br
19
+add cherrypick grep push sync
20
+.br
21
+addremove clean help rebuild tag
22
+.br
23
+all clone info remote timeline
24
+.br
25
+amend commit init repack tree
26
+.br
27
+annotate dbstat ls revert ui
28
+.br
29
+bisect delete merge rm undo
30
+.br
31
+blame describe merge-base settings unversioned
32
+.br
33
+branch diff mv sql update
34
+.br
35
+cat extras open ssl-config version
36
+.br
37
+changes finfo patch stash xdiff
38
+.br
39
+chat gdiff pull status
3540
3641
.SH FEATURES
3742
3843
Features as described on the fossil home page.
3944
4045
--- fossil.1
+++ fossil.1
@@ -1,6 +1,6 @@
1 .TH FOSSIL "1" "July 2021" "https://fossil-scm.org" "User Commands"
2 .SH NAME
3 fossil \- Distributed Version Control System
4 .SH SYNOPSIS
5 .B fossil
6 \fIhelp\fR
@@ -14,26 +14,31 @@
14 Fossil is a distributed version control system (DVCS) with built-in
15 forum, wiki, ticket tracker, CGI/HTTP interface, and HTTP server.
16
17 .SH Common COMMANDs:
18
19 add cat diff ls revert timeline
20 .br
21 addremove changes extras merge rm ui
22 .br
23 all chat finfo mv settings undo
24 .br
25 amend clean gdiff open sql unversioned
26 .br
27 annotate clone grep pull stash update
28 .br
29 bisect commit help push status version
30 .br
31 blame dbstat info rebuild sync
32 .br
33 branch delete init remote tag
34 .br
 
 
 
 
 
35
36 .SH FEATURES
37
38 Features as described on the fossil home page.
39
40
--- fossil.1
+++ fossil.1
@@ -1,6 +1,6 @@
1 .TH FOSSIL "1" "Oct 2024" "https://fossil-scm.org" "User Commands"
2 .SH NAME
3 fossil \- Distributed Version Control System
4 .SH SYNOPSIS
5 .B fossil
6 \fIhelp\fR
@@ -14,26 +14,31 @@
14 Fossil is a distributed version control system (DVCS) with built-in
15 forum, wiki, ticket tracker, CGI/HTTP interface, and HTTP server.
16
17 .SH Common COMMANDs:
18
19 add cherrypick grep push sync
20 .br
21 addremove clean help rebuild tag
22 .br
23 all clone info remote timeline
24 .br
25 amend commit init repack tree
26 .br
27 annotate dbstat ls revert ui
28 .br
29 bisect delete merge rm undo
30 .br
31 blame describe merge-base settings unversioned
32 .br
33 branch diff mv sql update
34 .br
35 cat extras open ssl-config version
36 .br
37 changes finfo patch stash xdiff
38 .br
39 chat gdiff pull status
40
41 .SH FEATURES
42
43 Features as described on the fossil home page.
44
45
--- skins/darkmode/css.txt
+++ skins/darkmode/css.txt
@@ -98,14 +98,17 @@
9898
}
9999
.fileage tr:hover,
100100
div.filetreeline:hover {
101101
background-color: #333;
102102
}
103
+div.file-change-line button {
104
+ background-color: #484848
105
+}
103106
.button,
104107
button {
105108
color: #aaa;
106
- background-color: #444;
109
+ background-color: #484848;
107110
border-radius: 5px;
108111
border: 0
109112
}
110113
.button:hover,
111114
button:hover {
112115
--- skins/darkmode/css.txt
+++ skins/darkmode/css.txt
@@ -98,14 +98,17 @@
98 }
99 .fileage tr:hover,
100 div.filetreeline:hover {
101 background-color: #333;
102 }
 
 
 
103 .button,
104 button {
105 color: #aaa;
106 background-color: #444;
107 border-radius: 5px;
108 border: 0
109 }
110 .button:hover,
111 button:hover {
112
--- skins/darkmode/css.txt
+++ skins/darkmode/css.txt
@@ -98,14 +98,17 @@
98 }
99 .fileage tr:hover,
100 div.filetreeline:hover {
101 background-color: #333;
102 }
103 div.file-change-line button {
104 background-color: #484848
105 }
106 .button,
107 button {
108 color: #aaa;
109 background-color: #484848;
110 border-radius: 5px;
111 border: 0
112 }
113 .button:hover,
114 button:hover {
115
--- skins/default/css.txt
+++ skins/default/css.txt
@@ -42,10 +42,11 @@
4242
.artifact h1.page-title,
4343
.dir h1.page-title,
4444
.doc h1.page-title,
4545
.wiki h1.page-title {
4646
display: block; /* …for potentially long doc titles… */
47
+ color: #444;
4748
}
4849
.artifact .title > .page-title,
4950
.dir .title > .page-title,
5051
.doc .title > .page-title,
5152
.wiki .title > .page-title {
@@ -725,16 +726,16 @@
725726
margin-left: 20pt; /* special case for MD in forum; need less indent */
726727
}
727728
728729
/* Fossil UI uses these, but in sufficiently constrained ways that we
729730
* don't have to be nearly as careful to avoid an overreach. */
730
- .doc > .content h1, .artifact h1, .dir h1, .fileedit h1, .wiki h1 { margin-left: 10pt; }
731
- .doc > .content h2, .artifact h2, .dir h2, .fileedit h2, .wiki h2 { margin-left: 20pt; }
732
- .doc > .content h3, .artifact h3, .dir h3, .fileedit h3, .wiki h3 { margin-left: 30pt; }
733
- .doc > .content h4, .artifact h4, .dir h4, .fileedit h4, .wiki h4 { margin-left: 40pt; }
734
- .doc > .content h5, .artifact h5, .dir h5, .fileedit h5, .wiki h5 { margin-left: 50pt; }
735
- .doc > .content hr, .artifact hr, .dir hr, .fileedit hr, .wiki hr { margin-left: 10pt; }
731
+ .doc > .content h1, .artifact .content h1, .dir .content h1, .fileedit .content h1, .wiki .content h1 { margin-left: 10pt; }
732
+ .doc > .content h2, .artifact .content h2, .dir .content h2, .fileedit .content h2, .wiki .content h2 { margin-left: 20pt; }
733
+ .doc > .content h3, .artifact .content h3, .dir .content h3, .fileedit .content h3, .wiki .content h3 { margin-left: 30pt; }
734
+ .doc > .content h4, .artifact .content h4, .dir .content h4, .fileedit .content h4, .wiki .content h4 { margin-left: 40pt; }
735
+ .doc > .content h5, .artifact .content h5, .dir .content h5, .fileedit .content h5, .wiki .content h5 { margin-left: 50pt; }
736
+ .doc > .content hr, .artifact .content hr, .dir .content hr, .fileedit .content hr, .wiki .content hr { margin-left: 10pt; }
736737
737738
/* Don't need to be nearly as careful with tags Fossil UI doesn't use. */
738739
.doc dd, .artifact dd, .dir dd, .fileedit dd, .wikiedit dd { margin-left: 30pt; margin-bottom: 1em; }
739740
.doc dl, .artifact dl, .dir dl, .fileedit dl, .wikiedit dl { margin-left: 60pt; }
740741
.doc dt, .artifact dt, .dir dt, .fileedit dt, .wikiedit dt { margin-left: 10pt; }
741742
--- skins/default/css.txt
+++ skins/default/css.txt
@@ -42,10 +42,11 @@
42 .artifact h1.page-title,
43 .dir h1.page-title,
44 .doc h1.page-title,
45 .wiki h1.page-title {
46 display: block; /* …for potentially long doc titles… */
 
47 }
48 .artifact .title > .page-title,
49 .dir .title > .page-title,
50 .doc .title > .page-title,
51 .wiki .title > .page-title {
@@ -725,16 +726,16 @@
725 margin-left: 20pt; /* special case for MD in forum; need less indent */
726 }
727
728 /* Fossil UI uses these, but in sufficiently constrained ways that we
729 * don't have to be nearly as careful to avoid an overreach. */
730 .doc > .content h1, .artifact h1, .dir h1, .fileedit h1, .wiki h1 { margin-left: 10pt; }
731 .doc > .content h2, .artifact h2, .dir h2, .fileedit h2, .wiki h2 { margin-left: 20pt; }
732 .doc > .content h3, .artifact h3, .dir h3, .fileedit h3, .wiki h3 { margin-left: 30pt; }
733 .doc > .content h4, .artifact h4, .dir h4, .fileedit h4, .wiki h4 { margin-left: 40pt; }
734 .doc > .content h5, .artifact h5, .dir h5, .fileedit h5, .wiki h5 { margin-left: 50pt; }
735 .doc > .content hr, .artifact hr, .dir hr, .fileedit hr, .wiki hr { margin-left: 10pt; }
736
737 /* Don't need to be nearly as careful with tags Fossil UI doesn't use. */
738 .doc dd, .artifact dd, .dir dd, .fileedit dd, .wikiedit dd { margin-left: 30pt; margin-bottom: 1em; }
739 .doc dl, .artifact dl, .dir dl, .fileedit dl, .wikiedit dl { margin-left: 60pt; }
740 .doc dt, .artifact dt, .dir dt, .fileedit dt, .wikiedit dt { margin-left: 10pt; }
741
--- skins/default/css.txt
+++ skins/default/css.txt
@@ -42,10 +42,11 @@
42 .artifact h1.page-title,
43 .dir h1.page-title,
44 .doc h1.page-title,
45 .wiki h1.page-title {
46 display: block; /* …for potentially long doc titles… */
47 color: #444;
48 }
49 .artifact .title > .page-title,
50 .dir .title > .page-title,
51 .doc .title > .page-title,
52 .wiki .title > .page-title {
@@ -725,16 +726,16 @@
726 margin-left: 20pt; /* special case for MD in forum; need less indent */
727 }
728
729 /* Fossil UI uses these, but in sufficiently constrained ways that we
730 * don't have to be nearly as careful to avoid an overreach. */
731 .doc > .content h1, .artifact .content h1, .dir .content h1, .fileedit .content h1, .wiki .content h1 { margin-left: 10pt; }
732 .doc > .content h2, .artifact .content h2, .dir .content h2, .fileedit .content h2, .wiki .content h2 { margin-left: 20pt; }
733 .doc > .content h3, .artifact .content h3, .dir .content h3, .fileedit .content h3, .wiki .content h3 { margin-left: 30pt; }
734 .doc > .content h4, .artifact .content h4, .dir .content h4, .fileedit .content h4, .wiki .content h4 { margin-left: 40pt; }
735 .doc > .content h5, .artifact .content h5, .dir .content h5, .fileedit .content h5, .wiki .content h5 { margin-left: 50pt; }
736 .doc > .content hr, .artifact .content hr, .dir .content hr, .fileedit .content hr, .wiki .content hr { margin-left: 10pt; }
737
738 /* Don't need to be nearly as careful with tags Fossil UI doesn't use. */
739 .doc dd, .artifact dd, .dir dd, .fileedit dd, .wikiedit dd { margin-left: 30pt; margin-bottom: 1em; }
740 .doc dl, .artifact dl, .dir dl, .fileedit dl, .wikiedit dl { margin-left: 60pt; }
741 .doc dt, .artifact dt, .dir dt, .fileedit dt, .wikiedit dt { margin-left: 10pt; }
742
+2 -3
--- src/add.c
+++ src/add.c
@@ -353,11 +353,11 @@
353353
** for files to be excluded. Example: '*.o,*.obj,*.exe' If the --ignore
354354
** option does not appear on the command line then the "ignore-glob" setting
355355
** is used. If the --clean option does not appear on the command line then
356356
** the "clean-glob" setting is used.
357357
**
358
-** If files are attempted to be added explicitly on the command line which
358
+** When attempting to explicitly add files on the commandline, and if those
359359
** match "ignore-glob", a confirmation is asked first. This can be prevented
360360
** using the -f|--force option.
361361
**
362362
** The --case-sensitive option determines whether or not filenames should
363363
** be treated case sensitive or not. If the option is not given, the default
@@ -753,12 +753,11 @@
753753
**
754754
** * All files in the repository but missing from the check-out (that is,
755755
** all files that show as MISSING with the "status" command) are
756756
** removed as if by the "[[rm]]" command.
757757
**
758
-** The command does not "[[commit]]". You must run the "[[commit]]" separately
759
-** as a separate step.
758
+** Note that this command does not "commit", as that is a separate step.
760759
**
761760
** Files and directories whose names begin with "." are ignored unless
762761
** the --dotfiles option is used.
763762
**
764763
** The --ignore option overrides the "ignore-glob" setting, as do the
765764
--- src/add.c
+++ src/add.c
@@ -353,11 +353,11 @@
353 ** for files to be excluded. Example: '*.o,*.obj,*.exe' If the --ignore
354 ** option does not appear on the command line then the "ignore-glob" setting
355 ** is used. If the --clean option does not appear on the command line then
356 ** the "clean-glob" setting is used.
357 **
358 ** If files are attempted to be added explicitly on the command line which
359 ** match "ignore-glob", a confirmation is asked first. This can be prevented
360 ** using the -f|--force option.
361 **
362 ** The --case-sensitive option determines whether or not filenames should
363 ** be treated case sensitive or not. If the option is not given, the default
@@ -753,12 +753,11 @@
753 **
754 ** * All files in the repository but missing from the check-out (that is,
755 ** all files that show as MISSING with the "status" command) are
756 ** removed as if by the "[[rm]]" command.
757 **
758 ** The command does not "[[commit]]". You must run the "[[commit]]" separately
759 ** as a separate step.
760 **
761 ** Files and directories whose names begin with "." are ignored unless
762 ** the --dotfiles option is used.
763 **
764 ** The --ignore option overrides the "ignore-glob" setting, as do the
765
--- src/add.c
+++ src/add.c
@@ -353,11 +353,11 @@
353 ** for files to be excluded. Example: '*.o,*.obj,*.exe' If the --ignore
354 ** option does not appear on the command line then the "ignore-glob" setting
355 ** is used. If the --clean option does not appear on the command line then
356 ** the "clean-glob" setting is used.
357 **
358 ** When attempting to explicitly add files on the commandline, and if those
359 ** match "ignore-glob", a confirmation is asked first. This can be prevented
360 ** using the -f|--force option.
361 **
362 ** The --case-sensitive option determines whether or not filenames should
363 ** be treated case sensitive or not. If the option is not given, the default
@@ -753,12 +753,11 @@
753 **
754 ** * All files in the repository but missing from the check-out (that is,
755 ** all files that show as MISSING with the "status" command) are
756 ** removed as if by the "[[rm]]" command.
757 **
758 ** Note that this command does not "commit", as that is a separate step.
 
759 **
760 ** Files and directories whose names begin with "." are ignored unless
761 ** the --dotfiles option is used.
762 **
763 ** The --ignore option overrides the "ignore-glob" setting, as do the
764
+23 -6
--- src/allrepo.c
+++ src/allrepo.c
@@ -50,11 +50,10 @@
5050
for(i=iStart; i<g.argc; i++){
5151
blob_appendf(pExtra, " %s", g.argv[i]);
5252
}
5353
}
5454
55
-
5655
/*
5756
** COMMAND: all
5857
**
5958
** Usage: %fossil all SUBCOMMAND ...
6059
**
@@ -429,44 +428,62 @@
429428
"add cache changes clean dbstat extras fts-config git ignore "
430429
"info list ls pull push rebuild remote "
431430
"server settings sync ui unset whatis");
432431
}
433432
verify_all_options();
434
- db_multi_exec("CREATE TEMP TABLE repolist(name,tag);");
433
+ db_multi_exec(
434
+ "CREATE TEMP TABLE repolist(\n"
435
+ " name TEXT, -- Filename\n"
436
+ " tag TEXT, -- Key for the GLOBAL_CONFIG table entry\n"
437
+ " inode TEXT -- Unique identifier for this file\n"
438
+ ");\n"
439
+
440
+ /* The seenFile() table holds inode names for entries that have
441
+ ** already been processed. */
442
+ "CREATE TEMP TABLE seenFile(x TEXT COLLATE nocase);\n"
443
+
444
+ /* The toDel() table holds the "tag" for entries that need to be
445
+ ** deleted because they are redundant or no longer exist */
446
+ "CREATE TEMP TABLE toDel(x TEXT);\n"
447
+ );
448
+ sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
449
+ file_inode_sql_func, 0, 0);
435450
if( useCheckouts ){
436451
db_multi_exec(
437452
"INSERT INTO repolist "
438
- "SELECT DISTINCT substr(name, 7), name COLLATE nocase"
453
+ "SELECT substr(name, 7), name, inode(substr(name,7))"
439454
" FROM global_config"
440455
" WHERE substr(name, 1, 6)=='ckout:'"
441456
" ORDER BY 1"
442457
);
443458
}else{
444459
db_multi_exec(
445460
"INSERT INTO repolist "
446
- "SELECT DISTINCT substr(name, 6), name COLLATE nocase"
461
+ "SELECT substr(name, 6), name, inode(substr(name,6))"
447462
" FROM global_config"
448463
" WHERE substr(name, 1, 5)=='repo:'"
449464
" ORDER BY 1"
450465
);
451466
}
452
- db_multi_exec("CREATE TEMP TABLE toDel(x TEXT)");
453
- db_prepare(&q, "SELECT name, tag FROM repolist ORDER BY 1");
467
+ db_prepare(&q,"SELECT name, tag, inode FROM repolist ORDER BY 1");
454468
while( db_step(&q)==SQLITE_ROW ){
455469
int rc;
456470
const char *zFilename = db_column_text(&q, 0);
471
+ const char *zInode = db_column_text(&q,2);
457472
#if !USE_SEE
458473
if( sqlite3_strglob("*.efossil", zFilename)==0 ) continue;
459474
#endif
460475
if( file_access(zFilename, F_OK)
461476
|| !file_is_canonical(zFilename)
462477
|| (useCheckouts && file_isdir(zFilename, ExtFILE)!=1)
478
+ || db_exists("SELECT 1 FROM temp.seenFile where x=%Q", zInode)
463479
){
464480
db_multi_exec("INSERT INTO toDel VALUES(%Q)", db_column_text(&q, 1));
465481
nToDel++;
466482
continue;
467483
}
484
+ db_multi_exec("INSERT INTO seenFile(x) VALUES(%Q)", zInode);
468485
if( zCmd[0]=='l' ){
469486
fossil_print("%s\n", zFilename);
470487
continue;
471488
}else if( showFile ){
472489
fossil_print("%s: %s\n", useCheckouts ? "check-out" : "repository",
473490
--- src/allrepo.c
+++ src/allrepo.c
@@ -50,11 +50,10 @@
50 for(i=iStart; i<g.argc; i++){
51 blob_appendf(pExtra, " %s", g.argv[i]);
52 }
53 }
54
55
56 /*
57 ** COMMAND: all
58 **
59 ** Usage: %fossil all SUBCOMMAND ...
60 **
@@ -429,44 +428,62 @@
429 "add cache changes clean dbstat extras fts-config git ignore "
430 "info list ls pull push rebuild remote "
431 "server settings sync ui unset whatis");
432 }
433 verify_all_options();
434 db_multi_exec("CREATE TEMP TABLE repolist(name,tag);");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435 if( useCheckouts ){
436 db_multi_exec(
437 "INSERT INTO repolist "
438 "SELECT DISTINCT substr(name, 7), name COLLATE nocase"
439 " FROM global_config"
440 " WHERE substr(name, 1, 6)=='ckout:'"
441 " ORDER BY 1"
442 );
443 }else{
444 db_multi_exec(
445 "INSERT INTO repolist "
446 "SELECT DISTINCT substr(name, 6), name COLLATE nocase"
447 " FROM global_config"
448 " WHERE substr(name, 1, 5)=='repo:'"
449 " ORDER BY 1"
450 );
451 }
452 db_multi_exec("CREATE TEMP TABLE toDel(x TEXT)");
453 db_prepare(&q, "SELECT name, tag FROM repolist ORDER BY 1");
454 while( db_step(&q)==SQLITE_ROW ){
455 int rc;
456 const char *zFilename = db_column_text(&q, 0);
 
457 #if !USE_SEE
458 if( sqlite3_strglob("*.efossil", zFilename)==0 ) continue;
459 #endif
460 if( file_access(zFilename, F_OK)
461 || !file_is_canonical(zFilename)
462 || (useCheckouts && file_isdir(zFilename, ExtFILE)!=1)
 
463 ){
464 db_multi_exec("INSERT INTO toDel VALUES(%Q)", db_column_text(&q, 1));
465 nToDel++;
466 continue;
467 }
 
468 if( zCmd[0]=='l' ){
469 fossil_print("%s\n", zFilename);
470 continue;
471 }else if( showFile ){
472 fossil_print("%s: %s\n", useCheckouts ? "check-out" : "repository",
473
--- src/allrepo.c
+++ src/allrepo.c
@@ -50,11 +50,10 @@
50 for(i=iStart; i<g.argc; i++){
51 blob_appendf(pExtra, " %s", g.argv[i]);
52 }
53 }
54
 
55 /*
56 ** COMMAND: all
57 **
58 ** Usage: %fossil all SUBCOMMAND ...
59 **
@@ -429,44 +428,62 @@
428 "add cache changes clean dbstat extras fts-config git ignore "
429 "info list ls pull push rebuild remote "
430 "server settings sync ui unset whatis");
431 }
432 verify_all_options();
433 db_multi_exec(
434 "CREATE TEMP TABLE repolist(\n"
435 " name TEXT, -- Filename\n"
436 " tag TEXT, -- Key for the GLOBAL_CONFIG table entry\n"
437 " inode TEXT -- Unique identifier for this file\n"
438 ");\n"
439
440 /* The seenFile() table holds inode names for entries that have
441 ** already been processed. */
442 "CREATE TEMP TABLE seenFile(x TEXT COLLATE nocase);\n"
443
444 /* The toDel() table holds the "tag" for entries that need to be
445 ** deleted because they are redundant or no longer exist */
446 "CREATE TEMP TABLE toDel(x TEXT);\n"
447 );
448 sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
449 file_inode_sql_func, 0, 0);
450 if( useCheckouts ){
451 db_multi_exec(
452 "INSERT INTO repolist "
453 "SELECT substr(name, 7), name, inode(substr(name,7))"
454 " FROM global_config"
455 " WHERE substr(name, 1, 6)=='ckout:'"
456 " ORDER BY 1"
457 );
458 }else{
459 db_multi_exec(
460 "INSERT INTO repolist "
461 "SELECT substr(name, 6), name, inode(substr(name,6))"
462 " FROM global_config"
463 " WHERE substr(name, 1, 5)=='repo:'"
464 " ORDER BY 1"
465 );
466 }
467 db_prepare(&q,"SELECT name, tag, inode FROM repolist ORDER BY 1");
 
468 while( db_step(&q)==SQLITE_ROW ){
469 int rc;
470 const char *zFilename = db_column_text(&q, 0);
471 const char *zInode = db_column_text(&q,2);
472 #if !USE_SEE
473 if( sqlite3_strglob("*.efossil", zFilename)==0 ) continue;
474 #endif
475 if( file_access(zFilename, F_OK)
476 || !file_is_canonical(zFilename)
477 || (useCheckouts && file_isdir(zFilename, ExtFILE)!=1)
478 || db_exists("SELECT 1 FROM temp.seenFile where x=%Q", zInode)
479 ){
480 db_multi_exec("INSERT INTO toDel VALUES(%Q)", db_column_text(&q, 1));
481 nToDel++;
482 continue;
483 }
484 db_multi_exec("INSERT INTO seenFile(x) VALUES(%Q)", zInode);
485 if( zCmd[0]=='l' ){
486 fossil_print("%s\n", zFilename);
487 continue;
488 }else if( showFile ){
489 fossil_print("%s: %s\n", useCheckouts ? "check-out" : "repository",
490
+8 -1
--- src/attach.c
+++ src/attach.c
@@ -634,11 +634,12 @@
634634
/*
635635
** Output HTML to show a list of attachments.
636636
*/
637637
void attachment_list(
638638
const char *zTarget, /* Object that things are attached to */
639
- const char *zHeader /* Header to display with attachments */
639
+ const char *zHeader, /* Header to display with attachments */
640
+ int fHorizontalRule /* Insert <hr> separator above header */
640641
){
641642
int cnt = 0;
642643
Stmt q;
643644
db_prepare(&q,
644645
"SELECT datetime(mtime,toLocal()), filename, user,"
@@ -654,11 +655,16 @@
654655
const char *zUser = db_column_text(&q, 2);
655656
const char *zUuid = db_column_text(&q, 3);
656657
const char *zSrc = db_column_text(&q, 4);
657658
const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
658659
if( cnt==0 ){
660
+ @ <section class='attachlist'>
661
+ if( fHorizontalRule ){
662
+ @ <hr>
663
+ }
659664
@ %s(zHeader)
665
+ @ <ul>
660666
}
661667
cnt++;
662668
@ <li>
663669
@ %z(href("%R/artifact/%!S",zSrc))%h(zFile)</a>
664670
@ [<a href="%R/attachdownload/%t(zFile)?page=%t(zTarget)&file=%t(zFile)">download</a>]
@@ -667,10 +673,11 @@
667673
@ [%z(href("%R/ainfo/%!S",zUuid))details</a>]
668674
@ </li>
669675
}
670676
if( cnt ){
671677
@ </ul>
678
+ @ </section>
672679
}
673680
db_finalize(&q);
674681
675682
}
676683
677684
--- src/attach.c
+++ src/attach.c
@@ -634,11 +634,12 @@
634 /*
635 ** Output HTML to show a list of attachments.
636 */
637 void attachment_list(
638 const char *zTarget, /* Object that things are attached to */
639 const char *zHeader /* Header to display with attachments */
 
640 ){
641 int cnt = 0;
642 Stmt q;
643 db_prepare(&q,
644 "SELECT datetime(mtime,toLocal()), filename, user,"
@@ -654,11 +655,16 @@
654 const char *zUser = db_column_text(&q, 2);
655 const char *zUuid = db_column_text(&q, 3);
656 const char *zSrc = db_column_text(&q, 4);
657 const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
658 if( cnt==0 ){
 
 
 
 
659 @ %s(zHeader)
 
660 }
661 cnt++;
662 @ <li>
663 @ %z(href("%R/artifact/%!S",zSrc))%h(zFile)</a>
664 @ [<a href="%R/attachdownload/%t(zFile)?page=%t(zTarget)&file=%t(zFile)">download</a>]
@@ -667,10 +673,11 @@
667 @ [%z(href("%R/ainfo/%!S",zUuid))details</a>]
668 @ </li>
669 }
670 if( cnt ){
671 @ </ul>
 
672 }
673 db_finalize(&q);
674
675 }
676
677
--- src/attach.c
+++ src/attach.c
@@ -634,11 +634,12 @@
634 /*
635 ** Output HTML to show a list of attachments.
636 */
637 void attachment_list(
638 const char *zTarget, /* Object that things are attached to */
639 const char *zHeader, /* Header to display with attachments */
640 int fHorizontalRule /* Insert <hr> separator above header */
641 ){
642 int cnt = 0;
643 Stmt q;
644 db_prepare(&q,
645 "SELECT datetime(mtime,toLocal()), filename, user,"
@@ -654,11 +655,16 @@
655 const char *zUser = db_column_text(&q, 2);
656 const char *zUuid = db_column_text(&q, 3);
657 const char *zSrc = db_column_text(&q, 4);
658 const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
659 if( cnt==0 ){
660 @ <section class='attachlist'>
661 if( fHorizontalRule ){
662 @ <hr>
663 }
664 @ %s(zHeader)
665 @ <ul>
666 }
667 cnt++;
668 @ <li>
669 @ %z(href("%R/artifact/%!S",zSrc))%h(zFile)</a>
670 @ [<a href="%R/attachdownload/%t(zFile)?page=%t(zTarget)&file=%t(zFile)">download</a>]
@@ -667,10 +673,11 @@
673 @ [%z(href("%R/ainfo/%!S",zUuid))details</a>]
674 @ </li>
675 }
676 if( cnt ){
677 @ </ul>
678 @ </section>
679 }
680 db_finalize(&q);
681
682 }
683
684
+38 -27
--- src/bisect.c
+++ src/bisect.c
@@ -197,10 +197,26 @@
197197
static void bisect_append_skip(int rid){
198198
db_multi_exec(
199199
"UPDATE vvar SET value=value||' s%d' WHERE name='bisect-log'", rid
200200
);
201201
}
202
+
203
+/*
204
+** Append a VALUES entry to the bilog table insert
205
+*/
206
+static void bisect_log_append(Blob *pSql,int iSeq,const char *zStat,int iRid){
207
+ if( (iSeq%6)==3 ){
208
+ blob_append_sql(pSql, ",\n ");
209
+ }else if( iSeq>1 ){
210
+ blob_append_sql(pSql, ",");
211
+ }
212
+ if( zStat ){
213
+ blob_append_sql(pSql, "(%d,%Q,%d)", iSeq, zStat, iRid);
214
+ }else{
215
+ blob_append_sql(pSql, "(NULL,NULL,%d)", iRid);
216
+ }
217
+}
202218
203219
/*
204220
** Create a TEMP table named "bilog" that contains the complete history
205221
** of the current bisect.
206222
**
@@ -215,14 +231,14 @@
215231
** in between the inner-most GOOD and BAD nodes.
216232
*/
217233
int bisect_create_bilog_table(int iCurrent, const char *zDesc, int bDetail){
218234
char *zLog;
219235
Blob log, id;
220
- Stmt q;
221236
int cnt = 0;
222237
int lastGood = -1;
223238
int lastBad = -1;
239
+ Blob ins = BLOB_INITIALIZER;
224240
225241
if( zDesc!=0 ){
226242
blob_init(&log, 0, 0);
227243
while( zDesc[0]=='y' || zDesc[0]=='n' || zDesc[0]=='s' ){
228244
int i;
@@ -253,55 +269,42 @@
253269
" rid INTEGER PRIMARY KEY," /* Sequence of events */
254270
" stat TEXT," /* Type of occurrence */
255271
" seq INTEGER UNIQUE" /* Check-in number */
256272
");"
257273
);
258
- db_prepare(&q, "INSERT OR IGNORE INTO bilog(seq,stat,rid)"
259
- " VALUES(:seq,:stat,:rid)");
274
+ blob_append_sql(&ins, "INSERT OR IGNORE INTO bilog(seq,stat,rid) VALUES");
260275
while( blob_token(&log, &id) ){
261276
int rid;
262
- db_bind_int(&q, ":seq", ++cnt);
277
+ cnt++;
263278
if( blob_str(&id)[0]=='s' ){
264279
rid = atoi(blob_str(&id)+1);
265
- db_bind_text(&q, ":stat", "SKIP");
266
- db_bind_int(&q, ":rid", rid);
280
+ bisect_log_append(&ins, cnt, "SKIP", rid);
267281
}else{
268282
rid = atoi(blob_str(&id));
269283
if( rid>0 ){
270
- db_bind_text(&q, ":stat","GOOD");
271
- db_bind_int(&q, ":rid", rid);
284
+ bisect_log_append(&ins, cnt, "GOOD", rid);
272285
lastGood = rid;
273286
}else{
274
- db_bind_text(&q, ":stat", "BAD");
275
- db_bind_int(&q, ":rid", -rid);
287
+ bisect_log_append(&ins, cnt, "BAD", rid);
276288
lastBad = -rid;
277289
}
278290
}
279
- db_step(&q);
280
- db_reset(&q);
281291
}
282292
if( iCurrent>0 ){
283
- db_bind_int(&q, ":seq", ++cnt);
284
- db_bind_text(&q, ":stat", "CURRENT");
285
- db_bind_int(&q, ":rid", iCurrent);
286
- db_step(&q);
287
- db_reset(&q);
293
+ bisect_log_append(&ins, ++cnt, "CURRENT", iCurrent);
288294
}
289295
if( bDetail && lastGood>0 && lastBad>0 ){
290296
PathNode *p;
291297
p = path_shortest(lastGood, lastBad, bisect_option("direct-only"),0, 0);
292298
while( p ){
293
- db_bind_null(&q, ":seq");
294
- db_bind_null(&q, ":stat");
295
- db_bind_int(&q, ":rid", p->rid);
296
- db_step(&q);
297
- db_reset(&q);
299
+ bisect_log_append(&ins, ++cnt, 0, p->rid);
298300
p = p->u.pTo;
299301
}
300302
path_reset();
301303
}
302
- db_finalize(&q);
304
+ db_exec_sql(blob_sql_text(&ins));
305
+ blob_reset(&ins);
303306
return 1;
304307
}
305308
306309
/* Return a permalink description of a bisect. Space is obtained from
307310
** fossil_malloc() and should be freed by the caller.
@@ -543,14 +546,16 @@
543546
**
544547
** > fossil bisect vlist|ls|status ?-a|--all?
545548
**
546549
** List the versions in between the inner-most "bad" and "good".
547550
**
548
-** > fossil bisect ui
551
+** > fossil bisect ui ?HOST@USER:PATH?
549552
**
550553
** Like "fossil ui" except start on a timeline that shows only the
551
-** check-ins that are part of the current bisect.
554
+** check-ins that are part of the current bisect. If the optional
555
+** fourth term is added, then information is shown for the bisect that
556
+** occurred in the PATH directory by USER on remote machine HOST.
552557
**
553558
** > fossil bisect undo
554559
**
555560
** Undo the most recent "good", "bad", or "skip" command.
556561
*/
@@ -719,17 +724,23 @@
719724
}
720725
}else if( strncmp(zCmd, "reset", n)==0 ){
721726
bisect_reset();
722727
}else if( strcmp(zCmd, "ui")==0 ){
723728
char *newArgv[8];
729
+ verify_all_options();
724730
newArgv[0] = g.argv[0];
725731
newArgv[1] = "ui";
726732
newArgv[2] = "--page";
727733
newArgv[3] = "timeline?bisect";
728
- newArgv[4] = 0;
734
+ if( g.argc==4 ){
735
+ newArgv[4] = g.argv[3];
736
+ g.argc = 5;
737
+ }else{
738
+ g.argc = 4;
739
+ }
740
+ newArgv[g.argc] = 0;
729741
g.argv = newArgv;
730
- g.argc = 4;
731742
cmd_webserver();
732743
}else if( strncmp(zCmd, "vlist", n)==0
733744
|| strncmp(zCmd, "ls", n)==0
734745
|| strncmp(zCmd, "status", n)==0
735746
){
736747
--- src/bisect.c
+++ src/bisect.c
@@ -197,10 +197,26 @@
197 static void bisect_append_skip(int rid){
198 db_multi_exec(
199 "UPDATE vvar SET value=value||' s%d' WHERE name='bisect-log'", rid
200 );
201 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
203 /*
204 ** Create a TEMP table named "bilog" that contains the complete history
205 ** of the current bisect.
206 **
@@ -215,14 +231,14 @@
215 ** in between the inner-most GOOD and BAD nodes.
216 */
217 int bisect_create_bilog_table(int iCurrent, const char *zDesc, int bDetail){
218 char *zLog;
219 Blob log, id;
220 Stmt q;
221 int cnt = 0;
222 int lastGood = -1;
223 int lastBad = -1;
 
224
225 if( zDesc!=0 ){
226 blob_init(&log, 0, 0);
227 while( zDesc[0]=='y' || zDesc[0]=='n' || zDesc[0]=='s' ){
228 int i;
@@ -253,55 +269,42 @@
253 " rid INTEGER PRIMARY KEY," /* Sequence of events */
254 " stat TEXT," /* Type of occurrence */
255 " seq INTEGER UNIQUE" /* Check-in number */
256 ");"
257 );
258 db_prepare(&q, "INSERT OR IGNORE INTO bilog(seq,stat,rid)"
259 " VALUES(:seq,:stat,:rid)");
260 while( blob_token(&log, &id) ){
261 int rid;
262 db_bind_int(&q, ":seq", ++cnt);
263 if( blob_str(&id)[0]=='s' ){
264 rid = atoi(blob_str(&id)+1);
265 db_bind_text(&q, ":stat", "SKIP");
266 db_bind_int(&q, ":rid", rid);
267 }else{
268 rid = atoi(blob_str(&id));
269 if( rid>0 ){
270 db_bind_text(&q, ":stat","GOOD");
271 db_bind_int(&q, ":rid", rid);
272 lastGood = rid;
273 }else{
274 db_bind_text(&q, ":stat", "BAD");
275 db_bind_int(&q, ":rid", -rid);
276 lastBad = -rid;
277 }
278 }
279 db_step(&q);
280 db_reset(&q);
281 }
282 if( iCurrent>0 ){
283 db_bind_int(&q, ":seq", ++cnt);
284 db_bind_text(&q, ":stat", "CURRENT");
285 db_bind_int(&q, ":rid", iCurrent);
286 db_step(&q);
287 db_reset(&q);
288 }
289 if( bDetail && lastGood>0 && lastBad>0 ){
290 PathNode *p;
291 p = path_shortest(lastGood, lastBad, bisect_option("direct-only"),0, 0);
292 while( p ){
293 db_bind_null(&q, ":seq");
294 db_bind_null(&q, ":stat");
295 db_bind_int(&q, ":rid", p->rid);
296 db_step(&q);
297 db_reset(&q);
298 p = p->u.pTo;
299 }
300 path_reset();
301 }
302 db_finalize(&q);
 
303 return 1;
304 }
305
306 /* Return a permalink description of a bisect. Space is obtained from
307 ** fossil_malloc() and should be freed by the caller.
@@ -543,14 +546,16 @@
543 **
544 ** > fossil bisect vlist|ls|status ?-a|--all?
545 **
546 ** List the versions in between the inner-most "bad" and "good".
547 **
548 ** > fossil bisect ui
549 **
550 ** Like "fossil ui" except start on a timeline that shows only the
551 ** check-ins that are part of the current bisect.
 
 
552 **
553 ** > fossil bisect undo
554 **
555 ** Undo the most recent "good", "bad", or "skip" command.
556 */
@@ -719,17 +724,23 @@
719 }
720 }else if( strncmp(zCmd, "reset", n)==0 ){
721 bisect_reset();
722 }else if( strcmp(zCmd, "ui")==0 ){
723 char *newArgv[8];
 
724 newArgv[0] = g.argv[0];
725 newArgv[1] = "ui";
726 newArgv[2] = "--page";
727 newArgv[3] = "timeline?bisect";
728 newArgv[4] = 0;
 
 
 
 
 
 
729 g.argv = newArgv;
730 g.argc = 4;
731 cmd_webserver();
732 }else if( strncmp(zCmd, "vlist", n)==0
733 || strncmp(zCmd, "ls", n)==0
734 || strncmp(zCmd, "status", n)==0
735 ){
736
--- src/bisect.c
+++ src/bisect.c
@@ -197,10 +197,26 @@
197 static void bisect_append_skip(int rid){
198 db_multi_exec(
199 "UPDATE vvar SET value=value||' s%d' WHERE name='bisect-log'", rid
200 );
201 }
202
203 /*
204 ** Append a VALUES entry to the bilog table insert
205 */
206 static void bisect_log_append(Blob *pSql,int iSeq,const char *zStat,int iRid){
207 if( (iSeq%6)==3 ){
208 blob_append_sql(pSql, ",\n ");
209 }else if( iSeq>1 ){
210 blob_append_sql(pSql, ",");
211 }
212 if( zStat ){
213 blob_append_sql(pSql, "(%d,%Q,%d)", iSeq, zStat, iRid);
214 }else{
215 blob_append_sql(pSql, "(NULL,NULL,%d)", iRid);
216 }
217 }
218
219 /*
220 ** Create a TEMP table named "bilog" that contains the complete history
221 ** of the current bisect.
222 **
@@ -215,14 +231,14 @@
231 ** in between the inner-most GOOD and BAD nodes.
232 */
233 int bisect_create_bilog_table(int iCurrent, const char *zDesc, int bDetail){
234 char *zLog;
235 Blob log, id;
 
236 int cnt = 0;
237 int lastGood = -1;
238 int lastBad = -1;
239 Blob ins = BLOB_INITIALIZER;
240
241 if( zDesc!=0 ){
242 blob_init(&log, 0, 0);
243 while( zDesc[0]=='y' || zDesc[0]=='n' || zDesc[0]=='s' ){
244 int i;
@@ -253,55 +269,42 @@
269 " rid INTEGER PRIMARY KEY," /* Sequence of events */
270 " stat TEXT," /* Type of occurrence */
271 " seq INTEGER UNIQUE" /* Check-in number */
272 ");"
273 );
274 blob_append_sql(&ins, "INSERT OR IGNORE INTO bilog(seq,stat,rid) VALUES");
 
275 while( blob_token(&log, &id) ){
276 int rid;
277 cnt++;
278 if( blob_str(&id)[0]=='s' ){
279 rid = atoi(blob_str(&id)+1);
280 bisect_log_append(&ins, cnt, "SKIP", rid);
 
281 }else{
282 rid = atoi(blob_str(&id));
283 if( rid>0 ){
284 bisect_log_append(&ins, cnt, "GOOD", rid);
 
285 lastGood = rid;
286 }else{
287 bisect_log_append(&ins, cnt, "BAD", rid);
 
288 lastBad = -rid;
289 }
290 }
 
 
291 }
292 if( iCurrent>0 ){
293 bisect_log_append(&ins, ++cnt, "CURRENT", iCurrent);
 
 
 
 
294 }
295 if( bDetail && lastGood>0 && lastBad>0 ){
296 PathNode *p;
297 p = path_shortest(lastGood, lastBad, bisect_option("direct-only"),0, 0);
298 while( p ){
299 bisect_log_append(&ins, ++cnt, 0, p->rid);
 
 
 
 
300 p = p->u.pTo;
301 }
302 path_reset();
303 }
304 db_exec_sql(blob_sql_text(&ins));
305 blob_reset(&ins);
306 return 1;
307 }
308
309 /* Return a permalink description of a bisect. Space is obtained from
310 ** fossil_malloc() and should be freed by the caller.
@@ -543,14 +546,16 @@
546 **
547 ** > fossil bisect vlist|ls|status ?-a|--all?
548 **
549 ** List the versions in between the inner-most "bad" and "good".
550 **
551 ** > fossil bisect ui ?HOST@USER:PATH?
552 **
553 ** Like "fossil ui" except start on a timeline that shows only the
554 ** check-ins that are part of the current bisect. If the optional
555 ** fourth term is added, then information is shown for the bisect that
556 ** occurred in the PATH directory by USER on remote machine HOST.
557 **
558 ** > fossil bisect undo
559 **
560 ** Undo the most recent "good", "bad", or "skip" command.
561 */
@@ -719,17 +724,23 @@
724 }
725 }else if( strncmp(zCmd, "reset", n)==0 ){
726 bisect_reset();
727 }else if( strcmp(zCmd, "ui")==0 ){
728 char *newArgv[8];
729 verify_all_options();
730 newArgv[0] = g.argv[0];
731 newArgv[1] = "ui";
732 newArgv[2] = "--page";
733 newArgv[3] = "timeline?bisect";
734 if( g.argc==4 ){
735 newArgv[4] = g.argv[3];
736 g.argc = 5;
737 }else{
738 g.argc = 4;
739 }
740 newArgv[g.argc] = 0;
741 g.argv = newArgv;
 
742 cmd_webserver();
743 }else if( strncmp(zCmd, "vlist", n)==0
744 || strncmp(zCmd, "ls", n)==0
745 || strncmp(zCmd, "status", n)==0
746 ){
747
+49 -1
--- src/blob.c
+++ src/blob.c
@@ -665,11 +665,12 @@
665665
pBlob->nUsed = dehttpize(pBlob->aData);
666666
}
667667
668668
/*
669669
** Extract N bytes from blob pFrom and use it to initialize blob pTo.
670
-** Return the actual number of bytes extracted.
670
+** Return the actual number of bytes extracted. The cursor position
671
+** is advanced by the number of bytes extracted.
671672
**
672673
** After this call completes, pTo will be an ephemeral blob.
673674
*/
674675
int blob_extract(Blob *pFrom, int N, Blob *pTo){
675676
blob_is_init(pFrom);
@@ -687,10 +688,57 @@
687688
pTo->iCursor = 0;
688689
pTo->xRealloc = blobReallocStatic;
689690
pFrom->iCursor += N;
690691
return N;
691692
}
693
+
694
+/*
695
+** Extract N **lines** of text from blob pFrom beginning at the current
696
+** cursor position and use that text to initialize blob pTo. Unlike the
697
+** blob_extract() routine, the cursor position is unchanged.
698
+**
699
+** pTo is assumed to be uninitialized.
700
+**
701
+** After this call completes, pTo will be an ephemeral blob.
702
+*/
703
+int blob_extract_lines(Blob *pFrom, int N, Blob *pTo){
704
+ int i;
705
+ int mx;
706
+ int iStart;
707
+ int n;
708
+ const char *z;
709
+
710
+ blob_zero(pTo);
711
+ z = pFrom->aData;
712
+ i = pFrom->iCursor;
713
+ mx = pFrom->nUsed;
714
+ while( N>0 ){
715
+ while( i<mx && z[i]!='\n' ){ i++; }
716
+ if( i>=mx ) break;
717
+ i++;
718
+ N--;
719
+ }
720
+ iStart = pFrom->iCursor;
721
+ n = blob_extract(pFrom, i-pFrom->iCursor, pTo);
722
+ pFrom->iCursor = iStart;
723
+ return n;
724
+}
725
+
726
+/*
727
+** Return the number of lines of text in the blob. If the last
728
+** line is incomplete (if it does not have a \n at the end) then
729
+** it still counts.
730
+*/
731
+int blob_linecount(Blob *p){
732
+ int n = 0;
733
+ int i;
734
+ for(i=0; i<p->nUsed; i++){
735
+ if( p->aData[i]=='\n' ) n++;
736
+ }
737
+ if( p->nUsed>0 && p->aData[p->nUsed-1]!='\n' ) n++;
738
+ return n;
739
+}
692740
693741
/*
694742
** Rewind the cursor on a blob back to the beginning.
695743
*/
696744
void blob_rewind(Blob *p){
697745
--- src/blob.c
+++ src/blob.c
@@ -665,11 +665,12 @@
665 pBlob->nUsed = dehttpize(pBlob->aData);
666 }
667
668 /*
669 ** Extract N bytes from blob pFrom and use it to initialize blob pTo.
670 ** Return the actual number of bytes extracted.
 
671 **
672 ** After this call completes, pTo will be an ephemeral blob.
673 */
674 int blob_extract(Blob *pFrom, int N, Blob *pTo){
675 blob_is_init(pFrom);
@@ -687,10 +688,57 @@
687 pTo->iCursor = 0;
688 pTo->xRealloc = blobReallocStatic;
689 pFrom->iCursor += N;
690 return N;
691 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
693 /*
694 ** Rewind the cursor on a blob back to the beginning.
695 */
696 void blob_rewind(Blob *p){
697
--- src/blob.c
+++ src/blob.c
@@ -665,11 +665,12 @@
665 pBlob->nUsed = dehttpize(pBlob->aData);
666 }
667
668 /*
669 ** Extract N bytes from blob pFrom and use it to initialize blob pTo.
670 ** Return the actual number of bytes extracted. The cursor position
671 ** is advanced by the number of bytes extracted.
672 **
673 ** After this call completes, pTo will be an ephemeral blob.
674 */
675 int blob_extract(Blob *pFrom, int N, Blob *pTo){
676 blob_is_init(pFrom);
@@ -687,10 +688,57 @@
688 pTo->iCursor = 0;
689 pTo->xRealloc = blobReallocStatic;
690 pFrom->iCursor += N;
691 return N;
692 }
693
694 /*
695 ** Extract N **lines** of text from blob pFrom beginning at the current
696 ** cursor position and use that text to initialize blob pTo. Unlike the
697 ** blob_extract() routine, the cursor position is unchanged.
698 **
699 ** pTo is assumed to be uninitialized.
700 **
701 ** After this call completes, pTo will be an ephemeral blob.
702 */
703 int blob_extract_lines(Blob *pFrom, int N, Blob *pTo){
704 int i;
705 int mx;
706 int iStart;
707 int n;
708 const char *z;
709
710 blob_zero(pTo);
711 z = pFrom->aData;
712 i = pFrom->iCursor;
713 mx = pFrom->nUsed;
714 while( N>0 ){
715 while( i<mx && z[i]!='\n' ){ i++; }
716 if( i>=mx ) break;
717 i++;
718 N--;
719 }
720 iStart = pFrom->iCursor;
721 n = blob_extract(pFrom, i-pFrom->iCursor, pTo);
722 pFrom->iCursor = iStart;
723 return n;
724 }
725
726 /*
727 ** Return the number of lines of text in the blob. If the last
728 ** line is incomplete (if it does not have a \n at the end) then
729 ** it still counts.
730 */
731 int blob_linecount(Blob *p){
732 int n = 0;
733 int i;
734 for(i=0; i<p->nUsed; i++){
735 if( p->aData[i]=='\n' ) n++;
736 }
737 if( p->nUsed>0 && p->aData[p->nUsed-1]!='\n' ) n++;
738 return n;
739 }
740
741 /*
742 ** Rewind the cursor on a blob back to the beginning.
743 */
744 void blob_rewind(Blob *p){
745
+2 -2
--- src/branch.c
+++ src/branch.c
@@ -640,12 +640,12 @@
640640
** -M|--unmerged List branches not merged into the current branch
641641
** -p List only private branches
642642
** -r Reverse the sort order
643643
** -t Show recently changed branches first
644644
** --self List only branches where you participate
645
-** --username USER List only branches where USER participate
646
-** --users N List up to N users partipiating
645
+** --username USER List only branches where USER participates
646
+** --users N List up to N users participating
647647
**
648648
** The current branch is marked with an asterisk. Private branches are
649649
** marked with a hash sign.
650650
**
651651
** If GLOB is given, show only branches matching the pattern.
652652
--- src/branch.c
+++ src/branch.c
@@ -640,12 +640,12 @@
640 ** -M|--unmerged List branches not merged into the current branch
641 ** -p List only private branches
642 ** -r Reverse the sort order
643 ** -t Show recently changed branches first
644 ** --self List only branches where you participate
645 ** --username USER List only branches where USER participate
646 ** --users N List up to N users partipiating
647 **
648 ** The current branch is marked with an asterisk. Private branches are
649 ** marked with a hash sign.
650 **
651 ** If GLOB is given, show only branches matching the pattern.
652
--- src/branch.c
+++ src/branch.c
@@ -640,12 +640,12 @@
640 ** -M|--unmerged List branches not merged into the current branch
641 ** -p List only private branches
642 ** -r Reverse the sort order
643 ** -t Show recently changed branches first
644 ** --self List only branches where you participate
645 ** --username USER List only branches where USER participates
646 ** --users N List up to N users participating
647 **
648 ** The current branch is marked with an asterisk. Private branches are
649 ** marked with a hash sign.
650 **
651 ** If GLOB is given, show only branches matching the pattern.
652
+1 -1
--- src/chat.c
+++ src/chat.c
@@ -1202,11 +1202,11 @@
12021202
**
12031203
** --all Download all chat content. Normally only
12041204
** previously undownloaded content is retrieved.
12051205
** --debug Additional debugging output
12061206
** --out DATABASE Store CHAT table in separate database file
1207
-** DATABASE rather that adding to local clone
1207
+** DATABASE rather than adding to local clone
12081208
** --unsafe Allow the use of unencrypted http://
12091209
**
12101210
** > fossil chat send [ARGUMENTS]
12111211
**
12121212
** This command sends a new message to the chatroom. The message
12131213
--- src/chat.c
+++ src/chat.c
@@ -1202,11 +1202,11 @@
1202 **
1203 ** --all Download all chat content. Normally only
1204 ** previously undownloaded content is retrieved.
1205 ** --debug Additional debugging output
1206 ** --out DATABASE Store CHAT table in separate database file
1207 ** DATABASE rather that adding to local clone
1208 ** --unsafe Allow the use of unencrypted http://
1209 **
1210 ** > fossil chat send [ARGUMENTS]
1211 **
1212 ** This command sends a new message to the chatroom. The message
1213
--- src/chat.c
+++ src/chat.c
@@ -1202,11 +1202,11 @@
1202 **
1203 ** --all Download all chat content. Normally only
1204 ** previously undownloaded content is retrieved.
1205 ** --debug Additional debugging output
1206 ** --out DATABASE Store CHAT table in separate database file
1207 ** DATABASE rather than adding to local clone
1208 ** --unsafe Allow the use of unencrypted http://
1209 **
1210 ** > fossil chat send [ARGUMENTS]
1211 **
1212 ** This command sends a new message to the chatroom. The message
1213
+77 -16
--- src/checkin.c
+++ src/checkin.c
@@ -567,11 +567,11 @@
567567
}
568568
569569
/* Confirm current working directory is within check-out. */
570570
db_must_be_within_tree();
571571
572
- /* Get check-out version. l*/
572
+ /* Get check-out version. */
573573
vid = db_lget_int("checkout", 0);
574574
575575
/* Relative path flag determination is done by a shared function. */
576576
if( determine_cwd_relative_option() ){
577577
flags |= C_RELPATH;
@@ -967,12 +967,12 @@
967967
/*
968968
** COMMAND: tree
969969
**
970970
** Usage: %fossil tree ?OPTIONS? ?PATHS ...?
971971
**
972
-** List all files in the current check-out in after the fashion of the
973
-** "tree" command. If PATHS is included, only the named files
972
+** List all files in the current check-out much like the "tree"
973
+** command does. If PATHS is included, only the named files
974974
** (or their children if directories) are shown.
975975
**
976976
** Options:
977977
** -r VERSION The specific check-in to list
978978
** -R|--repository REPO Extract info from repository REPO
@@ -1563,17 +1563,17 @@
15631563
break;
15641564
}
15651565
diffFiles[i].nName = strlen(diffFiles[i].zName);
15661566
diffFiles[i].nUsed = 0;
15671567
}
1568
- diff_against_disk(0, &DCfg, diffFiles, &prompt);
1568
+ diff_version_to_checkout(0, &DCfg, diffFiles, &prompt);
15691569
for( i=0; diffFiles[i].zName; ++i ){
15701570
fossil_free(diffFiles[i].zName);
15711571
}
15721572
fossil_free(diffFiles);
15731573
}else{
1574
- diff_against_disk(0, &DCfg, 0, &prompt);
1574
+ diff_version_to_checkout(0, &DCfg, 0, &prompt);
15751575
}
15761576
}
15771577
prompt_for_user_comment(pComment, &prompt);
15781578
blob_reset(&prompt);
15791579
}
@@ -2318,11 +2318,11 @@
23182318
**
23192319
** A check-in is not permitted to fork unless the --allow-fork option
23202320
** appears. An empty check-in (i.e. with nothing changed) is not
23212321
** allowed unless the --allow-empty option appears. A check-in may not
23222322
** be older than its ancestor unless the --allow-older option appears.
2323
-** If any of files in the check-in appear to contain unresolved merge
2323
+** If any files in the check-in appear to contain unresolved merge
23242324
** conflicts, the check-in will not be allowed unless the
23252325
** --allow-conflict option is present. In addition, the entire
23262326
** check-in process may be aborted if a file contains content that
23272327
** appears to be binary, Unicode text, or text with CR/LF line endings
23282328
** unless the interactive user chooses to proceed. If there is no
@@ -2358,11 +2358,11 @@
23582358
** than relying on file mtimes
23592359
** --ignore-clock-skew If a clock skew is detected, ignore it and
23602360
** behave as if the user had entered 'yes' to
23612361
** the question of whether to proceed despite
23622362
** the skew.
2363
-** --ignore-oversize Do not warning the user about oversized files
2363
+** --ignore-oversize Do not warn the user about oversized files
23642364
** --integrate Close all merged-in branches
23652365
** -m|--comment COMMENT-TEXT Use COMMENT-TEXT as commit comment
23662366
** -M|--message-file FILE Read the commit comment from given file
23672367
** --mimetype MIMETYPE Mimetype of check-in comment
23682368
** -n|--dry-run If given, display instead of run actions
@@ -2434,10 +2434,12 @@
24342434
Blob ans; /* Answer to continuation prompts */
24352435
char cReply; /* First character of ans */
24362436
int bRecheck = 0; /* Repeat fork and closed-branch checks*/
24372437
int bIgnoreSkew = 0; /* --ignore-clock-skew flag */
24382438
int mxSize;
2439
+ char *zCurBranch = 0; /* The current branch name of checkout */
2440
+ char *zNewBranch = 0; /* The branch name after update */
24392441
24402442
memset(&sCiInfo, 0, sizeof(sCiInfo));
24412443
url_proxy_options();
24422444
/* --sha1sum is an undocumented alias for --hash for backwards compatiblity */
24432445
useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0;
@@ -2508,10 +2510,55 @@
25082510
}
25092511
}else{
25102512
privateParent = content_is_private(vid);
25112513
}
25122514
2515
+ user_select();
2516
+ /*
2517
+ ** Check that the user exists.
2518
+ */
2519
+ if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){
2520
+ fossil_fatal("no such user: %s", g.zLogin);
2521
+ }
2522
+
2523
+ /*
2524
+ ** Detect if the branch name has changed from the parent check-in
2525
+ ** and prompt if necessary
2526
+ **/
2527
+ zCurBranch = db_text(0,
2528
+ " SELECT value FROM tagxref AS tx"
2529
+ " WHERE rid=(SELECT pid"
2530
+ " FROM tagxref LEFT JOIN event ON srcid=objid"
2531
+ " LEFT JOIN plink ON rid=cid"
2532
+ " WHERE rid=%d AND tagxref.tagid=%d"
2533
+ " AND srcid!=origid"
2534
+ " AND tagtype=2 AND coalesce(euser,user)!=%Q)"
2535
+ " AND tx.tagid=%d",
2536
+ vid, TAG_BRANCH, g.zLogin, TAG_BRANCH
2537
+ );
2538
+ if( zCurBranch!=0 && zCurBranch[0]!=0
2539
+ && forceFlag==0
2540
+ && noPrompt==0
2541
+ ){
2542
+ zNewBranch = branch_of_rid(vid);
2543
+ fossil_warning(
2544
+ "WARNING: The parent check-in [%.10s] has been moved from branch\n"
2545
+ " '%s' over to branch '%s'.",
2546
+ rid_to_uuid(vid), zCurBranch, zNewBranch
2547
+ );
2548
+ prompt_user("Commit anyway? (y/N) ", &ans);
2549
+ cReply = blob_str(&ans)[0];
2550
+ blob_reset(&ans);
2551
+ if( cReply!='y' && cReply!='Y' ){
2552
+ fossil_fatal("Abandoning commit because branch has changed");
2553
+ }
2554
+ fossil_free(zNewBranch);
2555
+ fossil_free(zCurBranch);
2556
+ zCurBranch = branch_of_rid(vid);
2557
+ }
2558
+ if( zCurBranch==0 ) zCurBranch = branch_of_rid(vid);
2559
+
25132560
/* Track the "private" status */
25142561
g.markPrivate = privateFlag || privateParent;
25152562
if( privateFlag && !privateParent ){
25162563
/* Apply default branch name ("private") and color ("orange") if not
25172564
** specified otherwise on the command-line, and if the parent is not
@@ -2560,11 +2607,11 @@
25602607
**
25612608
** If the remote repository sent an avoid-delta-manifests pragma on
25622609
** the autosync above, then also try to avoid deltas, unless the
25632610
** --delta option is specified. The remote repo will send the
25642611
** avoid-delta-manifests pragma if it has its "forbid-delta-manifests"
2565
- ** setting is enabled.
2612
+ ** setting enabled.
25662613
*/
25672614
if( !db_get_boolean("seen-delta-manifest",0)
25682615
|| db_get_boolean("forbid-delta-manifests",0)
25692616
|| g.bAvoidDeltaManifests
25702617
){
@@ -2639,18 +2686,10 @@
26392686
"'%s' was renamed to '%s'", zFrom, zTo, zFrom, zTo);
26402687
}
26412688
db_finalize(&q);
26422689
}
26432690
2644
- user_select();
2645
- /*
2646
- ** Check that the user exists.
2647
- */
2648
- if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){
2649
- fossil_fatal("no such user: %s", g.zLogin);
2650
- }
2651
-
26522691
hasChanges = unsaved_changes(useHash ? CKSIG_HASH : 0);
26532692
db_begin_transaction();
26542693
db_record_repository_filename(0);
26552694
if( hasChanges==0 && !isAMerge && !allowEmpty && !forceFlag ){
26562695
fossil_fatal("nothing has changed; use --allow-empty to override");
@@ -2713,10 +2752,32 @@
27132752
" WHERE tagid=%d AND rid=%d AND tagtype>0"
27142753
" AND value=%Q", TAG_BRANCH, vid, sCiInfo.zBranch))
27152754
){
27162755
fossil_fatal("cannot commit against a closed leaf");
27172756
}
2757
+
2758
+ /* Require confirmation to continue with the check-in if the branch
2759
+ ** has changed and the committer did not provide the same branch
2760
+ */
2761
+ zNewBranch = branch_of_rid(vid);
2762
+ if( fossil_strcmp(zCurBranch, zNewBranch)!=0
2763
+ && fossil_strcmp(sCiInfo.zBranch, zNewBranch)!=0
2764
+ && forceFlag==0
2765
+ && noPrompt==0
2766
+ ){
2767
+ fossil_warning("parent check-in [%.10s] branch changed from '%s' to '%s'",
2768
+ rid_to_uuid(vid), zCurBranch, zNewBranch);
2769
+ prompt_user("continue (y/N)? ", &ans);
2770
+ cReply = blob_str(&ans)[0];
2771
+ blob_reset(&ans);
2772
+ if( cReply!='y' && cReply!='Y' ){
2773
+ fossil_fatal("Abandoning commit because branch has changed");
2774
+ }
2775
+ fossil_free(zCurBranch);
2776
+ zCurBranch = branch_of_rid(vid);
2777
+ }
2778
+ fossil_free(zNewBranch);
27182779
27192780
/* Always exit the loop on the second pass */
27202781
if( bRecheck ) break;
27212782
27222783
27232784
--- src/checkin.c
+++ src/checkin.c
@@ -567,11 +567,11 @@
567 }
568
569 /* Confirm current working directory is within check-out. */
570 db_must_be_within_tree();
571
572 /* Get check-out version. l*/
573 vid = db_lget_int("checkout", 0);
574
575 /* Relative path flag determination is done by a shared function. */
576 if( determine_cwd_relative_option() ){
577 flags |= C_RELPATH;
@@ -967,12 +967,12 @@
967 /*
968 ** COMMAND: tree
969 **
970 ** Usage: %fossil tree ?OPTIONS? ?PATHS ...?
971 **
972 ** List all files in the current check-out in after the fashion of the
973 ** "tree" command. If PATHS is included, only the named files
974 ** (or their children if directories) are shown.
975 **
976 ** Options:
977 ** -r VERSION The specific check-in to list
978 ** -R|--repository REPO Extract info from repository REPO
@@ -1563,17 +1563,17 @@
1563 break;
1564 }
1565 diffFiles[i].nName = strlen(diffFiles[i].zName);
1566 diffFiles[i].nUsed = 0;
1567 }
1568 diff_against_disk(0, &DCfg, diffFiles, &prompt);
1569 for( i=0; diffFiles[i].zName; ++i ){
1570 fossil_free(diffFiles[i].zName);
1571 }
1572 fossil_free(diffFiles);
1573 }else{
1574 diff_against_disk(0, &DCfg, 0, &prompt);
1575 }
1576 }
1577 prompt_for_user_comment(pComment, &prompt);
1578 blob_reset(&prompt);
1579 }
@@ -2318,11 +2318,11 @@
2318 **
2319 ** A check-in is not permitted to fork unless the --allow-fork option
2320 ** appears. An empty check-in (i.e. with nothing changed) is not
2321 ** allowed unless the --allow-empty option appears. A check-in may not
2322 ** be older than its ancestor unless the --allow-older option appears.
2323 ** If any of files in the check-in appear to contain unresolved merge
2324 ** conflicts, the check-in will not be allowed unless the
2325 ** --allow-conflict option is present. In addition, the entire
2326 ** check-in process may be aborted if a file contains content that
2327 ** appears to be binary, Unicode text, or text with CR/LF line endings
2328 ** unless the interactive user chooses to proceed. If there is no
@@ -2358,11 +2358,11 @@
2358 ** than relying on file mtimes
2359 ** --ignore-clock-skew If a clock skew is detected, ignore it and
2360 ** behave as if the user had entered 'yes' to
2361 ** the question of whether to proceed despite
2362 ** the skew.
2363 ** --ignore-oversize Do not warning the user about oversized files
2364 ** --integrate Close all merged-in branches
2365 ** -m|--comment COMMENT-TEXT Use COMMENT-TEXT as commit comment
2366 ** -M|--message-file FILE Read the commit comment from given file
2367 ** --mimetype MIMETYPE Mimetype of check-in comment
2368 ** -n|--dry-run If given, display instead of run actions
@@ -2434,10 +2434,12 @@
2434 Blob ans; /* Answer to continuation prompts */
2435 char cReply; /* First character of ans */
2436 int bRecheck = 0; /* Repeat fork and closed-branch checks*/
2437 int bIgnoreSkew = 0; /* --ignore-clock-skew flag */
2438 int mxSize;
 
 
2439
2440 memset(&sCiInfo, 0, sizeof(sCiInfo));
2441 url_proxy_options();
2442 /* --sha1sum is an undocumented alias for --hash for backwards compatiblity */
2443 useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0;
@@ -2508,10 +2510,55 @@
2508 }
2509 }else{
2510 privateParent = content_is_private(vid);
2511 }
2512
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2513 /* Track the "private" status */
2514 g.markPrivate = privateFlag || privateParent;
2515 if( privateFlag && !privateParent ){
2516 /* Apply default branch name ("private") and color ("orange") if not
2517 ** specified otherwise on the command-line, and if the parent is not
@@ -2560,11 +2607,11 @@
2560 **
2561 ** If the remote repository sent an avoid-delta-manifests pragma on
2562 ** the autosync above, then also try to avoid deltas, unless the
2563 ** --delta option is specified. The remote repo will send the
2564 ** avoid-delta-manifests pragma if it has its "forbid-delta-manifests"
2565 ** setting is enabled.
2566 */
2567 if( !db_get_boolean("seen-delta-manifest",0)
2568 || db_get_boolean("forbid-delta-manifests",0)
2569 || g.bAvoidDeltaManifests
2570 ){
@@ -2639,18 +2686,10 @@
2639 "'%s' was renamed to '%s'", zFrom, zTo, zFrom, zTo);
2640 }
2641 db_finalize(&q);
2642 }
2643
2644 user_select();
2645 /*
2646 ** Check that the user exists.
2647 */
2648 if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){
2649 fossil_fatal("no such user: %s", g.zLogin);
2650 }
2651
2652 hasChanges = unsaved_changes(useHash ? CKSIG_HASH : 0);
2653 db_begin_transaction();
2654 db_record_repository_filename(0);
2655 if( hasChanges==0 && !isAMerge && !allowEmpty && !forceFlag ){
2656 fossil_fatal("nothing has changed; use --allow-empty to override");
@@ -2713,10 +2752,32 @@
2713 " WHERE tagid=%d AND rid=%d AND tagtype>0"
2714 " AND value=%Q", TAG_BRANCH, vid, sCiInfo.zBranch))
2715 ){
2716 fossil_fatal("cannot commit against a closed leaf");
2717 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2718
2719 /* Always exit the loop on the second pass */
2720 if( bRecheck ) break;
2721
2722
2723
--- src/checkin.c
+++ src/checkin.c
@@ -567,11 +567,11 @@
567 }
568
569 /* Confirm current working directory is within check-out. */
570 db_must_be_within_tree();
571
572 /* Get check-out version. */
573 vid = db_lget_int("checkout", 0);
574
575 /* Relative path flag determination is done by a shared function. */
576 if( determine_cwd_relative_option() ){
577 flags |= C_RELPATH;
@@ -967,12 +967,12 @@
967 /*
968 ** COMMAND: tree
969 **
970 ** Usage: %fossil tree ?OPTIONS? ?PATHS ...?
971 **
972 ** List all files in the current check-out much like the "tree"
973 ** command does. If PATHS is included, only the named files
974 ** (or their children if directories) are shown.
975 **
976 ** Options:
977 ** -r VERSION The specific check-in to list
978 ** -R|--repository REPO Extract info from repository REPO
@@ -1563,17 +1563,17 @@
1563 break;
1564 }
1565 diffFiles[i].nName = strlen(diffFiles[i].zName);
1566 diffFiles[i].nUsed = 0;
1567 }
1568 diff_version_to_checkout(0, &DCfg, diffFiles, &prompt);
1569 for( i=0; diffFiles[i].zName; ++i ){
1570 fossil_free(diffFiles[i].zName);
1571 }
1572 fossil_free(diffFiles);
1573 }else{
1574 diff_version_to_checkout(0, &DCfg, 0, &prompt);
1575 }
1576 }
1577 prompt_for_user_comment(pComment, &prompt);
1578 blob_reset(&prompt);
1579 }
@@ -2318,11 +2318,11 @@
2318 **
2319 ** A check-in is not permitted to fork unless the --allow-fork option
2320 ** appears. An empty check-in (i.e. with nothing changed) is not
2321 ** allowed unless the --allow-empty option appears. A check-in may not
2322 ** be older than its ancestor unless the --allow-older option appears.
2323 ** If any files in the check-in appear to contain unresolved merge
2324 ** conflicts, the check-in will not be allowed unless the
2325 ** --allow-conflict option is present. In addition, the entire
2326 ** check-in process may be aborted if a file contains content that
2327 ** appears to be binary, Unicode text, or text with CR/LF line endings
2328 ** unless the interactive user chooses to proceed. If there is no
@@ -2358,11 +2358,11 @@
2358 ** than relying on file mtimes
2359 ** --ignore-clock-skew If a clock skew is detected, ignore it and
2360 ** behave as if the user had entered 'yes' to
2361 ** the question of whether to proceed despite
2362 ** the skew.
2363 ** --ignore-oversize Do not warn the user about oversized files
2364 ** --integrate Close all merged-in branches
2365 ** -m|--comment COMMENT-TEXT Use COMMENT-TEXT as commit comment
2366 ** -M|--message-file FILE Read the commit comment from given file
2367 ** --mimetype MIMETYPE Mimetype of check-in comment
2368 ** -n|--dry-run If given, display instead of run actions
@@ -2434,10 +2434,12 @@
2434 Blob ans; /* Answer to continuation prompts */
2435 char cReply; /* First character of ans */
2436 int bRecheck = 0; /* Repeat fork and closed-branch checks*/
2437 int bIgnoreSkew = 0; /* --ignore-clock-skew flag */
2438 int mxSize;
2439 char *zCurBranch = 0; /* The current branch name of checkout */
2440 char *zNewBranch = 0; /* The branch name after update */
2441
2442 memset(&sCiInfo, 0, sizeof(sCiInfo));
2443 url_proxy_options();
2444 /* --sha1sum is an undocumented alias for --hash for backwards compatiblity */
2445 useHash = find_option("hash",0,0)!=0 || find_option("sha1sum",0,0)!=0;
@@ -2508,10 +2510,55 @@
2510 }
2511 }else{
2512 privateParent = content_is_private(vid);
2513 }
2514
2515 user_select();
2516 /*
2517 ** Check that the user exists.
2518 */
2519 if( !db_exists("SELECT 1 FROM user WHERE login=%Q", g.zLogin) ){
2520 fossil_fatal("no such user: %s", g.zLogin);
2521 }
2522
2523 /*
2524 ** Detect if the branch name has changed from the parent check-in
2525 ** and prompt if necessary
2526 **/
2527 zCurBranch = db_text(0,
2528 " SELECT value FROM tagxref AS tx"
2529 " WHERE rid=(SELECT pid"
2530 " FROM tagxref LEFT JOIN event ON srcid=objid"
2531 " LEFT JOIN plink ON rid=cid"
2532 " WHERE rid=%d AND tagxref.tagid=%d"
2533 " AND srcid!=origid"
2534 " AND tagtype=2 AND coalesce(euser,user)!=%Q)"
2535 " AND tx.tagid=%d",
2536 vid, TAG_BRANCH, g.zLogin, TAG_BRANCH
2537 );
2538 if( zCurBranch!=0 && zCurBranch[0]!=0
2539 && forceFlag==0
2540 && noPrompt==0
2541 ){
2542 zNewBranch = branch_of_rid(vid);
2543 fossil_warning(
2544 "WARNING: The parent check-in [%.10s] has been moved from branch\n"
2545 " '%s' over to branch '%s'.",
2546 rid_to_uuid(vid), zCurBranch, zNewBranch
2547 );
2548 prompt_user("Commit anyway? (y/N) ", &ans);
2549 cReply = blob_str(&ans)[0];
2550 blob_reset(&ans);
2551 if( cReply!='y' && cReply!='Y' ){
2552 fossil_fatal("Abandoning commit because branch has changed");
2553 }
2554 fossil_free(zNewBranch);
2555 fossil_free(zCurBranch);
2556 zCurBranch = branch_of_rid(vid);
2557 }
2558 if( zCurBranch==0 ) zCurBranch = branch_of_rid(vid);
2559
2560 /* Track the "private" status */
2561 g.markPrivate = privateFlag || privateParent;
2562 if( privateFlag && !privateParent ){
2563 /* Apply default branch name ("private") and color ("orange") if not
2564 ** specified otherwise on the command-line, and if the parent is not
@@ -2560,11 +2607,11 @@
2607 **
2608 ** If the remote repository sent an avoid-delta-manifests pragma on
2609 ** the autosync above, then also try to avoid deltas, unless the
2610 ** --delta option is specified. The remote repo will send the
2611 ** avoid-delta-manifests pragma if it has its "forbid-delta-manifests"
2612 ** setting enabled.
2613 */
2614 if( !db_get_boolean("seen-delta-manifest",0)
2615 || db_get_boolean("forbid-delta-manifests",0)
2616 || g.bAvoidDeltaManifests
2617 ){
@@ -2639,18 +2686,10 @@
2686 "'%s' was renamed to '%s'", zFrom, zTo, zFrom, zTo);
2687 }
2688 db_finalize(&q);
2689 }
2690
 
 
 
 
 
 
 
 
2691 hasChanges = unsaved_changes(useHash ? CKSIG_HASH : 0);
2692 db_begin_transaction();
2693 db_record_repository_filename(0);
2694 if( hasChanges==0 && !isAMerge && !allowEmpty && !forceFlag ){
2695 fossil_fatal("nothing has changed; use --allow-empty to override");
@@ -2713,10 +2752,32 @@
2752 " WHERE tagid=%d AND rid=%d AND tagtype>0"
2753 " AND value=%Q", TAG_BRANCH, vid, sCiInfo.zBranch))
2754 ){
2755 fossil_fatal("cannot commit against a closed leaf");
2756 }
2757
2758 /* Require confirmation to continue with the check-in if the branch
2759 ** has changed and the committer did not provide the same branch
2760 */
2761 zNewBranch = branch_of_rid(vid);
2762 if( fossil_strcmp(zCurBranch, zNewBranch)!=0
2763 && fossil_strcmp(sCiInfo.zBranch, zNewBranch)!=0
2764 && forceFlag==0
2765 && noPrompt==0
2766 ){
2767 fossil_warning("parent check-in [%.10s] branch changed from '%s' to '%s'",
2768 rid_to_uuid(vid), zCurBranch, zNewBranch);
2769 prompt_user("continue (y/N)? ", &ans);
2770 cReply = blob_str(&ans)[0];
2771 blob_reset(&ans);
2772 if( cReply!='y' && cReply!='Y' ){
2773 fossil_fatal("Abandoning commit because branch has changed");
2774 }
2775 fossil_free(zCurBranch);
2776 zCurBranch = branch_of_rid(vid);
2777 }
2778 fossil_free(zNewBranch);
2779
2780 /* Always exit the loop on the second pass */
2781 if( bRecheck ) break;
2782
2783
2784
+3 -4
--- src/checkout.c
+++ src/checkout.c
@@ -171,23 +171,22 @@
171171
** each character as a flag to enable writing "manifest", "manifest.uuid" or
172172
** "manifest.tags".
173173
*/
174174
void manifest_to_disk(int vid){
175175
char *zManFile;
176
- Blob manifest;
177
- Blob taglist;
178176
int flg;
179177
180178
flg = db_get_manifest_setting();
181179
182180
if( flg & MFESTFLG_RAW ){
183
- blob_zero(&manifest);
181
+ Blob manifest = BLOB_INITIALIZER;
184182
content_get(vid, &manifest);
185183
sterilize_manifest(&manifest, CFTYPE_MANIFEST);
186184
zManFile = mprintf("%smanifest", g.zLocalRoot);
187185
blob_write_to_file(&manifest, zManFile);
188186
free(zManFile);
187
+ blob_reset(&manifest);
189188
}else{
190189
if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest'") ){
191190
zManFile = mprintf("%smanifest", g.zLocalRoot);
192191
file_delete(zManFile);
193192
free(zManFile);
@@ -207,11 +206,11 @@
207206
file_delete(zManFile);
208207
free(zManFile);
209208
}
210209
}
211210
if( flg & MFESTFLG_TAGS ){
212
- blob_zero(&taglist);
211
+ Blob taglist = BLOB_INITIALIZER;
213212
zManFile = mprintf("%smanifest.tags", g.zLocalRoot);
214213
get_checkin_taglist(vid, &taglist);
215214
blob_write_to_file(&taglist, zManFile);
216215
free(zManFile);
217216
blob_reset(&taglist);
218217
--- src/checkout.c
+++ src/checkout.c
@@ -171,23 +171,22 @@
171 ** each character as a flag to enable writing "manifest", "manifest.uuid" or
172 ** "manifest.tags".
173 */
174 void manifest_to_disk(int vid){
175 char *zManFile;
176 Blob manifest;
177 Blob taglist;
178 int flg;
179
180 flg = db_get_manifest_setting();
181
182 if( flg & MFESTFLG_RAW ){
183 blob_zero(&manifest);
184 content_get(vid, &manifest);
185 sterilize_manifest(&manifest, CFTYPE_MANIFEST);
186 zManFile = mprintf("%smanifest", g.zLocalRoot);
187 blob_write_to_file(&manifest, zManFile);
188 free(zManFile);
 
189 }else{
190 if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest'") ){
191 zManFile = mprintf("%smanifest", g.zLocalRoot);
192 file_delete(zManFile);
193 free(zManFile);
@@ -207,11 +206,11 @@
207 file_delete(zManFile);
208 free(zManFile);
209 }
210 }
211 if( flg & MFESTFLG_TAGS ){
212 blob_zero(&taglist);
213 zManFile = mprintf("%smanifest.tags", g.zLocalRoot);
214 get_checkin_taglist(vid, &taglist);
215 blob_write_to_file(&taglist, zManFile);
216 free(zManFile);
217 blob_reset(&taglist);
218
--- src/checkout.c
+++ src/checkout.c
@@ -171,23 +171,22 @@
171 ** each character as a flag to enable writing "manifest", "manifest.uuid" or
172 ** "manifest.tags".
173 */
174 void manifest_to_disk(int vid){
175 char *zManFile;
 
 
176 int flg;
177
178 flg = db_get_manifest_setting();
179
180 if( flg & MFESTFLG_RAW ){
181 Blob manifest = BLOB_INITIALIZER;
182 content_get(vid, &manifest);
183 sterilize_manifest(&manifest, CFTYPE_MANIFEST);
184 zManFile = mprintf("%smanifest", g.zLocalRoot);
185 blob_write_to_file(&manifest, zManFile);
186 free(zManFile);
187 blob_reset(&manifest);
188 }else{
189 if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest'") ){
190 zManFile = mprintf("%smanifest", g.zLocalRoot);
191 file_delete(zManFile);
192 free(zManFile);
@@ -207,11 +206,11 @@
206 file_delete(zManFile);
207 free(zManFile);
208 }
209 }
210 if( flg & MFESTFLG_TAGS ){
211 Blob taglist = BLOB_INITIALIZER;
212 zManFile = mprintf("%smanifest.tags", g.zLocalRoot);
213 get_checkin_taglist(vid, &taglist);
214 blob_write_to_file(&taglist, zManFile);
215 free(zManFile);
216 blob_reset(&taglist);
217
+110 -40
--- src/db.c
+++ src/db.c
@@ -170,10 +170,12 @@
170170
void *pAuthArg; /* Argument to the authorizer */
171171
const char *zAuthName; /* Name of the authorizer */
172172
int bProtectTriggers; /* True if protection triggers already exist */
173173
int nProtect; /* Slots of aProtect used */
174174
unsigned aProtect[12]; /* Saved values of protectMask */
175
+ int pauseDmlLog; /* Ignore pDmlLog if positive */
176
+ Blob *pDmlLog; /* Append DML statements here, of not NULL */
175177
} db = {
176178
PROTECT_USER|PROTECT_CONFIG|PROTECT_BASELINE, /* protectMask */
177179
0, 0, 0, 0, 0, 0, 0, {{0}}, {0}, {0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0}};
178180
179181
/*
@@ -643,10 +645,43 @@
643645
*/
644646
#define DB_PREPARE_IGNORE_ERROR 0x001 /* Suppress errors */
645647
#define DB_PREPARE_PERSISTENT 0x002 /* Stmt will stick around for a while */
646648
#endif
647649
650
+/*
651
+** If zSql is a DML statement, append it db.pDmlLog.
652
+*/
653
+static void db_append_dml(const char *zSql){
654
+ size_t nSql;
655
+ if( db.pDmlLog==0 ) return;
656
+ if( db.pauseDmlLog ) return;
657
+ if( zSql==0 ) return;
658
+ nSql = strlen(zSql);
659
+ while( nSql>0 && fossil_isspace(zSql[0]) ){ nSql--; zSql++; }
660
+ while( nSql>0 && fossil_isspace(zSql[nSql-1]) ) nSql--;
661
+ if( nSql<6 ) return;
662
+ if( fossil_strnicmp(zSql, "SELECT", 6)==0 ) return;
663
+ if( fossil_strnicmp(zSql, "PRAGMA", 6)==0 ) return;
664
+ blob_append(db.pDmlLog, zSql, nSql);
665
+ if( zSql[nSql-1]!=';' ) blob_append_char(db.pDmlLog, ';');
666
+ blob_append_char(db.pDmlLog, '\n');
667
+}
668
+
669
+/*
670
+** Set the Blob to which DML statement text should be appended. Set it
671
+** to zero to stop appending DML statement text.
672
+*/
673
+void db_append_dml_to_blob(Blob *pBlob){
674
+ db.pDmlLog = pBlob;
675
+}
676
+
677
+/*
678
+** Pause or unpause the DML log
679
+*/
680
+void db_pause_dml_log(void){ db.pauseDmlLog++; }
681
+void db_unpause_dml_log(void){ db.pauseDmlLog--; }
682
+
648683
/*
649684
** Prepare a Stmt. Assume that the Stmt is previously uninitialized.
650685
** If the input string contains multiple SQL statements, only the first
651686
** one is processed. All statements beyond the first are silently ignored.
652687
*/
@@ -658,10 +693,11 @@
658693
blob_zero(&pStmt->sql);
659694
blob_vappendf(&pStmt->sql, zFormat, ap);
660695
va_end(ap);
661696
zSql = blob_str(&pStmt->sql);
662697
db.nPrepare++;
698
+ db_append_dml(zSql);
663699
if( flags & DB_PREPARE_PERSISTENT ){
664700
prepFlags = SQLITE_PREPARE_PERSISTENT;
665701
}
666702
rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra);
667703
if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
@@ -1047,10 +1083,11 @@
10471083
rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
10481084
if( rc ){
10491085
db_err("%s: {%s}", sqlite3_errmsg(g.db), z);
10501086
}else if( pStmt ){
10511087
db.nPrepare++;
1088
+ db_append_dml(sqlite3_sql(pStmt));
10521089
while( sqlite3_step(pStmt)==SQLITE_ROW ){}
10531090
rc = sqlite3_finalize(pStmt);
10541091
if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z);
10551092
}
10561093
z = zEnd;
@@ -1312,10 +1349,50 @@
13121349
sqlite3_result_int64(context, rid);
13131350
}
13141351
}
13151352
}
13161353
1354
+
1355
+/*
1356
+** SETTING: timeline-utc boolean default=on
1357
+**
1358
+** If the timeline-utc setting is true, then Fossil tries to understand
1359
+** and display all time values using UTC. If this setting is false, Fossil
1360
+** tries to understand and display time values using the local timezone.
1361
+**
1362
+** The word "timeline" in the name of this setting is historical.
1363
+** This setting applies to all user interfaces of Fossil,
1364
+** not just the timeline.
1365
+**
1366
+** Note that when accessing Fossil using the web interface, the localtime
1367
+** used is the localtime on the server, not on the client.
1368
+*/
1369
+/*
1370
+** Return true if Fossil is set to display times as UTC. Return false
1371
+** if it wants to display times using the local timezone.
1372
+**
1373
+** False is returned if display is set to localtime even if the localtime
1374
+** happens to be the same as UTC.
1375
+*/
1376
+int fossil_ui_utctime(void){
1377
+ if( g.fTimeFormat==0 ){
1378
+ if( db_get_int("timeline-utc", 1) ){
1379
+ g.fTimeFormat = 1; /* UTC */
1380
+ }else{
1381
+ g.fTimeFormat = 2; /* Localtime */
1382
+ }
1383
+ }
1384
+ return g.fTimeFormat==1;
1385
+}
1386
+
1387
+/*
1388
+** Return true if Fossil is set to display times using the local timezone.
1389
+*/
1390
+int fossil_ui_localtime(void){
1391
+ return fossil_ui_utctime()==0;
1392
+}
1393
+
13171394
/*
13181395
** The toLocal() SQL function returns a string that is an argument to a
13191396
** date/time function that is appropriate for modifying the time for display.
13201397
** If UTC time display is selected, no modification occurs. If local time
13211398
** display is selected, the time is adjusted appropriately.
@@ -1327,18 +1404,11 @@
13271404
void db_tolocal_function(
13281405
sqlite3_context *context,
13291406
int argc,
13301407
sqlite3_value **argv
13311408
){
1332
- if( g.fTimeFormat==0 ){
1333
- if( db_get_int("timeline-utc", 1) ){
1334
- g.fTimeFormat = 1;
1335
- }else{
1336
- g.fTimeFormat = 2;
1337
- }
1338
- }
1339
- if( g.fTimeFormat==1 ){
1409
+ if( fossil_ui_utctime() ){
13401410
sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC);
13411411
}else{
13421412
sqlite3_result_text(context, "localtime", -1, SQLITE_STATIC);
13431413
}
13441414
}
@@ -1356,18 +1426,11 @@
13561426
void db_fromlocal_function(
13571427
sqlite3_context *context,
13581428
int argc,
13591429
sqlite3_value **argv
13601430
){
1361
- if( g.fTimeFormat==0 ){
1362
- if( db_get_int("timeline-utc", 1) ){
1363
- g.fTimeFormat = 1;
1364
- }else{
1365
- g.fTimeFormat = 2;
1366
- }
1367
- }
1368
- if( g.fTimeFormat==1 ){
1431
+ if( fossil_ui_utctime() ){
13691432
sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC);
13701433
}else{
13711434
sqlite3_result_text(context, "utc", -1, SQLITE_STATIC);
13721435
}
13731436
}
@@ -1524,24 +1587,31 @@
15241587
** Register the SQL functions that are useful both to the internal
15251588
** representation and to the "fossil sql" command.
15261589
*/
15271590
void db_add_aux_functions(sqlite3 *db){
15281591
sqlite3_create_collation(db, "uintnocase", SQLITE_UTF8,0,uintNocaseCollFunc);
1529
- sqlite3_create_function(db, "checkin_mtime", 2, SQLITE_UTF8, 0,
1530
- db_checkin_mtime_function, 0, 0);
1531
- sqlite3_create_function(db, "symbolic_name_to_rid", 1, SQLITE_UTF8, 0,
1532
- db_sym2rid_function, 0, 0);
1533
- sqlite3_create_function(db, "symbolic_name_to_rid", 2, SQLITE_UTF8, 0,
1534
- db_sym2rid_function, 0, 0);
1535
- sqlite3_create_function(db, "now", 0, SQLITE_UTF8, 0,
1592
+ sqlite3_create_function(db, "checkin_mtime", 2,
1593
+ SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
1594
+ 0, db_checkin_mtime_function, 0, 0);
1595
+ sqlite3_create_function(db, "symbolic_name_to_rid", 1,
1596
+ SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
1597
+ 0, db_sym2rid_function, 0, 0);
1598
+ sqlite3_create_function(db, "symbolic_name_to_rid", 2,
1599
+ SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
1600
+ 0, db_sym2rid_function, 0, 0);
1601
+ sqlite3_create_function(db, "now", 0,
1602
+ SQLITE_UTF8|SQLITE_INNOCUOUS, 0,
15361603
db_now_function, 0, 0);
1537
- sqlite3_create_function(db, "toLocal", 0, SQLITE_UTF8, 0,
1538
- db_tolocal_function, 0, 0);
1539
- sqlite3_create_function(db, "fromLocal", 0, SQLITE_UTF8, 0,
1540
- db_fromlocal_function, 0, 0);
1541
- sqlite3_create_function(db, "hextoblob", 1, SQLITE_UTF8, 0,
1542
- db_hextoblob, 0, 0);
1604
+ sqlite3_create_function(db, "toLocal", 0,
1605
+ SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
1606
+ 0, db_tolocal_function, 0, 0);
1607
+ sqlite3_create_function(db, "fromLocal", 0,
1608
+ SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
1609
+ 0, db_fromlocal_function, 0, 0);
1610
+ sqlite3_create_function(db, "hextoblob", 1,
1611
+ SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
1612
+ 0, db_hextoblob, 0, 0);
15431613
sqlite3_create_function(db, "capunion", 1, SQLITE_UTF8, 0,
15441614
0, capability_union_step, capability_union_finalize);
15451615
sqlite3_create_function(db, "fullcap", 1, SQLITE_UTF8, 0,
15461616
capability_fullcap, 0, 0);
15471617
sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0,
@@ -1557,10 +1627,12 @@
15571627
sqlite3_create_function(db, "url_nouser", 1, SQLITE_UTF8, 0,
15581628
url_nouser_func,0,0);
15591629
sqlite3_create_function(db, "chat_msg_from_event", 4,
15601630
SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
15611631
chat_msg_from_event, 0, 0);
1632
+ sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
1633
+ file_inode_sql_func,0,0);
15621634
15631635
}
15641636
15651637
#if USE_SEE
15661638
/*
@@ -2103,11 +2175,11 @@
21032175
sqlite3 *, char **, const sqlite3_api_routines *
21042176
);
21052177
sqlite3_appendvfs_init(0,0,0);
21062178
g.zVfsName = "apndvfs";
21072179
}
2108
- blob_zero(&bNameCheck);
2180
+ blob_reset(&bNameCheck);
21092181
rc = sqlite3_open_v2(
21102182
zDbName, &db,
21112183
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
21122184
g.zVfsName
21132185
);
@@ -2623,13 +2695,18 @@
26232695
return res;
26242696
}
26252697
26262698
/*
26272699
** COMMAND: test-is-repo
2700
+** Usage: %fossil test-is-repo FILENAME...
2701
+**
2702
+** Test whether the specified files look like a SQLite database
2703
+** containing a Fossil repository schema.
26282704
*/
26292705
void test_is_repo(void){
26302706
int i;
2707
+ verify_all_options();
26312708
for(i=2; i<g.argc; i++){
26322709
fossil_print("%s: %s\n",
26332710
db_looks_like_a_repository(g.argv[i]) ? "yes" : " no",
26342711
g.argv[i]
26352712
);
@@ -4080,11 +4157,11 @@
40804157
** specified then that version is checked out. Otherwise the most recent
40814158
** check-in on the main branch (usually "trunk") is used.
40824159
**
40834160
** REPOSITORY can be the filename for a repository that already exists on the
40844161
** local machine or it can be a URI for a remote repository. If REPOSITORY
4085
-** is a URI in one of the formats recognized by the [[clone]] command, then
4162
+** is a URI in one of the formats recognized by the [[clone]] command, the
40864163
** remote repo is first cloned, then the clone is opened. The clone will be
40874164
** stored in the current directory, or in DIR if the "--repodir DIR" option
40884165
** is used. The name of the clone will be taken from the last term of the URI.
40894166
** For "http:" and "https:" URIs, you can append an extra term to the end of
40904167
** the URI to get any repository name you like. For example:
@@ -4681,17 +4758,10 @@
46814758
** to obtain a check-in lock during auto-sync, the server will
46824759
** send the "pragma avoid-delta-manifests" statement in its reply,
46834760
** which will cause the client to avoid generating a delta
46844761
** manifest.
46854762
*/
4686
-/*
4687
-** SETTING: forum-close-policy boolean default=off
4688
-** If true, forum moderators may close/re-open forum posts, and reply
4689
-** to closed posts. If false, only administrators may do so. Note that
4690
-** this only affects the forum web UI, not post-closing tags which
4691
-** arrive via the command-line or from synchronization with a remote.
4692
-*/
46934763
/*
46944764
** SETTING: gdiff-command width=40 default=gdiff sensitive
46954765
** The value is an external command to run when performing a graphical
46964766
** diff. If undefined, text diff will be used.
46974767
*/
@@ -5362,11 +5432,11 @@
53625432
}
53635433
53645434
/*
53655435
** Compute a "fingerprint" on the repository. A fingerprint is used
53665436
** to verify that that the repository has not been replaced by a clone
5367
-** of the same repository. More precisely, a fingerprint are used to
5437
+** of the same repository. More precisely, a fingerprint is used to
53685438
** verify that the mapping between SHA3 hashes and RID values is unchanged.
53695439
**
53705440
** The check-out database ("localdb") stores RID values. When associating
53715441
** a check-out database against a repository database, it is useful to verify
53725442
** the fingerprint so that we know tha the RID values in the check-out
@@ -5427,11 +5497,11 @@
54275497
** COMMAND: test-fingerprint
54285498
**
54295499
** Usage: %fossil test-fingerprint ?RCVID?
54305500
**
54315501
** Display the repository fingerprint using the supplied RCVID or
5432
-** using the latest RCVID if not is given on the command line.
5502
+** using the latest RCVID if none is given on the command line.
54335503
** Show both the legacy and the newer version of the fingerprint,
54345504
** and the currently stored fingerprint if there is one.
54355505
*/
54365506
void test_fingerprint(void){
54375507
int rcvid = 0;
54385508
--- src/db.c
+++ src/db.c
@@ -170,10 +170,12 @@
170 void *pAuthArg; /* Argument to the authorizer */
171 const char *zAuthName; /* Name of the authorizer */
172 int bProtectTriggers; /* True if protection triggers already exist */
173 int nProtect; /* Slots of aProtect used */
174 unsigned aProtect[12]; /* Saved values of protectMask */
 
 
175 } db = {
176 PROTECT_USER|PROTECT_CONFIG|PROTECT_BASELINE, /* protectMask */
177 0, 0, 0, 0, 0, 0, 0, {{0}}, {0}, {0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0}};
178
179 /*
@@ -643,10 +645,43 @@
643 */
644 #define DB_PREPARE_IGNORE_ERROR 0x001 /* Suppress errors */
645 #define DB_PREPARE_PERSISTENT 0x002 /* Stmt will stick around for a while */
646 #endif
647
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
648 /*
649 ** Prepare a Stmt. Assume that the Stmt is previously uninitialized.
650 ** If the input string contains multiple SQL statements, only the first
651 ** one is processed. All statements beyond the first are silently ignored.
652 */
@@ -658,10 +693,11 @@
658 blob_zero(&pStmt->sql);
659 blob_vappendf(&pStmt->sql, zFormat, ap);
660 va_end(ap);
661 zSql = blob_str(&pStmt->sql);
662 db.nPrepare++;
 
663 if( flags & DB_PREPARE_PERSISTENT ){
664 prepFlags = SQLITE_PREPARE_PERSISTENT;
665 }
666 rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra);
667 if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
@@ -1047,10 +1083,11 @@
1047 rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
1048 if( rc ){
1049 db_err("%s: {%s}", sqlite3_errmsg(g.db), z);
1050 }else if( pStmt ){
1051 db.nPrepare++;
 
1052 while( sqlite3_step(pStmt)==SQLITE_ROW ){}
1053 rc = sqlite3_finalize(pStmt);
1054 if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z);
1055 }
1056 z = zEnd;
@@ -1312,10 +1349,50 @@
1312 sqlite3_result_int64(context, rid);
1313 }
1314 }
1315 }
1316
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1317 /*
1318 ** The toLocal() SQL function returns a string that is an argument to a
1319 ** date/time function that is appropriate for modifying the time for display.
1320 ** If UTC time display is selected, no modification occurs. If local time
1321 ** display is selected, the time is adjusted appropriately.
@@ -1327,18 +1404,11 @@
1327 void db_tolocal_function(
1328 sqlite3_context *context,
1329 int argc,
1330 sqlite3_value **argv
1331 ){
1332 if( g.fTimeFormat==0 ){
1333 if( db_get_int("timeline-utc", 1) ){
1334 g.fTimeFormat = 1;
1335 }else{
1336 g.fTimeFormat = 2;
1337 }
1338 }
1339 if( g.fTimeFormat==1 ){
1340 sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC);
1341 }else{
1342 sqlite3_result_text(context, "localtime", -1, SQLITE_STATIC);
1343 }
1344 }
@@ -1356,18 +1426,11 @@
1356 void db_fromlocal_function(
1357 sqlite3_context *context,
1358 int argc,
1359 sqlite3_value **argv
1360 ){
1361 if( g.fTimeFormat==0 ){
1362 if( db_get_int("timeline-utc", 1) ){
1363 g.fTimeFormat = 1;
1364 }else{
1365 g.fTimeFormat = 2;
1366 }
1367 }
1368 if( g.fTimeFormat==1 ){
1369 sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC);
1370 }else{
1371 sqlite3_result_text(context, "utc", -1, SQLITE_STATIC);
1372 }
1373 }
@@ -1524,24 +1587,31 @@
1524 ** Register the SQL functions that are useful both to the internal
1525 ** representation and to the "fossil sql" command.
1526 */
1527 void db_add_aux_functions(sqlite3 *db){
1528 sqlite3_create_collation(db, "uintnocase", SQLITE_UTF8,0,uintNocaseCollFunc);
1529 sqlite3_create_function(db, "checkin_mtime", 2, SQLITE_UTF8, 0,
1530 db_checkin_mtime_function, 0, 0);
1531 sqlite3_create_function(db, "symbolic_name_to_rid", 1, SQLITE_UTF8, 0,
1532 db_sym2rid_function, 0, 0);
1533 sqlite3_create_function(db, "symbolic_name_to_rid", 2, SQLITE_UTF8, 0,
1534 db_sym2rid_function, 0, 0);
1535 sqlite3_create_function(db, "now", 0, SQLITE_UTF8, 0,
 
 
 
 
1536 db_now_function, 0, 0);
1537 sqlite3_create_function(db, "toLocal", 0, SQLITE_UTF8, 0,
1538 db_tolocal_function, 0, 0);
1539 sqlite3_create_function(db, "fromLocal", 0, SQLITE_UTF8, 0,
1540 db_fromlocal_function, 0, 0);
1541 sqlite3_create_function(db, "hextoblob", 1, SQLITE_UTF8, 0,
1542 db_hextoblob, 0, 0);
 
 
 
1543 sqlite3_create_function(db, "capunion", 1, SQLITE_UTF8, 0,
1544 0, capability_union_step, capability_union_finalize);
1545 sqlite3_create_function(db, "fullcap", 1, SQLITE_UTF8, 0,
1546 capability_fullcap, 0, 0);
1547 sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0,
@@ -1557,10 +1627,12 @@
1557 sqlite3_create_function(db, "url_nouser", 1, SQLITE_UTF8, 0,
1558 url_nouser_func,0,0);
1559 sqlite3_create_function(db, "chat_msg_from_event", 4,
1560 SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
1561 chat_msg_from_event, 0, 0);
 
 
1562
1563 }
1564
1565 #if USE_SEE
1566 /*
@@ -2103,11 +2175,11 @@
2103 sqlite3 *, char **, const sqlite3_api_routines *
2104 );
2105 sqlite3_appendvfs_init(0,0,0);
2106 g.zVfsName = "apndvfs";
2107 }
2108 blob_zero(&bNameCheck);
2109 rc = sqlite3_open_v2(
2110 zDbName, &db,
2111 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
2112 g.zVfsName
2113 );
@@ -2623,13 +2695,18 @@
2623 return res;
2624 }
2625
2626 /*
2627 ** COMMAND: test-is-repo
 
 
 
 
2628 */
2629 void test_is_repo(void){
2630 int i;
 
2631 for(i=2; i<g.argc; i++){
2632 fossil_print("%s: %s\n",
2633 db_looks_like_a_repository(g.argv[i]) ? "yes" : " no",
2634 g.argv[i]
2635 );
@@ -4080,11 +4157,11 @@
4080 ** specified then that version is checked out. Otherwise the most recent
4081 ** check-in on the main branch (usually "trunk") is used.
4082 **
4083 ** REPOSITORY can be the filename for a repository that already exists on the
4084 ** local machine or it can be a URI for a remote repository. If REPOSITORY
4085 ** is a URI in one of the formats recognized by the [[clone]] command, then
4086 ** remote repo is first cloned, then the clone is opened. The clone will be
4087 ** stored in the current directory, or in DIR if the "--repodir DIR" option
4088 ** is used. The name of the clone will be taken from the last term of the URI.
4089 ** For "http:" and "https:" URIs, you can append an extra term to the end of
4090 ** the URI to get any repository name you like. For example:
@@ -4681,17 +4758,10 @@
4681 ** to obtain a check-in lock during auto-sync, the server will
4682 ** send the "pragma avoid-delta-manifests" statement in its reply,
4683 ** which will cause the client to avoid generating a delta
4684 ** manifest.
4685 */
4686 /*
4687 ** SETTING: forum-close-policy boolean default=off
4688 ** If true, forum moderators may close/re-open forum posts, and reply
4689 ** to closed posts. If false, only administrators may do so. Note that
4690 ** this only affects the forum web UI, not post-closing tags which
4691 ** arrive via the command-line or from synchronization with a remote.
4692 */
4693 /*
4694 ** SETTING: gdiff-command width=40 default=gdiff sensitive
4695 ** The value is an external command to run when performing a graphical
4696 ** diff. If undefined, text diff will be used.
4697 */
@@ -5362,11 +5432,11 @@
5362 }
5363
5364 /*
5365 ** Compute a "fingerprint" on the repository. A fingerprint is used
5366 ** to verify that that the repository has not been replaced by a clone
5367 ** of the same repository. More precisely, a fingerprint are used to
5368 ** verify that the mapping between SHA3 hashes and RID values is unchanged.
5369 **
5370 ** The check-out database ("localdb") stores RID values. When associating
5371 ** a check-out database against a repository database, it is useful to verify
5372 ** the fingerprint so that we know tha the RID values in the check-out
@@ -5427,11 +5497,11 @@
5427 ** COMMAND: test-fingerprint
5428 **
5429 ** Usage: %fossil test-fingerprint ?RCVID?
5430 **
5431 ** Display the repository fingerprint using the supplied RCVID or
5432 ** using the latest RCVID if not is given on the command line.
5433 ** Show both the legacy and the newer version of the fingerprint,
5434 ** and the currently stored fingerprint if there is one.
5435 */
5436 void test_fingerprint(void){
5437 int rcvid = 0;
5438
--- src/db.c
+++ src/db.c
@@ -170,10 +170,12 @@
170 void *pAuthArg; /* Argument to the authorizer */
171 const char *zAuthName; /* Name of the authorizer */
172 int bProtectTriggers; /* True if protection triggers already exist */
173 int nProtect; /* Slots of aProtect used */
174 unsigned aProtect[12]; /* Saved values of protectMask */
175 int pauseDmlLog; /* Ignore pDmlLog if positive */
176 Blob *pDmlLog; /* Append DML statements here, of not NULL */
177 } db = {
178 PROTECT_USER|PROTECT_CONFIG|PROTECT_BASELINE, /* protectMask */
179 0, 0, 0, 0, 0, 0, 0, {{0}}, {0}, {0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0}};
180
181 /*
@@ -643,10 +645,43 @@
645 */
646 #define DB_PREPARE_IGNORE_ERROR 0x001 /* Suppress errors */
647 #define DB_PREPARE_PERSISTENT 0x002 /* Stmt will stick around for a while */
648 #endif
649
650 /*
651 ** If zSql is a DML statement, append it db.pDmlLog.
652 */
653 static void db_append_dml(const char *zSql){
654 size_t nSql;
655 if( db.pDmlLog==0 ) return;
656 if( db.pauseDmlLog ) return;
657 if( zSql==0 ) return;
658 nSql = strlen(zSql);
659 while( nSql>0 && fossil_isspace(zSql[0]) ){ nSql--; zSql++; }
660 while( nSql>0 && fossil_isspace(zSql[nSql-1]) ) nSql--;
661 if( nSql<6 ) return;
662 if( fossil_strnicmp(zSql, "SELECT", 6)==0 ) return;
663 if( fossil_strnicmp(zSql, "PRAGMA", 6)==0 ) return;
664 blob_append(db.pDmlLog, zSql, nSql);
665 if( zSql[nSql-1]!=';' ) blob_append_char(db.pDmlLog, ';');
666 blob_append_char(db.pDmlLog, '\n');
667 }
668
669 /*
670 ** Set the Blob to which DML statement text should be appended. Set it
671 ** to zero to stop appending DML statement text.
672 */
673 void db_append_dml_to_blob(Blob *pBlob){
674 db.pDmlLog = pBlob;
675 }
676
677 /*
678 ** Pause or unpause the DML log
679 */
680 void db_pause_dml_log(void){ db.pauseDmlLog++; }
681 void db_unpause_dml_log(void){ db.pauseDmlLog--; }
682
683 /*
684 ** Prepare a Stmt. Assume that the Stmt is previously uninitialized.
685 ** If the input string contains multiple SQL statements, only the first
686 ** one is processed. All statements beyond the first are silently ignored.
687 */
@@ -658,10 +693,11 @@
693 blob_zero(&pStmt->sql);
694 blob_vappendf(&pStmt->sql, zFormat, ap);
695 va_end(ap);
696 zSql = blob_str(&pStmt->sql);
697 db.nPrepare++;
698 db_append_dml(zSql);
699 if( flags & DB_PREPARE_PERSISTENT ){
700 prepFlags = SQLITE_PREPARE_PERSISTENT;
701 }
702 rc = sqlite3_prepare_v3(g.db, zSql, -1, prepFlags, &pStmt->pStmt, &zExtra);
703 if( rc!=0 && (flags & DB_PREPARE_IGNORE_ERROR)==0 ){
@@ -1047,10 +1083,11 @@
1083 rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
1084 if( rc ){
1085 db_err("%s: {%s}", sqlite3_errmsg(g.db), z);
1086 }else if( pStmt ){
1087 db.nPrepare++;
1088 db_append_dml(sqlite3_sql(pStmt));
1089 while( sqlite3_step(pStmt)==SQLITE_ROW ){}
1090 rc = sqlite3_finalize(pStmt);
1091 if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z);
1092 }
1093 z = zEnd;
@@ -1312,10 +1349,50 @@
1349 sqlite3_result_int64(context, rid);
1350 }
1351 }
1352 }
1353
1354
1355 /*
1356 ** SETTING: timeline-utc boolean default=on
1357 **
1358 ** If the timeline-utc setting is true, then Fossil tries to understand
1359 ** and display all time values using UTC. If this setting is false, Fossil
1360 ** tries to understand and display time values using the local timezone.
1361 **
1362 ** The word "timeline" in the name of this setting is historical.
1363 ** This setting applies to all user interfaces of Fossil,
1364 ** not just the timeline.
1365 **
1366 ** Note that when accessing Fossil using the web interface, the localtime
1367 ** used is the localtime on the server, not on the client.
1368 */
1369 /*
1370 ** Return true if Fossil is set to display times as UTC. Return false
1371 ** if it wants to display times using the local timezone.
1372 **
1373 ** False is returned if display is set to localtime even if the localtime
1374 ** happens to be the same as UTC.
1375 */
1376 int fossil_ui_utctime(void){
1377 if( g.fTimeFormat==0 ){
1378 if( db_get_int("timeline-utc", 1) ){
1379 g.fTimeFormat = 1; /* UTC */
1380 }else{
1381 g.fTimeFormat = 2; /* Localtime */
1382 }
1383 }
1384 return g.fTimeFormat==1;
1385 }
1386
1387 /*
1388 ** Return true if Fossil is set to display times using the local timezone.
1389 */
1390 int fossil_ui_localtime(void){
1391 return fossil_ui_utctime()==0;
1392 }
1393
1394 /*
1395 ** The toLocal() SQL function returns a string that is an argument to a
1396 ** date/time function that is appropriate for modifying the time for display.
1397 ** If UTC time display is selected, no modification occurs. If local time
1398 ** display is selected, the time is adjusted appropriately.
@@ -1327,18 +1404,11 @@
1404 void db_tolocal_function(
1405 sqlite3_context *context,
1406 int argc,
1407 sqlite3_value **argv
1408 ){
1409 if( fossil_ui_utctime() ){
 
 
 
 
 
 
 
1410 sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC);
1411 }else{
1412 sqlite3_result_text(context, "localtime", -1, SQLITE_STATIC);
1413 }
1414 }
@@ -1356,18 +1426,11 @@
1426 void db_fromlocal_function(
1427 sqlite3_context *context,
1428 int argc,
1429 sqlite3_value **argv
1430 ){
1431 if( fossil_ui_utctime() ){
 
 
 
 
 
 
 
1432 sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC);
1433 }else{
1434 sqlite3_result_text(context, "utc", -1, SQLITE_STATIC);
1435 }
1436 }
@@ -1524,24 +1587,31 @@
1587 ** Register the SQL functions that are useful both to the internal
1588 ** representation and to the "fossil sql" command.
1589 */
1590 void db_add_aux_functions(sqlite3 *db){
1591 sqlite3_create_collation(db, "uintnocase", SQLITE_UTF8,0,uintNocaseCollFunc);
1592 sqlite3_create_function(db, "checkin_mtime", 2,
1593 SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
1594 0, db_checkin_mtime_function, 0, 0);
1595 sqlite3_create_function(db, "symbolic_name_to_rid", 1,
1596 SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
1597 0, db_sym2rid_function, 0, 0);
1598 sqlite3_create_function(db, "symbolic_name_to_rid", 2,
1599 SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
1600 0, db_sym2rid_function, 0, 0);
1601 sqlite3_create_function(db, "now", 0,
1602 SQLITE_UTF8|SQLITE_INNOCUOUS, 0,
1603 db_now_function, 0, 0);
1604 sqlite3_create_function(db, "toLocal", 0,
1605 SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
1606 0, db_tolocal_function, 0, 0);
1607 sqlite3_create_function(db, "fromLocal", 0,
1608 SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
1609 0, db_fromlocal_function, 0, 0);
1610 sqlite3_create_function(db, "hextoblob", 1,
1611 SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS,
1612 0, db_hextoblob, 0, 0);
1613 sqlite3_create_function(db, "capunion", 1, SQLITE_UTF8, 0,
1614 0, capability_union_step, capability_union_finalize);
1615 sqlite3_create_function(db, "fullcap", 1, SQLITE_UTF8, 0,
1616 capability_fullcap, 0, 0);
1617 sqlite3_create_function(db, "find_emailaddr", 1, SQLITE_UTF8, 0,
@@ -1557,10 +1627,12 @@
1627 sqlite3_create_function(db, "url_nouser", 1, SQLITE_UTF8, 0,
1628 url_nouser_func,0,0);
1629 sqlite3_create_function(db, "chat_msg_from_event", 4,
1630 SQLITE_UTF8 | SQLITE_INNOCUOUS, 0,
1631 chat_msg_from_event, 0, 0);
1632 sqlite3_create_function(db, "inode", 1, SQLITE_UTF8, 0,
1633 file_inode_sql_func,0,0);
1634
1635 }
1636
1637 #if USE_SEE
1638 /*
@@ -2103,11 +2175,11 @@
2175 sqlite3 *, char **, const sqlite3_api_routines *
2176 );
2177 sqlite3_appendvfs_init(0,0,0);
2178 g.zVfsName = "apndvfs";
2179 }
2180 blob_reset(&bNameCheck);
2181 rc = sqlite3_open_v2(
2182 zDbName, &db,
2183 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
2184 g.zVfsName
2185 );
@@ -2623,13 +2695,18 @@
2695 return res;
2696 }
2697
2698 /*
2699 ** COMMAND: test-is-repo
2700 ** Usage: %fossil test-is-repo FILENAME...
2701 **
2702 ** Test whether the specified files look like a SQLite database
2703 ** containing a Fossil repository schema.
2704 */
2705 void test_is_repo(void){
2706 int i;
2707 verify_all_options();
2708 for(i=2; i<g.argc; i++){
2709 fossil_print("%s: %s\n",
2710 db_looks_like_a_repository(g.argv[i]) ? "yes" : " no",
2711 g.argv[i]
2712 );
@@ -4080,11 +4157,11 @@
4157 ** specified then that version is checked out. Otherwise the most recent
4158 ** check-in on the main branch (usually "trunk") is used.
4159 **
4160 ** REPOSITORY can be the filename for a repository that already exists on the
4161 ** local machine or it can be a URI for a remote repository. If REPOSITORY
4162 ** is a URI in one of the formats recognized by the [[clone]] command, the
4163 ** remote repo is first cloned, then the clone is opened. The clone will be
4164 ** stored in the current directory, or in DIR if the "--repodir DIR" option
4165 ** is used. The name of the clone will be taken from the last term of the URI.
4166 ** For "http:" and "https:" URIs, you can append an extra term to the end of
4167 ** the URI to get any repository name you like. For example:
@@ -4681,17 +4758,10 @@
4758 ** to obtain a check-in lock during auto-sync, the server will
4759 ** send the "pragma avoid-delta-manifests" statement in its reply,
4760 ** which will cause the client to avoid generating a delta
4761 ** manifest.
4762 */
 
 
 
 
 
 
 
4763 /*
4764 ** SETTING: gdiff-command width=40 default=gdiff sensitive
4765 ** The value is an external command to run when performing a graphical
4766 ** diff. If undefined, text diff will be used.
4767 */
@@ -5362,11 +5432,11 @@
5432 }
5433
5434 /*
5435 ** Compute a "fingerprint" on the repository. A fingerprint is used
5436 ** to verify that that the repository has not been replaced by a clone
5437 ** of the same repository. More precisely, a fingerprint is used to
5438 ** verify that the mapping between SHA3 hashes and RID values is unchanged.
5439 **
5440 ** The check-out database ("localdb") stores RID values. When associating
5441 ** a check-out database against a repository database, it is useful to verify
5442 ** the fingerprint so that we know tha the RID values in the check-out
@@ -5427,11 +5497,11 @@
5497 ** COMMAND: test-fingerprint
5498 **
5499 ** Usage: %fossil test-fingerprint ?RCVID?
5500 **
5501 ** Display the repository fingerprint using the supplied RCVID or
5502 ** using the latest RCVID if none is given on the command line.
5503 ** Show both the legacy and the newer version of the fingerprint,
5504 ** and the currently stored fingerprint if there is one.
5505 */
5506 void test_fingerprint(void){
5507 int rcvid = 0;
5508
+10 -4
--- src/default.css
+++ src/default.css
@@ -748,10 +748,20 @@
748748
border-bottom: 3px solid gold;
749749
}
750750
body.tkt div.content ol.tkt-changes > li:target > ol {
751751
border-left: 1px solid gold;
752752
}
753
+body.cpage-ckout .file-change-line,
754
+body.cpage-info .file-change-line,
755
+body.cpage-vdiff .file-change-line {
756
+ margin-top: 16px;
757
+ margin-bottom: 16px;
758
+ margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
759
+ display: flex;
760
+ flex-direction: row;
761
+ justify-content: space-between;
762
+}
753763
754764
span.modpending {
755765
color: #b03800;
756766
font-style: italic;
757767
}
@@ -1818,14 +1828,10 @@
18181828
}
18191829
body.fossil-dark-style .settings-icon {
18201830
filter: invert(100%);
18211831
}
18221832
1823
-input[type="checkbox"].diff-toggle {
1824
- float: right;
1825
-}
1826
-
18271833
body.branch .brlist > table > tbody > tr:hover:not(.selected),
18281834
body.branch .brlist > table > tbody > tr.selected {
18291835
background-color: #ffc;
18301836
}
18311837
body.branch .brlist > table > tbody td:first-child > input {
18321838
--- src/default.css
+++ src/default.css
@@ -748,10 +748,20 @@
748 border-bottom: 3px solid gold;
749 }
750 body.tkt div.content ol.tkt-changes > li:target > ol {
751 border-left: 1px solid gold;
752 }
 
 
 
 
 
 
 
 
 
 
753
754 span.modpending {
755 color: #b03800;
756 font-style: italic;
757 }
@@ -1818,14 +1828,10 @@
1818 }
1819 body.fossil-dark-style .settings-icon {
1820 filter: invert(100%);
1821 }
1822
1823 input[type="checkbox"].diff-toggle {
1824 float: right;
1825 }
1826
1827 body.branch .brlist > table > tbody > tr:hover:not(.selected),
1828 body.branch .brlist > table > tbody > tr.selected {
1829 background-color: #ffc;
1830 }
1831 body.branch .brlist > table > tbody td:first-child > input {
1832
--- src/default.css
+++ src/default.css
@@ -748,10 +748,20 @@
748 border-bottom: 3px solid gold;
749 }
750 body.tkt div.content ol.tkt-changes > li:target > ol {
751 border-left: 1px solid gold;
752 }
753 body.cpage-ckout .file-change-line,
754 body.cpage-info .file-change-line,
755 body.cpage-vdiff .file-change-line {
756 margin-top: 16px;
757 margin-bottom: 16px;
758 margin-right: 1em /* keep it from nudging right up against the scrollbar-reveal zone */;
759 display: flex;
760 flex-direction: row;
761 justify-content: space-between;
762 }
763
764 span.modpending {
765 color: #b03800;
766 font-style: italic;
767 }
@@ -1818,14 +1828,10 @@
1828 }
1829 body.fossil-dark-style .settings-icon {
1830 filter: invert(100%);
1831 }
1832
 
 
 
 
1833 body.branch .brlist > table > tbody > tr:hover:not(.selected),
1834 body.branch .brlist > table > tbody > tr.selected {
1835 background-color: #ffc;
1836 }
1837 body.branch .brlist > table > tbody td:first-child > input {
1838
+50 -48
--- src/delta.c
+++ src/delta.c
@@ -225,59 +225,61 @@
225225
** of four bytes.
226226
*/
227227
static unsigned int checksum(const char *zIn, size_t N){
228228
static const int byteOrderTest = 1;
229229
const unsigned char *z = (const unsigned char *)zIn;
230
- const unsigned char *zEnd = (const unsigned char*)&zIn[N&~3];
231230
unsigned sum = 0;
232
- assert( (z - (const unsigned char*)0)%4==0 ); /* Four-byte alignment */
233
- if( 0==*(char*)&byteOrderTest ){
234
- /* This is a big-endian machine */
235
- while( z<zEnd ){
236
- sum += *(unsigned*)z;
237
- z += 4;
238
- }
239
- }else{
240
- /* A little-endian machine */
241
-#if GCC_VERSION>=4003000
242
- while( z<zEnd ){
243
- sum += __builtin_bswap32(*(unsigned*)z);
244
- z += 4;
245
- }
246
-#elif defined(_MSC_VER) && _MSC_VER>=1300
247
- while( z<zEnd ){
248
- sum += _byteswap_ulong(*(unsigned*)z);
249
- z += 4;
250
- }
251
-#else
252
- unsigned sum0 = 0;
253
- unsigned sum1 = 0;
254
- unsigned sum2 = 0;
255
- while(N >= 16){
256
- sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]);
257
- sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]);
258
- sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]);
259
- sum += ((unsigned)z[3] + z[7] + z[11]+ z[15]);
260
- z += 16;
261
- N -= 16;
262
- }
263
- while(N >= 4){
264
- sum0 += z[0];
265
- sum1 += z[1];
266
- sum2 += z[2];
267
- sum += z[3];
268
- z += 4;
269
- N -= 4;
270
- }
271
- sum += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
272
-#endif
273
- }
274
- switch(N&3){
275
- case 3: sum += (z[2] << 8);
276
- case 2: sum += (z[1] << 16);
277
- case 1: sum += (z[0] << 24);
278
- default: ;
231
+ if( N>0 ){
232
+ const unsigned char *zEnd = (const unsigned char*)&zIn[N&~3];
233
+ assert( (z - (const unsigned char*)0)%4==0 ); /* Four-byte alignment */
234
+ if( 0==*(char*)&byteOrderTest ){
235
+ /* This is a big-endian machine */
236
+ while( z<zEnd ){
237
+ sum += *(unsigned*)z;
238
+ z += 4;
239
+ }
240
+ }else{
241
+ /* A little-endian machine */
242
+ #if GCC_VERSION>=4003000
243
+ while( z<zEnd ){
244
+ sum += __builtin_bswap32(*(unsigned*)z);
245
+ z += 4;
246
+ }
247
+ #elif defined(_MSC_VER) && _MSC_VER>=1300
248
+ while( z<zEnd ){
249
+ sum += _byteswap_ulong(*(unsigned*)z);
250
+ z += 4;
251
+ }
252
+ #else
253
+ unsigned sum0 = 0;
254
+ unsigned sum1 = 0;
255
+ unsigned sum2 = 0;
256
+ while(N >= 16){
257
+ sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]);
258
+ sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]);
259
+ sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]);
260
+ sum += ((unsigned)z[3] + z[7] + z[11]+ z[15]);
261
+ z += 16;
262
+ N -= 16;
263
+ }
264
+ while(N >= 4){
265
+ sum0 += z[0];
266
+ sum1 += z[1];
267
+ sum2 += z[2];
268
+ sum += z[3];
269
+ z += 4;
270
+ N -= 4;
271
+ }
272
+ sum += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
273
+ #endif
274
+ }
275
+ switch(N&3){
276
+ case 3: sum += (z[2] << 8);
277
+ case 2: sum += (z[1] << 16);
278
+ case 1: sum += (z[0] << 24);
279
+ default: ;
280
+ }
279281
}
280282
return sum;
281283
}
282284
283285
/*
284286
--- src/delta.c
+++ src/delta.c
@@ -225,59 +225,61 @@
225 ** of four bytes.
226 */
227 static unsigned int checksum(const char *zIn, size_t N){
228 static const int byteOrderTest = 1;
229 const unsigned char *z = (const unsigned char *)zIn;
230 const unsigned char *zEnd = (const unsigned char*)&zIn[N&~3];
231 unsigned sum = 0;
232 assert( (z - (const unsigned char*)0)%4==0 ); /* Four-byte alignment */
233 if( 0==*(char*)&byteOrderTest ){
234 /* This is a big-endian machine */
235 while( z<zEnd ){
236 sum += *(unsigned*)z;
237 z += 4;
238 }
239 }else{
240 /* A little-endian machine */
241 #if GCC_VERSION>=4003000
242 while( z<zEnd ){
243 sum += __builtin_bswap32(*(unsigned*)z);
244 z += 4;
245 }
246 #elif defined(_MSC_VER) && _MSC_VER>=1300
247 while( z<zEnd ){
248 sum += _byteswap_ulong(*(unsigned*)z);
249 z += 4;
250 }
251 #else
252 unsigned sum0 = 0;
253 unsigned sum1 = 0;
254 unsigned sum2 = 0;
255 while(N >= 16){
256 sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]);
257 sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]);
258 sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]);
259 sum += ((unsigned)z[3] + z[7] + z[11]+ z[15]);
260 z += 16;
261 N -= 16;
262 }
263 while(N >= 4){
264 sum0 += z[0];
265 sum1 += z[1];
266 sum2 += z[2];
267 sum += z[3];
268 z += 4;
269 N -= 4;
270 }
271 sum += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
272 #endif
273 }
274 switch(N&3){
275 case 3: sum += (z[2] << 8);
276 case 2: sum += (z[1] << 16);
277 case 1: sum += (z[0] << 24);
278 default: ;
 
 
 
279 }
280 return sum;
281 }
282
283 /*
284
--- src/delta.c
+++ src/delta.c
@@ -225,59 +225,61 @@
225 ** of four bytes.
226 */
227 static unsigned int checksum(const char *zIn, size_t N){
228 static const int byteOrderTest = 1;
229 const unsigned char *z = (const unsigned char *)zIn;
 
230 unsigned sum = 0;
231 if( N>0 ){
232 const unsigned char *zEnd = (const unsigned char*)&zIn[N&~3];
233 assert( (z - (const unsigned char*)0)%4==0 ); /* Four-byte alignment */
234 if( 0==*(char*)&byteOrderTest ){
235 /* This is a big-endian machine */
236 while( z<zEnd ){
237 sum += *(unsigned*)z;
238 z += 4;
239 }
240 }else{
241 /* A little-endian machine */
242 #if GCC_VERSION>=4003000
243 while( z<zEnd ){
244 sum += __builtin_bswap32(*(unsigned*)z);
245 z += 4;
246 }
247 #elif defined(_MSC_VER) && _MSC_VER>=1300
248 while( z<zEnd ){
249 sum += _byteswap_ulong(*(unsigned*)z);
250 z += 4;
251 }
252 #else
253 unsigned sum0 = 0;
254 unsigned sum1 = 0;
255 unsigned sum2 = 0;
256 while(N >= 16){
257 sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]);
258 sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]);
259 sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]);
260 sum += ((unsigned)z[3] + z[7] + z[11]+ z[15]);
261 z += 16;
262 N -= 16;
263 }
264 while(N >= 4){
265 sum0 += z[0];
266 sum1 += z[1];
267 sum2 += z[2];
268 sum += z[3];
269 z += 4;
270 N -= 4;
271 }
272 sum += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
273 #endif
274 }
275 switch(N&3){
276 case 3: sum += (z[2] << 8);
277 case 2: sum += (z[1] << 16);
278 case 1: sum += (z[0] << 24);
279 default: ;
280 }
281 }
282 return sum;
283 }
284
285 /*
286
+49 -39
--- src/descendants.c
+++ src/descendants.c
@@ -202,29 +202,28 @@
202202
rLimitMtime = db_double(0.0,
203203
"SELECT mtime FROM event WHERE objid=%d",
204204
ridBackTo);
205205
}
206206
db_multi_exec(
207
- "WITH RECURSIVE "
208
- " parent(pid,cid,isCP) AS ("
209
- " SELECT plink.pid, plink.cid, 0 AS xisCP FROM plink"
210
- " UNION ALL"
211
- " SELECT parentid, childid, 1 FROM cherrypick WHERE NOT isExclude"
212
- " ),"
213
- " ancestor(rid, mtime, isCP) AS ("
214
- " SELECT %d, mtime, 0 FROM event WHERE objid=%d "
215
- " UNION "
216
- " SELECT parent.pid, event.mtime, parent.isCP"
217
- " FROM ancestor, parent, event"
218
- " WHERE parent.cid=ancestor.rid"
219
- " AND event.objid=parent.pid"
220
- " AND NOT ancestor.isCP"
221
- " AND (event.mtime>=%.17g OR parent.pid=%d)"
222
- " ORDER BY mtime DESC LIMIT %d"
223
- " )"
224
- "INSERT OR IGNORE INTO ok"
225
- " SELECT rid FROM ancestor;",
207
+ "WITH RECURSIVE\n"
208
+ " parent(pid,cid,isCP) AS (\n"
209
+ " SELECT plink.pid, plink.cid, 0 AS xisCP FROM plink\n"
210
+ " UNION ALL\n"
211
+ " SELECT parentid, childid, 1 FROM cherrypick WHERE NOT isExclude\n"
212
+ " ),\n"
213
+ " ancestor(rid, mtime, isCP) AS (\n"
214
+ " SELECT %d, mtime, 0 FROM event WHERE objid=%d\n"
215
+ " UNION\n"
216
+ " SELECT parent.pid, event.mtime, parent.isCP\n"
217
+ " FROM ancestor, parent, event\n"
218
+ " WHERE parent.cid=ancestor.rid\n"
219
+ " AND event.objid=parent.pid\n"
220
+ " AND NOT ancestor.isCP\n"
221
+ " AND (event.mtime>=%.17g OR parent.pid=%d)\n"
222
+ " ORDER BY mtime DESC LIMIT %d\n"
223
+ " )\n"
224
+ "INSERT OR IGNORE INTO ok SELECT rid FROM ancestor;",
226225
rid, rid, rLimitMtime, ridBackTo, N
227226
);
228227
if( ridBackTo && db_changes()>1 ){
229228
db_multi_exec("INSERT OR IGNORE INTO ok VALUES(%d)", ridBackTo);
230229
}
@@ -322,18 +321,18 @@
322321
N = -1;
323322
}else if( N<0 ){
324323
N = -N;
325324
}
326325
db_multi_exec(
327
- "WITH RECURSIVE"
328
- " dx(rid,mtime) AS ("
329
- " SELECT %d, 0"
330
- " UNION"
331
- " SELECT plink.cid, plink.mtime FROM dx, plink"
332
- " WHERE plink.pid=dx.rid"
333
- " ORDER BY 2"
334
- " )"
326
+ "WITH RECURSIVE\n"
327
+ " dx(rid,mtime) AS (\n"
328
+ " SELECT %d, 0\n"
329
+ " UNION\n"
330
+ " SELECT plink.cid, plink.mtime FROM dx, plink\n"
331
+ " WHERE plink.pid=dx.rid\n"
332
+ " ORDER BY 2\n"
333
+ " )\n"
335334
"INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d",
336335
rid, N
337336
);
338337
}
339338
@@ -639,44 +638,56 @@
639638
/* Flag parameters to compute_uses_file() */
640639
#define USESFILE_DELETE 0x01 /* Include the check-ins where file deleted */
641640
642641
#endif
643642
643
+/*
644
+** Append a new VALUES term.
645
+*/
646
+static void uses_file_append_term(Blob *pSql, int *pnCnt, int rid){
647
+ if( *pnCnt==0 ){
648
+ blob_append_sql(pSql, "(%d)", rid);
649
+ *pnCnt = 4;
650
+ }else if( (*pnCnt)%10==9 ){
651
+ blob_append_sql(pSql, ",\n (%d)", rid);
652
+ }else{
653
+ blob_append_sql(pSql, ",(%d)", rid);
654
+ }
655
+ ++*pnCnt;
656
+}
657
+
644658
645659
/*
646660
** Add to table zTab the record ID (rid) of every check-in that contains
647661
** the file fid.
648662
*/
649663
void compute_uses_file(const char *zTab, int fid, int usesFlags){
650664
Bag seen;
651665
Bag pending;
652
- Stmt ins;
666
+ Blob ins = BLOB_INITIALIZER;
667
+ int nIns = 0;
653668
Stmt q;
654669
int rid;
655670
656671
bag_init(&seen);
657672
bag_init(&pending);
658
- db_prepare(&ins, "INSERT OR IGNORE INTO \"%w\" VALUES(:rid)", zTab);
673
+ blob_append_sql(&ins, "INSERT OR IGNORE INTO \"%w\" VALUES", zTab);
659674
db_prepare(&q, "SELECT mid FROM mlink WHERE fid=%d", fid);
660675
while( db_step(&q)==SQLITE_ROW ){
661676
int mid = db_column_int(&q, 0);
662677
bag_insert(&pending, mid);
663678
bag_insert(&seen, mid);
664
- db_bind_int(&ins, ":rid", mid);
665
- db_step(&ins);
666
- db_reset(&ins);
679
+ uses_file_append_term(&ins, &nIns, mid);
667680
}
668681
db_finalize(&q);
669682
670683
db_prepare(&q, "SELECT mid FROM mlink WHERE pid=%d", fid);
671684
while( db_step(&q)==SQLITE_ROW ){
672685
int mid = db_column_int(&q, 0);
673686
bag_insert(&seen, mid);
674687
if( usesFlags & USESFILE_DELETE ){
675
- db_bind_int(&ins, ":rid", mid);
676
- db_step(&ins);
677
- db_reset(&ins);
688
+ uses_file_append_term(&ins, &nIns, mid);
678689
}
679690
}
680691
db_finalize(&q);
681692
db_prepare(&q, "SELECT cid FROM plink WHERE pid=:rid AND isprim");
682693
@@ -686,16 +697,15 @@
686697
while( db_step(&q)==SQLITE_ROW ){
687698
int mid = db_column_int(&q, 0);
688699
if( bag_find(&seen, mid) ) continue;
689700
bag_insert(&seen, mid);
690701
bag_insert(&pending, mid);
691
- db_bind_int(&ins, ":rid", mid);
692
- db_step(&ins);
693
- db_reset(&ins);
702
+ uses_file_append_term(&ins, &nIns, mid);
694703
}
695704
db_reset(&q);
696705
}
697706
db_finalize(&q);
698
- db_finalize(&ins);
707
+ db_exec_sql(blob_str(&ins));
708
+ blob_reset(&ins);
699709
bag_clear(&seen);
700710
bag_clear(&pending);
701711
}
702712
--- src/descendants.c
+++ src/descendants.c
@@ -202,29 +202,28 @@
202 rLimitMtime = db_double(0.0,
203 "SELECT mtime FROM event WHERE objid=%d",
204 ridBackTo);
205 }
206 db_multi_exec(
207 "WITH RECURSIVE "
208 " parent(pid,cid,isCP) AS ("
209 " SELECT plink.pid, plink.cid, 0 AS xisCP FROM plink"
210 " UNION ALL"
211 " SELECT parentid, childid, 1 FROM cherrypick WHERE NOT isExclude"
212 " ),"
213 " ancestor(rid, mtime, isCP) AS ("
214 " SELECT %d, mtime, 0 FROM event WHERE objid=%d "
215 " UNION "
216 " SELECT parent.pid, event.mtime, parent.isCP"
217 " FROM ancestor, parent, event"
218 " WHERE parent.cid=ancestor.rid"
219 " AND event.objid=parent.pid"
220 " AND NOT ancestor.isCP"
221 " AND (event.mtime>=%.17g OR parent.pid=%d)"
222 " ORDER BY mtime DESC LIMIT %d"
223 " )"
224 "INSERT OR IGNORE INTO ok"
225 " SELECT rid FROM ancestor;",
226 rid, rid, rLimitMtime, ridBackTo, N
227 );
228 if( ridBackTo && db_changes()>1 ){
229 db_multi_exec("INSERT OR IGNORE INTO ok VALUES(%d)", ridBackTo);
230 }
@@ -322,18 +321,18 @@
322 N = -1;
323 }else if( N<0 ){
324 N = -N;
325 }
326 db_multi_exec(
327 "WITH RECURSIVE"
328 " dx(rid,mtime) AS ("
329 " SELECT %d, 0"
330 " UNION"
331 " SELECT plink.cid, plink.mtime FROM dx, plink"
332 " WHERE plink.pid=dx.rid"
333 " ORDER BY 2"
334 " )"
335 "INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d",
336 rid, N
337 );
338 }
339
@@ -639,44 +638,56 @@
639 /* Flag parameters to compute_uses_file() */
640 #define USESFILE_DELETE 0x01 /* Include the check-ins where file deleted */
641
642 #endif
643
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
644
645 /*
646 ** Add to table zTab the record ID (rid) of every check-in that contains
647 ** the file fid.
648 */
649 void compute_uses_file(const char *zTab, int fid, int usesFlags){
650 Bag seen;
651 Bag pending;
652 Stmt ins;
 
653 Stmt q;
654 int rid;
655
656 bag_init(&seen);
657 bag_init(&pending);
658 db_prepare(&ins, "INSERT OR IGNORE INTO \"%w\" VALUES(:rid)", zTab);
659 db_prepare(&q, "SELECT mid FROM mlink WHERE fid=%d", fid);
660 while( db_step(&q)==SQLITE_ROW ){
661 int mid = db_column_int(&q, 0);
662 bag_insert(&pending, mid);
663 bag_insert(&seen, mid);
664 db_bind_int(&ins, ":rid", mid);
665 db_step(&ins);
666 db_reset(&ins);
667 }
668 db_finalize(&q);
669
670 db_prepare(&q, "SELECT mid FROM mlink WHERE pid=%d", fid);
671 while( db_step(&q)==SQLITE_ROW ){
672 int mid = db_column_int(&q, 0);
673 bag_insert(&seen, mid);
674 if( usesFlags & USESFILE_DELETE ){
675 db_bind_int(&ins, ":rid", mid);
676 db_step(&ins);
677 db_reset(&ins);
678 }
679 }
680 db_finalize(&q);
681 db_prepare(&q, "SELECT cid FROM plink WHERE pid=:rid AND isprim");
682
@@ -686,16 +697,15 @@
686 while( db_step(&q)==SQLITE_ROW ){
687 int mid = db_column_int(&q, 0);
688 if( bag_find(&seen, mid) ) continue;
689 bag_insert(&seen, mid);
690 bag_insert(&pending, mid);
691 db_bind_int(&ins, ":rid", mid);
692 db_step(&ins);
693 db_reset(&ins);
694 }
695 db_reset(&q);
696 }
697 db_finalize(&q);
698 db_finalize(&ins);
 
699 bag_clear(&seen);
700 bag_clear(&pending);
701 }
702
--- src/descendants.c
+++ src/descendants.c
@@ -202,29 +202,28 @@
202 rLimitMtime = db_double(0.0,
203 "SELECT mtime FROM event WHERE objid=%d",
204 ridBackTo);
205 }
206 db_multi_exec(
207 "WITH RECURSIVE\n"
208 " parent(pid,cid,isCP) AS (\n"
209 " SELECT plink.pid, plink.cid, 0 AS xisCP FROM plink\n"
210 " UNION ALL\n"
211 " SELECT parentid, childid, 1 FROM cherrypick WHERE NOT isExclude\n"
212 " ),\n"
213 " ancestor(rid, mtime, isCP) AS (\n"
214 " SELECT %d, mtime, 0 FROM event WHERE objid=%d\n"
215 " UNION\n"
216 " SELECT parent.pid, event.mtime, parent.isCP\n"
217 " FROM ancestor, parent, event\n"
218 " WHERE parent.cid=ancestor.rid\n"
219 " AND event.objid=parent.pid\n"
220 " AND NOT ancestor.isCP\n"
221 " AND (event.mtime>=%.17g OR parent.pid=%d)\n"
222 " ORDER BY mtime DESC LIMIT %d\n"
223 " )\n"
224 "INSERT OR IGNORE INTO ok SELECT rid FROM ancestor;",
 
225 rid, rid, rLimitMtime, ridBackTo, N
226 );
227 if( ridBackTo && db_changes()>1 ){
228 db_multi_exec("INSERT OR IGNORE INTO ok VALUES(%d)", ridBackTo);
229 }
@@ -322,18 +321,18 @@
321 N = -1;
322 }else if( N<0 ){
323 N = -N;
324 }
325 db_multi_exec(
326 "WITH RECURSIVE\n"
327 " dx(rid,mtime) AS (\n"
328 " SELECT %d, 0\n"
329 " UNION\n"
330 " SELECT plink.cid, plink.mtime FROM dx, plink\n"
331 " WHERE plink.pid=dx.rid\n"
332 " ORDER BY 2\n"
333 " )\n"
334 "INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d",
335 rid, N
336 );
337 }
338
@@ -639,44 +638,56 @@
638 /* Flag parameters to compute_uses_file() */
639 #define USESFILE_DELETE 0x01 /* Include the check-ins where file deleted */
640
641 #endif
642
643 /*
644 ** Append a new VALUES term.
645 */
646 static void uses_file_append_term(Blob *pSql, int *pnCnt, int rid){
647 if( *pnCnt==0 ){
648 blob_append_sql(pSql, "(%d)", rid);
649 *pnCnt = 4;
650 }else if( (*pnCnt)%10==9 ){
651 blob_append_sql(pSql, ",\n (%d)", rid);
652 }else{
653 blob_append_sql(pSql, ",(%d)", rid);
654 }
655 ++*pnCnt;
656 }
657
658
659 /*
660 ** Add to table zTab the record ID (rid) of every check-in that contains
661 ** the file fid.
662 */
663 void compute_uses_file(const char *zTab, int fid, int usesFlags){
664 Bag seen;
665 Bag pending;
666 Blob ins = BLOB_INITIALIZER;
667 int nIns = 0;
668 Stmt q;
669 int rid;
670
671 bag_init(&seen);
672 bag_init(&pending);
673 blob_append_sql(&ins, "INSERT OR IGNORE INTO \"%w\" VALUES", zTab);
674 db_prepare(&q, "SELECT mid FROM mlink WHERE fid=%d", fid);
675 while( db_step(&q)==SQLITE_ROW ){
676 int mid = db_column_int(&q, 0);
677 bag_insert(&pending, mid);
678 bag_insert(&seen, mid);
679 uses_file_append_term(&ins, &nIns, mid);
 
 
680 }
681 db_finalize(&q);
682
683 db_prepare(&q, "SELECT mid FROM mlink WHERE pid=%d", fid);
684 while( db_step(&q)==SQLITE_ROW ){
685 int mid = db_column_int(&q, 0);
686 bag_insert(&seen, mid);
687 if( usesFlags & USESFILE_DELETE ){
688 uses_file_append_term(&ins, &nIns, mid);
 
 
689 }
690 }
691 db_finalize(&q);
692 db_prepare(&q, "SELECT cid FROM plink WHERE pid=:rid AND isprim");
693
@@ -686,16 +697,15 @@
697 while( db_step(&q)==SQLITE_ROW ){
698 int mid = db_column_int(&q, 0);
699 if( bag_find(&seen, mid) ) continue;
700 bag_insert(&seen, mid);
701 bag_insert(&pending, mid);
702 uses_file_append_term(&ins, &nIns, mid);
 
 
703 }
704 db_reset(&q);
705 }
706 db_finalize(&q);
707 db_exec_sql(blob_str(&ins));
708 blob_reset(&ins);
709 bag_clear(&seen);
710 bag_clear(&pending);
711 }
712
+145 -6
--- src/diff.c
+++ src/diff.c
@@ -50,10 +50,11 @@
5050
#define DIFF_RAW 0x00040000 /* Raw triples - for debugging */
5151
#define DIFF_TCL 0x00080000 /* For the --tk option */
5252
#define DIFF_INCBINARY 0x00100000 /* The --diff-binary option */
5353
#define DIFF_SHOW_VERS 0x00200000 /* Show compared versions */
5454
#define DIFF_DARKMODE 0x00400000 /* Use dark mode for HTML */
55
+#define DIFF_BY_TOKEN 0x01000000 /* Split on tokens, not lines */
5556
5657
/*
5758
** Per file information that may influence output.
5859
*/
5960
#define DIFF_FILE_ADDED 0x40000000 /* Added or rename destination */
@@ -319,10 +320,113 @@
319320
320321
/* Return results */
321322
*pnLine = nLine;
322323
return a;
323324
}
325
+
326
+/*
327
+** Character classes for the purpose of tokenization.
328
+**
329
+** 1 - alphanumeric
330
+** 2 - whitespace
331
+** 3 - punctuation
332
+*/
333
+static char aTCharClass[256] = {
334
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
335
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
336
+ 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
337
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3,
338
+
339
+ 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
340
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3,
341
+ 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
342
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3,
343
+
344
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
345
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
346
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
347
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
348
+
349
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
350
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
351
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
352
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
353
+};
354
+
355
+/*
356
+** Count the number of tokens in the given string.
357
+*/
358
+static int count_tokens(const unsigned char *p, int n){
359
+ int nToken = 0;
360
+ int iPrev = 0;
361
+ int i;
362
+ for(i=0; i<n; i++){
363
+ char x = aTCharClass[p[i]];
364
+ if( x!=iPrev ){
365
+ iPrev = x;
366
+ nToken++;
367
+ }
368
+ }
369
+ return nToken;
370
+}
371
+
372
+/*
373
+** Return an array of DLine objects containing a pointer to the
374
+** start of each token and a hash of that token. The lower
375
+** bits of the hash store the length of each token.
376
+**
377
+** This is like break_into_lines() except that it works with tokens
378
+** instead of lines. A token is:
379
+**
380
+** * A contiguous sequence of alphanumeric characters.
381
+** * A contiguous sequence of whitespace
382
+** * A contiguous sequence of punctuation characters.
383
+**
384
+** Return 0 if the file is binary or contains a line that is
385
+** too long.
386
+*/
387
+static DLine *break_into_tokens(
388
+ const char *z,
389
+ int n,
390
+ int *pnToken,
391
+ u64 diffFlags
392
+){
393
+ int nToken, i, k;
394
+ u64 h, h2;
395
+ DLine *a;
396
+ unsigned char *p = (unsigned char*)z;
397
+
398
+ nToken = count_tokens(p, n);
399
+ a = fossil_malloc( sizeof(a[0])*(nToken+1) );
400
+ memset(a, 0, sizeof(a[0])*(nToken+1));
401
+ if( n==0 ){
402
+ *pnToken = 0;
403
+ return a;
404
+ }
405
+ i = 0;
406
+ while( n>0 ){
407
+ char x = aTCharClass[*p];
408
+ h = 0xcbf29ce484222325LL;
409
+ for(k=1; k<n && aTCharClass[p[k]]==x; k++){
410
+ h ^= p[k];
411
+ h *= 0x100000001b3LL;
412
+ }
413
+ a[i].z = (char*)p;
414
+ a[i].n = k;
415
+ a[i].h = h = ((h%281474976710597LL)<<LENGTH_MASK_SZ) | k;
416
+ h2 = h % nToken;
417
+ a[i].iNext = a[h2].iHash;
418
+ a[h2].iHash = i+1;
419
+ p += k; n -= k;
420
+ i++;
421
+ };
422
+ assert( i==nToken );
423
+
424
+ /* Return results */
425
+ *pnToken = nToken;
426
+ return a;
427
+}
324428
325429
/*
326430
** Return zero if two DLine elements are identical.
327431
*/
328432
static int compare_dline(const DLine *pA, const DLine *pB){
@@ -2462,11 +2566,11 @@
24622566
int span; /* combined width of the input sequences */
24632567
int cutoff = 4; /* Max hash chain entries to follow */
24642568
int nextCutoff = -1; /* Value of cutoff for next iteration */
24652569
24662570
span = (iE1 - iS1) + (iE2 - iS2);
2467
- bestScore = -10000;
2571
+ bestScore = -9223300000*(sqlite3_int64)1000000000;
24682572
score = 0;
24692573
iSXb = iSXp = iS1;
24702574
iEXb = iEXp = iS1;
24712575
iSYb = iSYp = iS2;
24722576
iEYb = iEYp = iS2;
@@ -2997,14 +3101,21 @@
29973101
if( (pCfg->diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
29983102
c.xDiffer = compare_dline_ignore_allws;
29993103
}else{
30003104
c.xDiffer = compare_dline;
30013105
}
3002
- c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
3003
- &c.nFrom, pCfg->diffFlags);
3004
- c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
3005
- &c.nTo, pCfg->diffFlags);
3106
+ if( pCfg->diffFlags & DIFF_BY_TOKEN ){
3107
+ c.aFrom = break_into_tokens(blob_str(pA_Blob), blob_size(pA_Blob),
3108
+ &c.nFrom, pCfg->diffFlags);
3109
+ c.aTo = break_into_tokens(blob_str(pB_Blob), blob_size(pB_Blob),
3110
+ &c.nTo, pCfg->diffFlags);
3111
+ }else{
3112
+ c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
3113
+ &c.nFrom, pCfg->diffFlags);
3114
+ c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
3115
+ &c.nTo, pCfg->diffFlags);
3116
+ }
30063117
if( c.aFrom==0 || c.aTo==0 ){
30073118
fossil_free(c.aFrom);
30083119
fossil_free(c.aTo);
30093120
if( pOut ){
30103121
diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, pCfg->diffFlags);
@@ -3035,10 +3146,26 @@
30353146
}
30363147
}
30373148
if( (pCfg->diffFlags & DIFF_NOOPT)==0 ){
30383149
diff_optimize(&c);
30393150
}
3151
+ if( (pCfg->diffFlags & DIFF_BY_TOKEN)!=0 ){
3152
+ /* Convert token counts into byte counts. */
3153
+ int i;
3154
+ int iA = 0;
3155
+ int iB = 0;
3156
+ for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){
3157
+ int k, sum;
3158
+ for(k=0, sum=0; k<c.aEdit[i]; k++) sum += c.aFrom[iA++].n;
3159
+ iB += c.aEdit[i];
3160
+ c.aEdit[i] = sum;
3161
+ for(k=0, sum=0; k<c.aEdit[i+1]; k++) sum += c.aFrom[iA++].n;
3162
+ c.aEdit[i+1] = sum;
3163
+ for(k=0, sum=0; k<c.aEdit[i+2]; k++) sum += c.aTo[iB++].n;
3164
+ c.aEdit[i+2] = sum;
3165
+ }
3166
+ }
30403167
30413168
if( pOut ){
30423169
if( pCfg->diffFlags & DIFF_NUMSTAT ){
30433170
int nDel = 0, nIns = 0, i;
30443171
for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){
@@ -3049,11 +3176,11 @@
30493176
g.diffCnt[2] += nDel;
30503177
if( nIns+nDel ){
30513178
g.diffCnt[0]++;
30523179
blob_appendf(pOut, "%10d %10d", nIns, nDel);
30533180
}
3054
- }else if( pCfg->diffFlags & DIFF_RAW ){
3181
+ }else if( pCfg->diffFlags & (DIFF_RAW|DIFF_BY_TOKEN) ){
30553182
const int *R = c.aEdit;
30563183
unsigned int r;
30573184
for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
30583185
blob_appendf(pOut, " copy %6d delete %6d insert %6d\n",
30593186
R[r], R[r+1], R[r+2]);
@@ -3100,20 +3227,29 @@
31003227
** Initialize the DiffConfig object using command-line options.
31013228
**
31023229
** Process diff-related command-line options and return an appropriate
31033230
** "diffFlags" integer.
31043231
**
3232
+** -b|--browser Show the diff output in a web-browser
31053233
** --brief Show filenames only DIFF_BRIEF
3234
+** --by Shorthand for "--browser -y"
31063235
** -c|--context N N lines of context. nContext
3236
+** --dark Use dark mode for Tcl/Tk and HTML output
31073237
** --html Format for HTML DIFF_HTML
3238
+** -i|--internal Use built-in diff, not an external tool
31083239
** --invert Invert the diff DIFF_INVERT
3240
+** --json Output formatted as JSON
31093241
** -n|--linenum Show line numbers DIFF_LINENO
3242
+** -N|--new-file Alias for --verbose
31103243
** --noopt Disable optimization DIFF_NOOPT
31113244
** --numstat Show change counts DIFF_NUMSTAT
31123245
** --strip-trailing-cr Strip trailing CR DIFF_STRIP_EOLCR
3246
+** --tcl Tcl-formatted output used internally by --tk
31133247
** --unified Unified diff. ~DIFF_SIDEBYSIDE
3248
+** -v|--verbose Show complete text of added or deleted files
31143249
** -w|--ignore-all-space Ignore all whitespaces DIFF_IGNORE_ALLWS
3250
+** --webpage Format output as a stand-alone HTML webpage
31153251
** -W|--width N N character lines. wColumn
31163252
** -y|--side-by-side Side-by-side diff. DIFF_SIDEBYSIDE
31173253
** -Z|--ignore-trailing-space Ignore eol-whitespaces DIFF_IGNORE_EOLWS
31183254
*/
31193255
void diff_options(DiffConfig *pCfg, int isGDiff, int bUnifiedTextOnly){
@@ -3157,10 +3293,13 @@
31573293
31583294
/* Undocumented and unsupported flags used for development
31593295
** debugging and analysis: */
31603296
if( find_option("debug",0,0)!=0 ) diffFlags |= DIFF_DEBUG;
31613297
if( find_option("raw",0,0)!=0 ) diffFlags |= DIFF_RAW;
3298
+ if( find_option("bytoken",0,0)!=0 ){
3299
+ diffFlags = DIFF_RAW|DIFF_BY_TOKEN;
3300
+ }
31623301
}
31633302
if( (z = find_option("context","c",1))!=0 ){
31643303
char *zEnd;
31653304
f = (int)strtol(z, &zEnd, 10);
31663305
if( zEnd[0]==0 && errno!=ERANGE ){
31673306
--- src/diff.c
+++ src/diff.c
@@ -50,10 +50,11 @@
50 #define DIFF_RAW 0x00040000 /* Raw triples - for debugging */
51 #define DIFF_TCL 0x00080000 /* For the --tk option */
52 #define DIFF_INCBINARY 0x00100000 /* The --diff-binary option */
53 #define DIFF_SHOW_VERS 0x00200000 /* Show compared versions */
54 #define DIFF_DARKMODE 0x00400000 /* Use dark mode for HTML */
 
55
56 /*
57 ** Per file information that may influence output.
58 */
59 #define DIFF_FILE_ADDED 0x40000000 /* Added or rename destination */
@@ -319,10 +320,113 @@
319
320 /* Return results */
321 *pnLine = nLine;
322 return a;
323 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
325 /*
326 ** Return zero if two DLine elements are identical.
327 */
328 static int compare_dline(const DLine *pA, const DLine *pB){
@@ -2462,11 +2566,11 @@
2462 int span; /* combined width of the input sequences */
2463 int cutoff = 4; /* Max hash chain entries to follow */
2464 int nextCutoff = -1; /* Value of cutoff for next iteration */
2465
2466 span = (iE1 - iS1) + (iE2 - iS2);
2467 bestScore = -10000;
2468 score = 0;
2469 iSXb = iSXp = iS1;
2470 iEXb = iEXp = iS1;
2471 iSYb = iSYp = iS2;
2472 iEYb = iEYp = iS2;
@@ -2997,14 +3101,21 @@
2997 if( (pCfg->diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
2998 c.xDiffer = compare_dline_ignore_allws;
2999 }else{
3000 c.xDiffer = compare_dline;
3001 }
3002 c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
3003 &c.nFrom, pCfg->diffFlags);
3004 c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
3005 &c.nTo, pCfg->diffFlags);
 
 
 
 
 
 
 
3006 if( c.aFrom==0 || c.aTo==0 ){
3007 fossil_free(c.aFrom);
3008 fossil_free(c.aTo);
3009 if( pOut ){
3010 diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, pCfg->diffFlags);
@@ -3035,10 +3146,26 @@
3035 }
3036 }
3037 if( (pCfg->diffFlags & DIFF_NOOPT)==0 ){
3038 diff_optimize(&c);
3039 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3040
3041 if( pOut ){
3042 if( pCfg->diffFlags & DIFF_NUMSTAT ){
3043 int nDel = 0, nIns = 0, i;
3044 for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){
@@ -3049,11 +3176,11 @@
3049 g.diffCnt[2] += nDel;
3050 if( nIns+nDel ){
3051 g.diffCnt[0]++;
3052 blob_appendf(pOut, "%10d %10d", nIns, nDel);
3053 }
3054 }else if( pCfg->diffFlags & DIFF_RAW ){
3055 const int *R = c.aEdit;
3056 unsigned int r;
3057 for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
3058 blob_appendf(pOut, " copy %6d delete %6d insert %6d\n",
3059 R[r], R[r+1], R[r+2]);
@@ -3100,20 +3227,29 @@
3100 ** Initialize the DiffConfig object using command-line options.
3101 **
3102 ** Process diff-related command-line options and return an appropriate
3103 ** "diffFlags" integer.
3104 **
 
3105 ** --brief Show filenames only DIFF_BRIEF
 
3106 ** -c|--context N N lines of context. nContext
 
3107 ** --html Format for HTML DIFF_HTML
 
3108 ** --invert Invert the diff DIFF_INVERT
 
3109 ** -n|--linenum Show line numbers DIFF_LINENO
 
3110 ** --noopt Disable optimization DIFF_NOOPT
3111 ** --numstat Show change counts DIFF_NUMSTAT
3112 ** --strip-trailing-cr Strip trailing CR DIFF_STRIP_EOLCR
 
3113 ** --unified Unified diff. ~DIFF_SIDEBYSIDE
 
3114 ** -w|--ignore-all-space Ignore all whitespaces DIFF_IGNORE_ALLWS
 
3115 ** -W|--width N N character lines. wColumn
3116 ** -y|--side-by-side Side-by-side diff. DIFF_SIDEBYSIDE
3117 ** -Z|--ignore-trailing-space Ignore eol-whitespaces DIFF_IGNORE_EOLWS
3118 */
3119 void diff_options(DiffConfig *pCfg, int isGDiff, int bUnifiedTextOnly){
@@ -3157,10 +3293,13 @@
3157
3158 /* Undocumented and unsupported flags used for development
3159 ** debugging and analysis: */
3160 if( find_option("debug",0,0)!=0 ) diffFlags |= DIFF_DEBUG;
3161 if( find_option("raw",0,0)!=0 ) diffFlags |= DIFF_RAW;
 
 
 
3162 }
3163 if( (z = find_option("context","c",1))!=0 ){
3164 char *zEnd;
3165 f = (int)strtol(z, &zEnd, 10);
3166 if( zEnd[0]==0 && errno!=ERANGE ){
3167
--- src/diff.c
+++ src/diff.c
@@ -50,10 +50,11 @@
50 #define DIFF_RAW 0x00040000 /* Raw triples - for debugging */
51 #define DIFF_TCL 0x00080000 /* For the --tk option */
52 #define DIFF_INCBINARY 0x00100000 /* The --diff-binary option */
53 #define DIFF_SHOW_VERS 0x00200000 /* Show compared versions */
54 #define DIFF_DARKMODE 0x00400000 /* Use dark mode for HTML */
55 #define DIFF_BY_TOKEN 0x01000000 /* Split on tokens, not lines */
56
57 /*
58 ** Per file information that may influence output.
59 */
60 #define DIFF_FILE_ADDED 0x40000000 /* Added or rename destination */
@@ -319,10 +320,113 @@
320
321 /* Return results */
322 *pnLine = nLine;
323 return a;
324 }
325
326 /*
327 ** Character classes for the purpose of tokenization.
328 **
329 ** 1 - alphanumeric
330 ** 2 - whitespace
331 ** 3 - punctuation
332 */
333 static char aTCharClass[256] = {
334 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
335 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
336 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
337 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3,
338
339 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
340 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3,
341 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
342 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3,
343
344 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
345 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
346 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
347 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
348
349 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
350 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
351 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
352 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
353 };
354
355 /*
356 ** Count the number of tokens in the given string.
357 */
358 static int count_tokens(const unsigned char *p, int n){
359 int nToken = 0;
360 int iPrev = 0;
361 int i;
362 for(i=0; i<n; i++){
363 char x = aTCharClass[p[i]];
364 if( x!=iPrev ){
365 iPrev = x;
366 nToken++;
367 }
368 }
369 return nToken;
370 }
371
372 /*
373 ** Return an array of DLine objects containing a pointer to the
374 ** start of each token and a hash of that token. The lower
375 ** bits of the hash store the length of each token.
376 **
377 ** This is like break_into_lines() except that it works with tokens
378 ** instead of lines. A token is:
379 **
380 ** * A contiguous sequence of alphanumeric characters.
381 ** * A contiguous sequence of whitespace
382 ** * A contiguous sequence of punctuation characters.
383 **
384 ** Return 0 if the file is binary or contains a line that is
385 ** too long.
386 */
387 static DLine *break_into_tokens(
388 const char *z,
389 int n,
390 int *pnToken,
391 u64 diffFlags
392 ){
393 int nToken, i, k;
394 u64 h, h2;
395 DLine *a;
396 unsigned char *p = (unsigned char*)z;
397
398 nToken = count_tokens(p, n);
399 a = fossil_malloc( sizeof(a[0])*(nToken+1) );
400 memset(a, 0, sizeof(a[0])*(nToken+1));
401 if( n==0 ){
402 *pnToken = 0;
403 return a;
404 }
405 i = 0;
406 while( n>0 ){
407 char x = aTCharClass[*p];
408 h = 0xcbf29ce484222325LL;
409 for(k=1; k<n && aTCharClass[p[k]]==x; k++){
410 h ^= p[k];
411 h *= 0x100000001b3LL;
412 }
413 a[i].z = (char*)p;
414 a[i].n = k;
415 a[i].h = h = ((h%281474976710597LL)<<LENGTH_MASK_SZ) | k;
416 h2 = h % nToken;
417 a[i].iNext = a[h2].iHash;
418 a[h2].iHash = i+1;
419 p += k; n -= k;
420 i++;
421 };
422 assert( i==nToken );
423
424 /* Return results */
425 *pnToken = nToken;
426 return a;
427 }
428
429 /*
430 ** Return zero if two DLine elements are identical.
431 */
432 static int compare_dline(const DLine *pA, const DLine *pB){
@@ -2462,11 +2566,11 @@
2566 int span; /* combined width of the input sequences */
2567 int cutoff = 4; /* Max hash chain entries to follow */
2568 int nextCutoff = -1; /* Value of cutoff for next iteration */
2569
2570 span = (iE1 - iS1) + (iE2 - iS2);
2571 bestScore = -9223300000*(sqlite3_int64)1000000000;
2572 score = 0;
2573 iSXb = iSXp = iS1;
2574 iEXb = iEXp = iS1;
2575 iSYb = iSYp = iS2;
2576 iEYb = iEYp = iS2;
@@ -2997,14 +3101,21 @@
3101 if( (pCfg->diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
3102 c.xDiffer = compare_dline_ignore_allws;
3103 }else{
3104 c.xDiffer = compare_dline;
3105 }
3106 if( pCfg->diffFlags & DIFF_BY_TOKEN ){
3107 c.aFrom = break_into_tokens(blob_str(pA_Blob), blob_size(pA_Blob),
3108 &c.nFrom, pCfg->diffFlags);
3109 c.aTo = break_into_tokens(blob_str(pB_Blob), blob_size(pB_Blob),
3110 &c.nTo, pCfg->diffFlags);
3111 }else{
3112 c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
3113 &c.nFrom, pCfg->diffFlags);
3114 c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
3115 &c.nTo, pCfg->diffFlags);
3116 }
3117 if( c.aFrom==0 || c.aTo==0 ){
3118 fossil_free(c.aFrom);
3119 fossil_free(c.aTo);
3120 if( pOut ){
3121 diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, pCfg->diffFlags);
@@ -3035,10 +3146,26 @@
3146 }
3147 }
3148 if( (pCfg->diffFlags & DIFF_NOOPT)==0 ){
3149 diff_optimize(&c);
3150 }
3151 if( (pCfg->diffFlags & DIFF_BY_TOKEN)!=0 ){
3152 /* Convert token counts into byte counts. */
3153 int i;
3154 int iA = 0;
3155 int iB = 0;
3156 for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){
3157 int k, sum;
3158 for(k=0, sum=0; k<c.aEdit[i]; k++) sum += c.aFrom[iA++].n;
3159 iB += c.aEdit[i];
3160 c.aEdit[i] = sum;
3161 for(k=0, sum=0; k<c.aEdit[i+1]; k++) sum += c.aFrom[iA++].n;
3162 c.aEdit[i+1] = sum;
3163 for(k=0, sum=0; k<c.aEdit[i+2]; k++) sum += c.aTo[iB++].n;
3164 c.aEdit[i+2] = sum;
3165 }
3166 }
3167
3168 if( pOut ){
3169 if( pCfg->diffFlags & DIFF_NUMSTAT ){
3170 int nDel = 0, nIns = 0, i;
3171 for(i=0; c.aEdit[i] || c.aEdit[i+1] || c.aEdit[i+2]; i+=3){
@@ -3049,11 +3176,11 @@
3176 g.diffCnt[2] += nDel;
3177 if( nIns+nDel ){
3178 g.diffCnt[0]++;
3179 blob_appendf(pOut, "%10d %10d", nIns, nDel);
3180 }
3181 }else if( pCfg->diffFlags & (DIFF_RAW|DIFF_BY_TOKEN) ){
3182 const int *R = c.aEdit;
3183 unsigned int r;
3184 for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){
3185 blob_appendf(pOut, " copy %6d delete %6d insert %6d\n",
3186 R[r], R[r+1], R[r+2]);
@@ -3100,20 +3227,29 @@
3227 ** Initialize the DiffConfig object using command-line options.
3228 **
3229 ** Process diff-related command-line options and return an appropriate
3230 ** "diffFlags" integer.
3231 **
3232 ** -b|--browser Show the diff output in a web-browser
3233 ** --brief Show filenames only DIFF_BRIEF
3234 ** --by Shorthand for "--browser -y"
3235 ** -c|--context N N lines of context. nContext
3236 ** --dark Use dark mode for Tcl/Tk and HTML output
3237 ** --html Format for HTML DIFF_HTML
3238 ** -i|--internal Use built-in diff, not an external tool
3239 ** --invert Invert the diff DIFF_INVERT
3240 ** --json Output formatted as JSON
3241 ** -n|--linenum Show line numbers DIFF_LINENO
3242 ** -N|--new-file Alias for --verbose
3243 ** --noopt Disable optimization DIFF_NOOPT
3244 ** --numstat Show change counts DIFF_NUMSTAT
3245 ** --strip-trailing-cr Strip trailing CR DIFF_STRIP_EOLCR
3246 ** --tcl Tcl-formatted output used internally by --tk
3247 ** --unified Unified diff. ~DIFF_SIDEBYSIDE
3248 ** -v|--verbose Show complete text of added or deleted files
3249 ** -w|--ignore-all-space Ignore all whitespaces DIFF_IGNORE_ALLWS
3250 ** --webpage Format output as a stand-alone HTML webpage
3251 ** -W|--width N N character lines. wColumn
3252 ** -y|--side-by-side Side-by-side diff. DIFF_SIDEBYSIDE
3253 ** -Z|--ignore-trailing-space Ignore eol-whitespaces DIFF_IGNORE_EOLWS
3254 */
3255 void diff_options(DiffConfig *pCfg, int isGDiff, int bUnifiedTextOnly){
@@ -3157,10 +3293,13 @@
3293
3294 /* Undocumented and unsupported flags used for development
3295 ** debugging and analysis: */
3296 if( find_option("debug",0,0)!=0 ) diffFlags |= DIFF_DEBUG;
3297 if( find_option("raw",0,0)!=0 ) diffFlags |= DIFF_RAW;
3298 if( find_option("bytoken",0,0)!=0 ){
3299 diffFlags = DIFF_RAW|DIFF_BY_TOKEN;
3300 }
3301 }
3302 if( (z = find_option("context","c",1))!=0 ){
3303 char *zEnd;
3304 f = (int)strtol(z, &zEnd, 10);
3305 if( zEnd[0]==0 && errno!=ERANGE ){
3306
+6 -2
--- src/diff.tcl
+++ src/diff.tcl
@@ -1,11 +1,11 @@
11
# The "diff --tk" command outputs prepends a "set fossilcmd {...}" line
22
# to this file, then runs this file using "tclsh" in order to display the
33
# graphical diff in a separate window. A typical "set fossilcmd" line
44
# looks like this:
55
#
6
-# set fossilcmd {| "./fossil" diff --html -y -i -v}
6
+# set fossilcmd {| "./fossil" diff --tcl -i -v}
77
#
88
# This header comment is stripped off by the "mkbuiltin.c" program.
99
#
1010
set prog {
1111
package require Tk
@@ -110,11 +110,15 @@
110110
111111
set fromIndex [lsearch -glob $fossilcmd *-from]
112112
set toIndex [lsearch -glob $fossilcmd *-to]
113113
set branchIndex [lsearch -glob $fossilcmd *-branch]
114114
set checkinIndex [lsearch -glob $fossilcmd *-checkin]
115
- set fA {base check-in}
115
+ if {[string match *?--external-baseline* $fossilcmd]} {
116
+ set fA {external baseline}
117
+ } else {
118
+ set fA {base check-in}
119
+ }
116120
set fB {current check-out}
117121
if {$fromIndex > -1} {set fA [lindex $fossilcmd $fromIndex+1]}
118122
if {$toIndex > -1} {set fB [lindex $fossilcmd $toIndex+1]}
119123
if {$branchIndex > -1} {set fA "branch point"; set fB "leaf of branch '[lindex $fossilcmd $branchIndex+1]'"}
120124
if {$checkinIndex > -1} {set fA "primary parent"; set fB [lindex $fossilcmd $checkinIndex+1]}
121125
--- src/diff.tcl
+++ src/diff.tcl
@@ -1,11 +1,11 @@
1 # The "diff --tk" command outputs prepends a "set fossilcmd {...}" line
2 # to this file, then runs this file using "tclsh" in order to display the
3 # graphical diff in a separate window. A typical "set fossilcmd" line
4 # looks like this:
5 #
6 # set fossilcmd {| "./fossil" diff --html -y -i -v}
7 #
8 # This header comment is stripped off by the "mkbuiltin.c" program.
9 #
10 set prog {
11 package require Tk
@@ -110,11 +110,15 @@
110
111 set fromIndex [lsearch -glob $fossilcmd *-from]
112 set toIndex [lsearch -glob $fossilcmd *-to]
113 set branchIndex [lsearch -glob $fossilcmd *-branch]
114 set checkinIndex [lsearch -glob $fossilcmd *-checkin]
115 set fA {base check-in}
 
 
 
 
116 set fB {current check-out}
117 if {$fromIndex > -1} {set fA [lindex $fossilcmd $fromIndex+1]}
118 if {$toIndex > -1} {set fB [lindex $fossilcmd $toIndex+1]}
119 if {$branchIndex > -1} {set fA "branch point"; set fB "leaf of branch '[lindex $fossilcmd $branchIndex+1]'"}
120 if {$checkinIndex > -1} {set fA "primary parent"; set fB [lindex $fossilcmd $checkinIndex+1]}
121
--- src/diff.tcl
+++ src/diff.tcl
@@ -1,11 +1,11 @@
1 # The "diff --tk" command outputs prepends a "set fossilcmd {...}" line
2 # to this file, then runs this file using "tclsh" in order to display the
3 # graphical diff in a separate window. A typical "set fossilcmd" line
4 # looks like this:
5 #
6 # set fossilcmd {| "./fossil" diff --tcl -i -v}
7 #
8 # This header comment is stripped off by the "mkbuiltin.c" program.
9 #
10 set prog {
11 package require Tk
@@ -110,11 +110,15 @@
110
111 set fromIndex [lsearch -glob $fossilcmd *-from]
112 set toIndex [lsearch -glob $fossilcmd *-to]
113 set branchIndex [lsearch -glob $fossilcmd *-branch]
114 set checkinIndex [lsearch -glob $fossilcmd *-checkin]
115 if {[string match *?--external-baseline* $fossilcmd]} {
116 set fA {external baseline}
117 } else {
118 set fA {base check-in}
119 }
120 set fB {current check-out}
121 if {$fromIndex > -1} {set fA [lindex $fossilcmd $fromIndex+1]}
122 if {$toIndex > -1} {set fB [lindex $fossilcmd $toIndex+1]}
123 if {$branchIndex > -1} {set fA "branch point"; set fB "leaf of branch '[lindex $fossilcmd $branchIndex+1]'"}
124 if {$checkinIndex > -1} {set fA "primary parent"; set fB [lindex $fossilcmd $checkinIndex+1]}
125
+97 -21
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -249,19 +249,19 @@
249249
@ margin: 0 0 0 0;
250250
@ line-height: inherit;
251251
@ font-size: inherit;
252252
@ }
253253
@ td.diffln {
254
-@ width: 1px;
254
+@ width: fit-content;
255255
@ text-align: right;
256256
@ padding: 0 1em 0 0;
257257
@ }
258258
@ td.difflne {
259259
@ padding-bottom: 0.4em;
260260
@ }
261261
@ td.diffsep {
262
-@ width: 1px;
262
+@ width: fit-content;
263263
@ padding: 0 0.3em 0 1em;
264264
@ line-height: inherit;
265265
@ font-size: inherit;
266266
@ }
267267
@ td.diffsep pre {
@@ -379,19 +379,19 @@
379379
@ margin: 0 0 0 0;
380380
@ line-height: inherit;
381381
@ font-size: inherit;
382382
@ }
383383
@ td.diffln {
384
-@ width: 1px;
384
+@ width: fit-content;
385385
@ text-align: right;
386386
@ padding: 0 1em 0 0;
387387
@ }
388388
@ td.difflne {
389389
@ padding-bottom: 0.4em;
390390
@ }
391391
@ td.diffsep {
392
-@ width: 1px;
392
+@ width: fit-content;
393393
@ padding: 0 0.3em 0 1em;
394394
@ line-height: inherit;
395395
@ font-size: inherit;
396396
@ }
397397
@ td.diffsep pre {
@@ -784,26 +784,27 @@
784784
blob_reset(&file);
785785
return rc;
786786
}
787787
788788
/*
789
-** Run a diff between the version zFrom and files on disk. zFrom might
790
-** be NULL which means to simply show the difference between the edited
791
-** files on disk and the check-out on which they are based.
789
+** Run a diff between the version zFrom and files on disk in the current
790
+** working checkout. zFrom might be NULL which means to simply show the
791
+** difference between the edited files on disk and the check-out on which
792
+** they are based.
792793
**
793794
** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the
794795
** command zDiffCmd to do the diffing.
795796
**
796797
** When using an external diff program, zBinGlob contains the GLOB patterns
797798
** for file names to treat as binary. If fIncludeBinary is zero, these files
798799
** will be skipped in addition to files that may contain binary content.
799800
*/
800
-void diff_against_disk(
801
+void diff_version_to_checkout(
801802
const char *zFrom, /* Version to difference from */
802803
DiffConfig *pCfg, /* Flags controlling diff output */
803804
FileDirList *pFileDir, /* Which files to diff */
804
- Blob *pOut /* Blob to output diff instead of stdout */
805
+ Blob *pOut /* Blob to output diff instead of stdout */
805806
){
806807
int vid;
807808
Blob sql;
808809
Stmt q;
809810
int asNewFile; /* Treat non-existant files as empty files */
@@ -928,20 +929,20 @@
928929
db_finalize(&q);
929930
db_end_transaction(1); /* ROLLBACK */
930931
}
931932
932933
/*
933
-** Run a diff between the undo buffer and files on disk.
934
+** Run a diff from the undo buffer to files on disk.
934935
**
935936
** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the
936937
** command zDiffCmd to do the diffing.
937938
**
938939
** When using an external diff program, zBinGlob contains the GLOB patterns
939940
** for file names to treat as binary. If fIncludeBinary is zero, these files
940941
** will be skipped in addition to files that may contain binary content.
941942
*/
942
-static void diff_against_undo(
943
+static void diff_undo_to_checkout(
943944
DiffConfig *pCfg, /* Flags controlling diff output */
944945
FileDirList *pFileDir /* List of files and directories to diff */
945946
){
946947
Stmt q;
947948
Blob content;
@@ -1088,10 +1089,67 @@
10881089
}
10891090
}
10901091
manifest_destroy(pFrom);
10911092
manifest_destroy(pTo);
10921093
}
1094
+
1095
+/*
1096
+** Compute the difference from an external tree of files to the current
1097
+** working checkout with its edits.
1098
+**
1099
+** To put it another way: Every managed file in the current working
1100
+** checkout is compared to the file with same name under zExternBase. The
1101
+** zExternBase files are on the left and the files in the current working
1102
+** directory are on the right.
1103
+*/
1104
+void diff_externbase_to_checkout(
1105
+ const char *zExternBase, /* Remote tree to use as the baseline */
1106
+ DiffConfig *pCfg, /* Diff settings */
1107
+ FileDirList *pFileDir /* Only look at these files */
1108
+){
1109
+ int vid;
1110
+ Stmt q;
1111
+
1112
+ vid = db_lget_int("checkout",0);
1113
+ if( file_isdir(zExternBase, ExtFILE)!=1 ){
1114
+ fossil_fatal("\"%s\" is not a directory", zExternBase);
1115
+ }
1116
+ db_prepare(&q,
1117
+ "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname",
1118
+ vid
1119
+ );
1120
+ while( db_step(&q)==SQLITE_ROW ){
1121
+ const char *zFile; /* Name of file in the repository */
1122
+ char *zLhs; /* Full name of left-hand side file */
1123
+ char *zRhs; /* Full name of right-hand side file */
1124
+ Blob rhs; /* Full text of RHS */
1125
+ Blob lhs; /* Full text of LHS */
1126
+
1127
+ zFile = db_column_text(&q,0);
1128
+ if( !file_dir_match(pFileDir, zFile) ) continue;
1129
+ zLhs = mprintf("%s/%s", zExternBase, zFile);
1130
+ zRhs = mprintf("%s%s", g.zLocalRoot, zFile);
1131
+ if( file_size(zLhs, ExtFILE)<0 ){
1132
+ blob_zero(&lhs);
1133
+ }else{
1134
+ blob_read_from_file(&lhs, zLhs, ExtFILE);
1135
+ }
1136
+ blob_read_from_file(&rhs, zRhs, ExtFILE);
1137
+ if( blob_size(&lhs)!=blob_size(&rhs)
1138
+ || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0
1139
+ ){
1140
+ diff_print_index(zFile, pCfg, 0);
1141
+ diff_file_mem(&lhs, &rhs, zFile, pCfg);
1142
+ }
1143
+ blob_reset(&lhs);
1144
+ blob_reset(&rhs);
1145
+ fossil_free(zLhs);
1146
+ fossil_free(zRhs);
1147
+ }
1148
+ db_finalize(&q);
1149
+}
1150
+
10931151
10941152
/*
10951153
** Return the name of the external diff command, or return NULL if
10961154
** no external diff command is defined.
10971155
*/
@@ -1224,10 +1282,14 @@
12241282
** option specifies the check-in from which the second version of the file
12251283
** or files is taken. If there is no "--to" option then the (possibly edited)
12261284
** files in the current check-out are used. The "--checkin VERSION" option
12271285
** shows the changes made by check-in VERSION relative to its primary parent.
12281286
** The "--branch BRANCHNAME" shows all the changes on the branch BRANCHNAME.
1287
+**
1288
+** With the "--from VERSION" option, if VERSION is actually a directory name
1289
+** (not a tag or check-in hash) then the files under that directory are used
1290
+** as the baseline for the diff.
12291291
**
12301292
** The "-i" command-line option forces the use of Fossil's own internal
12311293
** diff logic rather than any external diff program that might be configured
12321294
** using the "setting" command. If no external diff program is configured,
12331295
** then the "-i" option is a no-op. The "-i" option converts "gdiff" into
@@ -1256,25 +1318,27 @@
12561318
** with negative N meaning show all content
12571319
** --dark Use dark mode for the Tcl/Tk-based GUI and HTML
12581320
** --diff-binary BOOL Include binary files with external commands
12591321
** --exec-abs-paths Force absolute path names on external commands
12601322
** --exec-rel-paths Force relative path names on external commands
1261
-** -r|--from VERSION Select VERSION as source for the diff
1323
+** -r|--from VERSION Use VERSION as the baseline for the diff, or
1324
+** if VERSION is a directory name, use files in
1325
+** that directory as the baseline.
12621326
** -w|--ignore-all-space Ignore white space when comparing lines
12631327
** -i|--internal Use internal diff logic
12641328
** --invert Invert the diff
12651329
** --json Output formatted as JSON
12661330
** -n|--linenum Show line numbers
12671331
** -N|--new-file Alias for --verbose
12681332
** --numstat Show only the number of added and deleted lines
12691333
** -y|--side-by-side Side-by-side diff
12701334
** --strip-trailing-cr Strip trailing CR
1271
-** --tcl Tcl-formated output used internally by --tk
1335
+** --tcl Tcl-formatted output used internally by --tk
12721336
** --tclsh PATH Tcl/Tk shell used for --tk (default: "tclsh")
12731337
** --tk Launch a Tcl/Tk GUI for display
12741338
** --to VERSION Select VERSION as target for the diff
1275
-** --undo Diff against the "undo" buffer
1339
+** --undo Use the undo buffer as the baseline
12761340
** --unified Unified diff
12771341
** -v|--verbose Output complete text of added or deleted files
12781342
** -h|--versions Show compared versions in the diff header
12791343
** --webpage Format output as a stand-alone HTML webpage
12801344
** -W|--width N Width of lines in side-by-side diff
@@ -1287,10 +1351,11 @@
12871351
const char *zCheckin; /* Check-in version number */
12881352
const char *zBranch; /* Branch to diff */
12891353
int againstUndo = 0; /* Diff against files in the undo buffer */
12901354
FileDirList *pFileDir = 0; /* Restrict the diff to these files */
12911355
DiffConfig DCfg; /* Diff configuration object */
1356
+ int bFromIsDir = 0; /* True if zFrom is a directory name */
12921357
12931358
if( find_option("tk",0,0)!=0 || has_option("tclsh") ){
12941359
diff_tk("diff", 2);
12951360
return;
12961361
}
@@ -1298,11 +1363,11 @@
12981363
zFrom = find_option("from", "r", 1);
12991364
zTo = find_option("to", 0, 1);
13001365
zCheckin = find_option("checkin", "ci", 1);
13011366
zBranch = find_option("branch", 0, 1);
13021367
againstUndo = find_option("undo",0,0)!=0;
1303
- if( againstUndo && ( zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){
1368
+ if( againstUndo && (zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){
13041369
fossil_fatal("cannot use --undo together with --from, --to, --checkin,"
13051370
" or --branch");
13061371
}
13071372
if( zBranch ){
13081373
if( zTo || zFrom || zCheckin ){
@@ -1309,14 +1374,13 @@
13091374
fossil_fatal("cannot use --from, --to, or --checkin with --branch");
13101375
}
13111376
zTo = zBranch;
13121377
zFrom = mprintf("root:%s", zBranch);
13131378
}
1314
- if( zCheckin!=0 && ( zFrom!=0 || zTo!=0 ) ){
1379
+ if( zCheckin!=0 && (zFrom!=0 || zTo!=0) ){
13151380
fossil_fatal("cannot use --checkin together with --from or --to");
13161381
}
1317
- g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0;
13181382
if( 0==zCheckin ){
13191383
if( zTo==0 || againstUndo ){
13201384
db_must_be_within_tree();
13211385
}else if( zFrom==0 ){
13221386
fossil_fatal("must use --from if --to is present");
@@ -1324,13 +1388,23 @@
13241388
db_find_and_open_repository(0, 0);
13251389
}
13261390
}else{
13271391
db_find_and_open_repository(0, 0);
13281392
}
1329
- diff_options(&DCfg, isGDiff, 0);
13301393
determine_exec_relative_option(1);
1394
+ if( zFrom!=file_tail(zFrom)
1395
+ && file_isdir(zFrom, ExtFILE)==1
1396
+ && !db_exists("SELECT 1 FROM tag WHERE tagname='sym-%q'", zFrom)
1397
+ ){
1398
+ bFromIsDir = 1;
1399
+ if( zTo ){
1400
+ fossil_fatal("cannot use --to together with \"--from PATH\"");
1401
+ }
1402
+ }
1403
+ diff_options(&DCfg, isGDiff, 0);
13311404
verify_all_options();
1405
+ g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0;
13321406
if( g.argc>=3 ){
13331407
int i;
13341408
Blob fname;
13351409
pFileDir = fossil_malloc( sizeof(*pFileDir) * (g.argc-1) );
13361410
memset(pFileDir, 0, sizeof(*pFileDir) * (g.argc-1));
@@ -1357,18 +1431,20 @@
13571431
if( zFrom==0 ){
13581432
fossil_fatal("check-in %s has no parent", zTo);
13591433
}
13601434
}
13611435
diff_begin(&DCfg);
1362
- if( againstUndo ){
1436
+ if( bFromIsDir ){
1437
+ diff_externbase_to_checkout(zFrom, &DCfg, pFileDir);
1438
+ }else if( againstUndo ){
13631439
if( db_lget_int("undo_available",0)==0 ){
13641440
fossil_print("No undo or redo is available\n");
13651441
return;
13661442
}
1367
- diff_against_undo(&DCfg, pFileDir);
1443
+ diff_undo_to_checkout(&DCfg, pFileDir);
13681444
}else if( zTo==0 ){
1369
- diff_against_disk(zFrom, &DCfg, pFileDir, 0);
1445
+ diff_version_to_checkout(zFrom, &DCfg, pFileDir, 0);
13701446
}else{
13711447
diff_two_versions(zFrom, zTo, &DCfg, pFileDir);
13721448
}
13731449
if( pFileDir ){
13741450
int i;
13751451
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -249,19 +249,19 @@
249 @ margin: 0 0 0 0;
250 @ line-height: inherit;
251 @ font-size: inherit;
252 @ }
253 @ td.diffln {
254 @ width: 1px;
255 @ text-align: right;
256 @ padding: 0 1em 0 0;
257 @ }
258 @ td.difflne {
259 @ padding-bottom: 0.4em;
260 @ }
261 @ td.diffsep {
262 @ width: 1px;
263 @ padding: 0 0.3em 0 1em;
264 @ line-height: inherit;
265 @ font-size: inherit;
266 @ }
267 @ td.diffsep pre {
@@ -379,19 +379,19 @@
379 @ margin: 0 0 0 0;
380 @ line-height: inherit;
381 @ font-size: inherit;
382 @ }
383 @ td.diffln {
384 @ width: 1px;
385 @ text-align: right;
386 @ padding: 0 1em 0 0;
387 @ }
388 @ td.difflne {
389 @ padding-bottom: 0.4em;
390 @ }
391 @ td.diffsep {
392 @ width: 1px;
393 @ padding: 0 0.3em 0 1em;
394 @ line-height: inherit;
395 @ font-size: inherit;
396 @ }
397 @ td.diffsep pre {
@@ -784,26 +784,27 @@
784 blob_reset(&file);
785 return rc;
786 }
787
788 /*
789 ** Run a diff between the version zFrom and files on disk. zFrom might
790 ** be NULL which means to simply show the difference between the edited
791 ** files on disk and the check-out on which they are based.
 
792 **
793 ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the
794 ** command zDiffCmd to do the diffing.
795 **
796 ** When using an external diff program, zBinGlob contains the GLOB patterns
797 ** for file names to treat as binary. If fIncludeBinary is zero, these files
798 ** will be skipped in addition to files that may contain binary content.
799 */
800 void diff_against_disk(
801 const char *zFrom, /* Version to difference from */
802 DiffConfig *pCfg, /* Flags controlling diff output */
803 FileDirList *pFileDir, /* Which files to diff */
804 Blob *pOut /* Blob to output diff instead of stdout */
805 ){
806 int vid;
807 Blob sql;
808 Stmt q;
809 int asNewFile; /* Treat non-existant files as empty files */
@@ -928,20 +929,20 @@
928 db_finalize(&q);
929 db_end_transaction(1); /* ROLLBACK */
930 }
931
932 /*
933 ** Run a diff between the undo buffer and files on disk.
934 **
935 ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the
936 ** command zDiffCmd to do the diffing.
937 **
938 ** When using an external diff program, zBinGlob contains the GLOB patterns
939 ** for file names to treat as binary. If fIncludeBinary is zero, these files
940 ** will be skipped in addition to files that may contain binary content.
941 */
942 static void diff_against_undo(
943 DiffConfig *pCfg, /* Flags controlling diff output */
944 FileDirList *pFileDir /* List of files and directories to diff */
945 ){
946 Stmt q;
947 Blob content;
@@ -1088,10 +1089,67 @@
1088 }
1089 }
1090 manifest_destroy(pFrom);
1091 manifest_destroy(pTo);
1092 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1093
1094 /*
1095 ** Return the name of the external diff command, or return NULL if
1096 ** no external diff command is defined.
1097 */
@@ -1224,10 +1282,14 @@
1224 ** option specifies the check-in from which the second version of the file
1225 ** or files is taken. If there is no "--to" option then the (possibly edited)
1226 ** files in the current check-out are used. The "--checkin VERSION" option
1227 ** shows the changes made by check-in VERSION relative to its primary parent.
1228 ** The "--branch BRANCHNAME" shows all the changes on the branch BRANCHNAME.
 
 
 
 
1229 **
1230 ** The "-i" command-line option forces the use of Fossil's own internal
1231 ** diff logic rather than any external diff program that might be configured
1232 ** using the "setting" command. If no external diff program is configured,
1233 ** then the "-i" option is a no-op. The "-i" option converts "gdiff" into
@@ -1256,25 +1318,27 @@
1256 ** with negative N meaning show all content
1257 ** --dark Use dark mode for the Tcl/Tk-based GUI and HTML
1258 ** --diff-binary BOOL Include binary files with external commands
1259 ** --exec-abs-paths Force absolute path names on external commands
1260 ** --exec-rel-paths Force relative path names on external commands
1261 ** -r|--from VERSION Select VERSION as source for the diff
 
 
1262 ** -w|--ignore-all-space Ignore white space when comparing lines
1263 ** -i|--internal Use internal diff logic
1264 ** --invert Invert the diff
1265 ** --json Output formatted as JSON
1266 ** -n|--linenum Show line numbers
1267 ** -N|--new-file Alias for --verbose
1268 ** --numstat Show only the number of added and deleted lines
1269 ** -y|--side-by-side Side-by-side diff
1270 ** --strip-trailing-cr Strip trailing CR
1271 ** --tcl Tcl-formated output used internally by --tk
1272 ** --tclsh PATH Tcl/Tk shell used for --tk (default: "tclsh")
1273 ** --tk Launch a Tcl/Tk GUI for display
1274 ** --to VERSION Select VERSION as target for the diff
1275 ** --undo Diff against the "undo" buffer
1276 ** --unified Unified diff
1277 ** -v|--verbose Output complete text of added or deleted files
1278 ** -h|--versions Show compared versions in the diff header
1279 ** --webpage Format output as a stand-alone HTML webpage
1280 ** -W|--width N Width of lines in side-by-side diff
@@ -1287,10 +1351,11 @@
1287 const char *zCheckin; /* Check-in version number */
1288 const char *zBranch; /* Branch to diff */
1289 int againstUndo = 0; /* Diff against files in the undo buffer */
1290 FileDirList *pFileDir = 0; /* Restrict the diff to these files */
1291 DiffConfig DCfg; /* Diff configuration object */
 
1292
1293 if( find_option("tk",0,0)!=0 || has_option("tclsh") ){
1294 diff_tk("diff", 2);
1295 return;
1296 }
@@ -1298,11 +1363,11 @@
1298 zFrom = find_option("from", "r", 1);
1299 zTo = find_option("to", 0, 1);
1300 zCheckin = find_option("checkin", "ci", 1);
1301 zBranch = find_option("branch", 0, 1);
1302 againstUndo = find_option("undo",0,0)!=0;
1303 if( againstUndo && ( zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){
1304 fossil_fatal("cannot use --undo together with --from, --to, --checkin,"
1305 " or --branch");
1306 }
1307 if( zBranch ){
1308 if( zTo || zFrom || zCheckin ){
@@ -1309,14 +1374,13 @@
1309 fossil_fatal("cannot use --from, --to, or --checkin with --branch");
1310 }
1311 zTo = zBranch;
1312 zFrom = mprintf("root:%s", zBranch);
1313 }
1314 if( zCheckin!=0 && ( zFrom!=0 || zTo!=0 ) ){
1315 fossil_fatal("cannot use --checkin together with --from or --to");
1316 }
1317 g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0;
1318 if( 0==zCheckin ){
1319 if( zTo==0 || againstUndo ){
1320 db_must_be_within_tree();
1321 }else if( zFrom==0 ){
1322 fossil_fatal("must use --from if --to is present");
@@ -1324,13 +1388,23 @@
1324 db_find_and_open_repository(0, 0);
1325 }
1326 }else{
1327 db_find_and_open_repository(0, 0);
1328 }
1329 diff_options(&DCfg, isGDiff, 0);
1330 determine_exec_relative_option(1);
 
 
 
 
 
 
 
 
 
 
1331 verify_all_options();
 
1332 if( g.argc>=3 ){
1333 int i;
1334 Blob fname;
1335 pFileDir = fossil_malloc( sizeof(*pFileDir) * (g.argc-1) );
1336 memset(pFileDir, 0, sizeof(*pFileDir) * (g.argc-1));
@@ -1357,18 +1431,20 @@
1357 if( zFrom==0 ){
1358 fossil_fatal("check-in %s has no parent", zTo);
1359 }
1360 }
1361 diff_begin(&DCfg);
1362 if( againstUndo ){
 
 
1363 if( db_lget_int("undo_available",0)==0 ){
1364 fossil_print("No undo or redo is available\n");
1365 return;
1366 }
1367 diff_against_undo(&DCfg, pFileDir);
1368 }else if( zTo==0 ){
1369 diff_against_disk(zFrom, &DCfg, pFileDir, 0);
1370 }else{
1371 diff_two_versions(zFrom, zTo, &DCfg, pFileDir);
1372 }
1373 if( pFileDir ){
1374 int i;
1375
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -249,19 +249,19 @@
249 @ margin: 0 0 0 0;
250 @ line-height: inherit;
251 @ font-size: inherit;
252 @ }
253 @ td.diffln {
254 @ width: fit-content;
255 @ text-align: right;
256 @ padding: 0 1em 0 0;
257 @ }
258 @ td.difflne {
259 @ padding-bottom: 0.4em;
260 @ }
261 @ td.diffsep {
262 @ width: fit-content;
263 @ padding: 0 0.3em 0 1em;
264 @ line-height: inherit;
265 @ font-size: inherit;
266 @ }
267 @ td.diffsep pre {
@@ -379,19 +379,19 @@
379 @ margin: 0 0 0 0;
380 @ line-height: inherit;
381 @ font-size: inherit;
382 @ }
383 @ td.diffln {
384 @ width: fit-content;
385 @ text-align: right;
386 @ padding: 0 1em 0 0;
387 @ }
388 @ td.difflne {
389 @ padding-bottom: 0.4em;
390 @ }
391 @ td.diffsep {
392 @ width: fit-content;
393 @ padding: 0 0.3em 0 1em;
394 @ line-height: inherit;
395 @ font-size: inherit;
396 @ }
397 @ td.diffsep pre {
@@ -784,26 +784,27 @@
784 blob_reset(&file);
785 return rc;
786 }
787
788 /*
789 ** Run a diff between the version zFrom and files on disk in the current
790 ** working checkout. zFrom might be NULL which means to simply show the
791 ** difference between the edited files on disk and the check-out on which
792 ** they are based.
793 **
794 ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the
795 ** command zDiffCmd to do the diffing.
796 **
797 ** When using an external diff program, zBinGlob contains the GLOB patterns
798 ** for file names to treat as binary. If fIncludeBinary is zero, these files
799 ** will be skipped in addition to files that may contain binary content.
800 */
801 void diff_version_to_checkout(
802 const char *zFrom, /* Version to difference from */
803 DiffConfig *pCfg, /* Flags controlling diff output */
804 FileDirList *pFileDir, /* Which files to diff */
805 Blob *pOut /* Blob to output diff instead of stdout */
806 ){
807 int vid;
808 Blob sql;
809 Stmt q;
810 int asNewFile; /* Treat non-existant files as empty files */
@@ -928,20 +929,20 @@
929 db_finalize(&q);
930 db_end_transaction(1); /* ROLLBACK */
931 }
932
933 /*
934 ** Run a diff from the undo buffer to files on disk.
935 **
936 ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the
937 ** command zDiffCmd to do the diffing.
938 **
939 ** When using an external diff program, zBinGlob contains the GLOB patterns
940 ** for file names to treat as binary. If fIncludeBinary is zero, these files
941 ** will be skipped in addition to files that may contain binary content.
942 */
943 static void diff_undo_to_checkout(
944 DiffConfig *pCfg, /* Flags controlling diff output */
945 FileDirList *pFileDir /* List of files and directories to diff */
946 ){
947 Stmt q;
948 Blob content;
@@ -1088,10 +1089,67 @@
1089 }
1090 }
1091 manifest_destroy(pFrom);
1092 manifest_destroy(pTo);
1093 }
1094
1095 /*
1096 ** Compute the difference from an external tree of files to the current
1097 ** working checkout with its edits.
1098 **
1099 ** To put it another way: Every managed file in the current working
1100 ** checkout is compared to the file with same name under zExternBase. The
1101 ** zExternBase files are on the left and the files in the current working
1102 ** directory are on the right.
1103 */
1104 void diff_externbase_to_checkout(
1105 const char *zExternBase, /* Remote tree to use as the baseline */
1106 DiffConfig *pCfg, /* Diff settings */
1107 FileDirList *pFileDir /* Only look at these files */
1108 ){
1109 int vid;
1110 Stmt q;
1111
1112 vid = db_lget_int("checkout",0);
1113 if( file_isdir(zExternBase, ExtFILE)!=1 ){
1114 fossil_fatal("\"%s\" is not a directory", zExternBase);
1115 }
1116 db_prepare(&q,
1117 "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname",
1118 vid
1119 );
1120 while( db_step(&q)==SQLITE_ROW ){
1121 const char *zFile; /* Name of file in the repository */
1122 char *zLhs; /* Full name of left-hand side file */
1123 char *zRhs; /* Full name of right-hand side file */
1124 Blob rhs; /* Full text of RHS */
1125 Blob lhs; /* Full text of LHS */
1126
1127 zFile = db_column_text(&q,0);
1128 if( !file_dir_match(pFileDir, zFile) ) continue;
1129 zLhs = mprintf("%s/%s", zExternBase, zFile);
1130 zRhs = mprintf("%s%s", g.zLocalRoot, zFile);
1131 if( file_size(zLhs, ExtFILE)<0 ){
1132 blob_zero(&lhs);
1133 }else{
1134 blob_read_from_file(&lhs, zLhs, ExtFILE);
1135 }
1136 blob_read_from_file(&rhs, zRhs, ExtFILE);
1137 if( blob_size(&lhs)!=blob_size(&rhs)
1138 || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0
1139 ){
1140 diff_print_index(zFile, pCfg, 0);
1141 diff_file_mem(&lhs, &rhs, zFile, pCfg);
1142 }
1143 blob_reset(&lhs);
1144 blob_reset(&rhs);
1145 fossil_free(zLhs);
1146 fossil_free(zRhs);
1147 }
1148 db_finalize(&q);
1149 }
1150
1151
1152 /*
1153 ** Return the name of the external diff command, or return NULL if
1154 ** no external diff command is defined.
1155 */
@@ -1224,10 +1282,14 @@
1282 ** option specifies the check-in from which the second version of the file
1283 ** or files is taken. If there is no "--to" option then the (possibly edited)
1284 ** files in the current check-out are used. The "--checkin VERSION" option
1285 ** shows the changes made by check-in VERSION relative to its primary parent.
1286 ** The "--branch BRANCHNAME" shows all the changes on the branch BRANCHNAME.
1287 **
1288 ** With the "--from VERSION" option, if VERSION is actually a directory name
1289 ** (not a tag or check-in hash) then the files under that directory are used
1290 ** as the baseline for the diff.
1291 **
1292 ** The "-i" command-line option forces the use of Fossil's own internal
1293 ** diff logic rather than any external diff program that might be configured
1294 ** using the "setting" command. If no external diff program is configured,
1295 ** then the "-i" option is a no-op. The "-i" option converts "gdiff" into
@@ -1256,25 +1318,27 @@
1318 ** with negative N meaning show all content
1319 ** --dark Use dark mode for the Tcl/Tk-based GUI and HTML
1320 ** --diff-binary BOOL Include binary files with external commands
1321 ** --exec-abs-paths Force absolute path names on external commands
1322 ** --exec-rel-paths Force relative path names on external commands
1323 ** -r|--from VERSION Use VERSION as the baseline for the diff, or
1324 ** if VERSION is a directory name, use files in
1325 ** that directory as the baseline.
1326 ** -w|--ignore-all-space Ignore white space when comparing lines
1327 ** -i|--internal Use internal diff logic
1328 ** --invert Invert the diff
1329 ** --json Output formatted as JSON
1330 ** -n|--linenum Show line numbers
1331 ** -N|--new-file Alias for --verbose
1332 ** --numstat Show only the number of added and deleted lines
1333 ** -y|--side-by-side Side-by-side diff
1334 ** --strip-trailing-cr Strip trailing CR
1335 ** --tcl Tcl-formatted output used internally by --tk
1336 ** --tclsh PATH Tcl/Tk shell used for --tk (default: "tclsh")
1337 ** --tk Launch a Tcl/Tk GUI for display
1338 ** --to VERSION Select VERSION as target for the diff
1339 ** --undo Use the undo buffer as the baseline
1340 ** --unified Unified diff
1341 ** -v|--verbose Output complete text of added or deleted files
1342 ** -h|--versions Show compared versions in the diff header
1343 ** --webpage Format output as a stand-alone HTML webpage
1344 ** -W|--width N Width of lines in side-by-side diff
@@ -1287,10 +1351,11 @@
1351 const char *zCheckin; /* Check-in version number */
1352 const char *zBranch; /* Branch to diff */
1353 int againstUndo = 0; /* Diff against files in the undo buffer */
1354 FileDirList *pFileDir = 0; /* Restrict the diff to these files */
1355 DiffConfig DCfg; /* Diff configuration object */
1356 int bFromIsDir = 0; /* True if zFrom is a directory name */
1357
1358 if( find_option("tk",0,0)!=0 || has_option("tclsh") ){
1359 diff_tk("diff", 2);
1360 return;
1361 }
@@ -1298,11 +1363,11 @@
1363 zFrom = find_option("from", "r", 1);
1364 zTo = find_option("to", 0, 1);
1365 zCheckin = find_option("checkin", "ci", 1);
1366 zBranch = find_option("branch", 0, 1);
1367 againstUndo = find_option("undo",0,0)!=0;
1368 if( againstUndo && (zFrom!=0 || zTo!=0 || zCheckin!=0 || zBranch!=0) ){
1369 fossil_fatal("cannot use --undo together with --from, --to, --checkin,"
1370 " or --branch");
1371 }
1372 if( zBranch ){
1373 if( zTo || zFrom || zCheckin ){
@@ -1309,14 +1374,13 @@
1374 fossil_fatal("cannot use --from, --to, or --checkin with --branch");
1375 }
1376 zTo = zBranch;
1377 zFrom = mprintf("root:%s", zBranch);
1378 }
1379 if( zCheckin!=0 && (zFrom!=0 || zTo!=0) ){
1380 fossil_fatal("cannot use --checkin together with --from or --to");
1381 }
 
1382 if( 0==zCheckin ){
1383 if( zTo==0 || againstUndo ){
1384 db_must_be_within_tree();
1385 }else if( zFrom==0 ){
1386 fossil_fatal("must use --from if --to is present");
@@ -1324,13 +1388,23 @@
1388 db_find_and_open_repository(0, 0);
1389 }
1390 }else{
1391 db_find_and_open_repository(0, 0);
1392 }
 
1393 determine_exec_relative_option(1);
1394 if( zFrom!=file_tail(zFrom)
1395 && file_isdir(zFrom, ExtFILE)==1
1396 && !db_exists("SELECT 1 FROM tag WHERE tagname='sym-%q'", zFrom)
1397 ){
1398 bFromIsDir = 1;
1399 if( zTo ){
1400 fossil_fatal("cannot use --to together with \"--from PATH\"");
1401 }
1402 }
1403 diff_options(&DCfg, isGDiff, 0);
1404 verify_all_options();
1405 g.diffCnt[0] = g.diffCnt[1] = g.diffCnt[2] = 0;
1406 if( g.argc>=3 ){
1407 int i;
1408 Blob fname;
1409 pFileDir = fossil_malloc( sizeof(*pFileDir) * (g.argc-1) );
1410 memset(pFileDir, 0, sizeof(*pFileDir) * (g.argc-1));
@@ -1357,18 +1431,20 @@
1431 if( zFrom==0 ){
1432 fossil_fatal("check-in %s has no parent", zTo);
1433 }
1434 }
1435 diff_begin(&DCfg);
1436 if( bFromIsDir ){
1437 diff_externbase_to_checkout(zFrom, &DCfg, pFileDir);
1438 }else if( againstUndo ){
1439 if( db_lget_int("undo_available",0)==0 ){
1440 fossil_print("No undo or redo is available\n");
1441 return;
1442 }
1443 diff_undo_to_checkout(&DCfg, pFileDir);
1444 }else if( zTo==0 ){
1445 diff_version_to_checkout(zFrom, &DCfg, pFileDir, 0);
1446 }else{
1447 diff_two_versions(zFrom, zTo, &DCfg, pFileDir);
1448 }
1449 if( pFileDir ){
1450 int i;
1451
+26 -1
--- src/dispatch.c
+++ src/dispatch.c
@@ -1307,17 +1307,42 @@
13071307
}
13081308
13091309
/*
13101310
** Return a pointer to the setting information array.
13111311
**
1312
-** This routine provides access to the aSetting2[] array which is created
1312
+** This routine provides access to the aSetting[] array which is created
13131313
** by the mkindex utility program and included with <page_index.h>.
13141314
*/
13151315
const Setting *setting_info(int *pnCount){
13161316
if( pnCount ) *pnCount = (int)(sizeof(aSetting)/sizeof(aSetting[0])) - 1;
13171317
return aSetting;
13181318
}
1319
+
1320
+/*
1321
+** Return a pointer to a specific Setting entry for the setting named
1322
+** in the argument. Or return NULL if no such setting exists.
1323
+**
1324
+** The pointer returned points into the middle of the global aSetting[]
1325
+** array that is generated by mkindex. Use setting_info() to fetch the
1326
+** whole array. Use this routine to fetch a specific entry.
1327
+*/
1328
+const Setting *setting_find(const char *zName){
1329
+ int iFirst = 0;
1330
+ int iLast = ArraySize(aSetting)-1;
1331
+ while( iFirst<=iLast ){
1332
+ int iCur = (iFirst+iLast)/2;
1333
+ int c = strcmp(aSetting[iCur].name, zName);
1334
+ if( c<0 ){
1335
+ iFirst = iCur+1;
1336
+ }else if( c>0 ){
1337
+ iLast = iCur-1;
1338
+ }else{
1339
+ return &aSetting[iCur];
1340
+ }
1341
+ }
1342
+ return 0;
1343
+}
13191344
13201345
/*****************************************************************************
13211346
** A virtual table for accessing the information in aCommand[], and
13221347
** especially the help-text
13231348
*/
13241349
--- src/dispatch.c
+++ src/dispatch.c
@@ -1307,17 +1307,42 @@
1307 }
1308
1309 /*
1310 ** Return a pointer to the setting information array.
1311 **
1312 ** This routine provides access to the aSetting2[] array which is created
1313 ** by the mkindex utility program and included with <page_index.h>.
1314 */
1315 const Setting *setting_info(int *pnCount){
1316 if( pnCount ) *pnCount = (int)(sizeof(aSetting)/sizeof(aSetting[0])) - 1;
1317 return aSetting;
1318 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1319
1320 /*****************************************************************************
1321 ** A virtual table for accessing the information in aCommand[], and
1322 ** especially the help-text
1323 */
1324
--- src/dispatch.c
+++ src/dispatch.c
@@ -1307,17 +1307,42 @@
1307 }
1308
1309 /*
1310 ** Return a pointer to the setting information array.
1311 **
1312 ** This routine provides access to the aSetting[] array which is created
1313 ** by the mkindex utility program and included with <page_index.h>.
1314 */
1315 const Setting *setting_info(int *pnCount){
1316 if( pnCount ) *pnCount = (int)(sizeof(aSetting)/sizeof(aSetting[0])) - 1;
1317 return aSetting;
1318 }
1319
1320 /*
1321 ** Return a pointer to a specific Setting entry for the setting named
1322 ** in the argument. Or return NULL if no such setting exists.
1323 **
1324 ** The pointer returned points into the middle of the global aSetting[]
1325 ** array that is generated by mkindex. Use setting_info() to fetch the
1326 ** whole array. Use this routine to fetch a specific entry.
1327 */
1328 const Setting *setting_find(const char *zName){
1329 int iFirst = 0;
1330 int iLast = ArraySize(aSetting)-1;
1331 while( iFirst<=iLast ){
1332 int iCur = (iFirst+iLast)/2;
1333 int c = strcmp(aSetting[iCur].name, zName);
1334 if( c<0 ){
1335 iFirst = iCur+1;
1336 }else if( c>0 ){
1337 iLast = iCur-1;
1338 }else{
1339 return &aSetting[iCur];
1340 }
1341 }
1342 return 0;
1343 }
1344
1345 /*****************************************************************************
1346 ** A virtual table for accessing the information in aCommand[], and
1347 ** especially the help-text
1348 */
1349
+8 -1
--- src/doc.c
+++ src/doc.c
@@ -788,11 +788,11 @@
788788
){
789789
Blob title;
790790
int isPopup = P("popup")!=0;
791791
blob_init(&title,0,0);
792792
if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){
793
- Blob tail;
793
+ Blob tail = BLOB_INITIALIZER;
794794
style_adunit_config(ADUNIT_RIGHT_OK);
795795
if( wiki_find_title(pBody, &title, &tail) ){
796796
if( !isPopup ) style_header("%s", blob_str(&title));
797797
wiki_convert(&tail, 0, WIKI_BUTTONS);
798798
}else{
@@ -801,10 +801,11 @@
801801
}
802802
if( !isPopup ){
803803
document_emit_js();
804804
style_finish_page();
805805
}
806
+ blob_reset(&tail);
806807
}else if( fossil_strcmp(zMime, "text/x-markdown")==0 ){
807808
Blob tail = BLOB_INITIALIZER;
808809
markdown_to_html(pBody, &title, &tail);
809810
if( !isPopup ){
810811
if( blob_size(&title)>0 ){
@@ -816,10 +817,11 @@
816817
convert_href_and_output(&tail);
817818
if( !isPopup ){
818819
document_emit_js();
819820
style_finish_page();
820821
}
822
+ blob_reset(&tail);
821823
}else if( fossil_strcmp(zMime, "text/plain")==0 ){
822824
style_header("%s", zDefaultTitle);
823825
@ <blockquote><pre>
824826
@ %h(blob_str(pBody))
825827
@ </pre></blockquote>
@@ -949,10 +951,11 @@
949951
950952
login_check_credentials();
951953
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
952954
style_set_current_feature("doc");
953955
blob_init(&title, 0, 0);
956
+ blob_init(&filebody, 0, 0);
954957
zDfltTitle = isUV ? "" : "Documentation";
955958
db_begin_transaction();
956959
while( rid==0 && (++nMiss)<=count(azSuffix) ){
957960
zName = P("name");
958961
if( isUV ){
@@ -1059,10 +1062,12 @@
10591062
}
10601063
cgi_check_for_malice();
10611064
document_render(&filebody, zMime, zDfltTitle, zName);
10621065
if( nMiss>=count(azSuffix) ) cgi_set_status(404, "Not Found");
10631066
db_end_transaction(0);
1067
+ blob_reset(&title);
1068
+ blob_reset(&filebody);
10641069
return;
10651070
10661071
/* Jump here when unable to locate the document */
10671072
doc_not_found:
10681073
db_end_transaction(0);
@@ -1075,10 +1080,12 @@
10751080
@ <p>Document %h(zOrigName) not found
10761081
if( fossil_strcmp(zCheckin,"ckout")!=0 ){
10771082
@ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a>
10781083
}
10791084
style_finish_page();
1085
+ blob_reset(&title);
1086
+ blob_reset(&filebody);
10801087
return;
10811088
}
10821089
10831090
/*
10841091
** The default logo.
10851092
--- src/doc.c
+++ src/doc.c
@@ -788,11 +788,11 @@
788 ){
789 Blob title;
790 int isPopup = P("popup")!=0;
791 blob_init(&title,0,0);
792 if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){
793 Blob tail;
794 style_adunit_config(ADUNIT_RIGHT_OK);
795 if( wiki_find_title(pBody, &title, &tail) ){
796 if( !isPopup ) style_header("%s", blob_str(&title));
797 wiki_convert(&tail, 0, WIKI_BUTTONS);
798 }else{
@@ -801,10 +801,11 @@
801 }
802 if( !isPopup ){
803 document_emit_js();
804 style_finish_page();
805 }
 
806 }else if( fossil_strcmp(zMime, "text/x-markdown")==0 ){
807 Blob tail = BLOB_INITIALIZER;
808 markdown_to_html(pBody, &title, &tail);
809 if( !isPopup ){
810 if( blob_size(&title)>0 ){
@@ -816,10 +817,11 @@
816 convert_href_and_output(&tail);
817 if( !isPopup ){
818 document_emit_js();
819 style_finish_page();
820 }
 
821 }else if( fossil_strcmp(zMime, "text/plain")==0 ){
822 style_header("%s", zDefaultTitle);
823 @ <blockquote><pre>
824 @ %h(blob_str(pBody))
825 @ </pre></blockquote>
@@ -949,10 +951,11 @@
949
950 login_check_credentials();
951 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
952 style_set_current_feature("doc");
953 blob_init(&title, 0, 0);
 
954 zDfltTitle = isUV ? "" : "Documentation";
955 db_begin_transaction();
956 while( rid==0 && (++nMiss)<=count(azSuffix) ){
957 zName = P("name");
958 if( isUV ){
@@ -1059,10 +1062,12 @@
1059 }
1060 cgi_check_for_malice();
1061 document_render(&filebody, zMime, zDfltTitle, zName);
1062 if( nMiss>=count(azSuffix) ) cgi_set_status(404, "Not Found");
1063 db_end_transaction(0);
 
 
1064 return;
1065
1066 /* Jump here when unable to locate the document */
1067 doc_not_found:
1068 db_end_transaction(0);
@@ -1075,10 +1080,12 @@
1075 @ <p>Document %h(zOrigName) not found
1076 if( fossil_strcmp(zCheckin,"ckout")!=0 ){
1077 @ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a>
1078 }
1079 style_finish_page();
 
 
1080 return;
1081 }
1082
1083 /*
1084 ** The default logo.
1085
--- src/doc.c
+++ src/doc.c
@@ -788,11 +788,11 @@
788 ){
789 Blob title;
790 int isPopup = P("popup")!=0;
791 blob_init(&title,0,0);
792 if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){
793 Blob tail = BLOB_INITIALIZER;
794 style_adunit_config(ADUNIT_RIGHT_OK);
795 if( wiki_find_title(pBody, &title, &tail) ){
796 if( !isPopup ) style_header("%s", blob_str(&title));
797 wiki_convert(&tail, 0, WIKI_BUTTONS);
798 }else{
@@ -801,10 +801,11 @@
801 }
802 if( !isPopup ){
803 document_emit_js();
804 style_finish_page();
805 }
806 blob_reset(&tail);
807 }else if( fossil_strcmp(zMime, "text/x-markdown")==0 ){
808 Blob tail = BLOB_INITIALIZER;
809 markdown_to_html(pBody, &title, &tail);
810 if( !isPopup ){
811 if( blob_size(&title)>0 ){
@@ -816,10 +817,11 @@
817 convert_href_and_output(&tail);
818 if( !isPopup ){
819 document_emit_js();
820 style_finish_page();
821 }
822 blob_reset(&tail);
823 }else if( fossil_strcmp(zMime, "text/plain")==0 ){
824 style_header("%s", zDefaultTitle);
825 @ <blockquote><pre>
826 @ %h(blob_str(pBody))
827 @ </pre></blockquote>
@@ -949,10 +951,11 @@
951
952 login_check_credentials();
953 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
954 style_set_current_feature("doc");
955 blob_init(&title, 0, 0);
956 blob_init(&filebody, 0, 0);
957 zDfltTitle = isUV ? "" : "Documentation";
958 db_begin_transaction();
959 while( rid==0 && (++nMiss)<=count(azSuffix) ){
960 zName = P("name");
961 if( isUV ){
@@ -1059,10 +1062,12 @@
1062 }
1063 cgi_check_for_malice();
1064 document_render(&filebody, zMime, zDfltTitle, zName);
1065 if( nMiss>=count(azSuffix) ) cgi_set_status(404, "Not Found");
1066 db_end_transaction(0);
1067 blob_reset(&title);
1068 blob_reset(&filebody);
1069 return;
1070
1071 /* Jump here when unable to locate the document */
1072 doc_not_found:
1073 db_end_transaction(0);
@@ -1075,10 +1080,12 @@
1080 @ <p>Document %h(zOrigName) not found
1081 if( fossil_strcmp(zCheckin,"ckout")!=0 ){
1082 @ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a>
1083 }
1084 style_finish_page();
1085 blob_reset(&title);
1086 blob_reset(&filebody);
1087 return;
1088 }
1089
1090 /*
1091 ** The default logo.
1092
+18
--- src/encode.c
+++ src/encode.c
@@ -729,10 +729,28 @@
729729
while( *z && n-- ){
730730
*z = zEncode[zDecode[(*z)&0x7f]&0x1f];
731731
z++;
732732
}
733733
}
734
+
735
+/*
736
+** Decode hexadecimal into a string and return the new string. Space to
737
+** hold the string is obtained from fossil_malloc() and should be released
738
+** by the caller.
739
+**
740
+** If the input is not hex, return NULL.
741
+*/
742
+char *decode16_dup(const char *zIn){
743
+ int nIn = (int)strlen(zIn);
744
+ char *zOut;
745
+ if( !validate16(zIn, nIn) ) return 0;
746
+ zOut = fossil_malloc(nIn/2+1);
747
+ decode16((const u8*)zIn, (u8*)zOut, nIn);
748
+ zOut[nIn/2] = 0;
749
+ return zOut;
750
+}
751
+
734752
735753
/*
736754
** Decode a string encoded using "quoted-printable".
737755
**
738756
** (1) "=" followed by two hex digits becomes a single
739757
--- src/encode.c
+++ src/encode.c
@@ -729,10 +729,28 @@
729 while( *z && n-- ){
730 *z = zEncode[zDecode[(*z)&0x7f]&0x1f];
731 z++;
732 }
733 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
734
735 /*
736 ** Decode a string encoded using "quoted-printable".
737 **
738 ** (1) "=" followed by two hex digits becomes a single
739
--- src/encode.c
+++ src/encode.c
@@ -729,10 +729,28 @@
729 while( *z && n-- ){
730 *z = zEncode[zDecode[(*z)&0x7f]&0x1f];
731 z++;
732 }
733 }
734
735 /*
736 ** Decode hexadecimal into a string and return the new string. Space to
737 ** hold the string is obtained from fossil_malloc() and should be released
738 ** by the caller.
739 **
740 ** If the input is not hex, return NULL.
741 */
742 char *decode16_dup(const char *zIn){
743 int nIn = (int)strlen(zIn);
744 char *zOut;
745 if( !validate16(zIn, nIn) ) return 0;
746 zOut = fossil_malloc(nIn/2+1);
747 decode16((const u8*)zIn, (u8*)zOut, nIn);
748 zOut[nIn/2] = 0;
749 return zOut;
750 }
751
752
753 /*
754 ** Decode a string encoded using "quoted-printable".
755 **
756 ** (1) "=" followed by two hex digits becomes a single
757
+1 -1
--- src/event.c
+++ src/event.c
@@ -229,11 +229,11 @@
229229
}
230230
zFullId = db_text(0, "SELECT SUBSTR(tagname,7)"
231231
" FROM tag"
232232
" WHERE tagname GLOB 'event-%q*'",
233233
zId);
234
- attachment_list(zFullId, "<hr><h2>Attachments:</h2><ul>");
234
+ attachment_list(zFullId, "<h2>Attachments:</h2>", 1);
235235
document_emit_js();
236236
style_finish_page();
237237
manifest_destroy(pTNote);
238238
}
239239
240240
--- src/event.c
+++ src/event.c
@@ -229,11 +229,11 @@
229 }
230 zFullId = db_text(0, "SELECT SUBSTR(tagname,7)"
231 " FROM tag"
232 " WHERE tagname GLOB 'event-%q*'",
233 zId);
234 attachment_list(zFullId, "<hr><h2>Attachments:</h2><ul>");
235 document_emit_js();
236 style_finish_page();
237 manifest_destroy(pTNote);
238 }
239
240
--- src/event.c
+++ src/event.c
@@ -229,11 +229,11 @@
229 }
230 zFullId = db_text(0, "SELECT SUBSTR(tagname,7)"
231 " FROM tag"
232 " WHERE tagname GLOB 'event-%q*'",
233 zId);
234 attachment_list(zFullId, "<h2>Attachments:</h2>", 1);
235 document_emit_js();
236 style_finish_page();
237 manifest_destroy(pTNote);
238 }
239
240
+94 -8
--- src/file.c
+++ src/file.c
@@ -1337,25 +1337,37 @@
13371337
}
13381338
}
13391339
13401340
/*
13411341
** Compute a canonical pathname for a file or directory.
1342
-** Make the name absolute if it is relative.
1343
-** Remove redundant / characters
1344
-** Remove all /./ path elements.
1345
-** Convert /A/../ to just /
1342
+**
1343
+** * Make the name absolute if it is relative.
1344
+** * Remove redundant / characters
1345
+** * Remove all /./ path elements.
1346
+** * Convert /A/../ to just /
1347
+** * On windows, add the drive letter prefix.
1348
+**
13461349
** If the slash parameter is non-zero, the trailing slash, if any,
13471350
** is retained.
13481351
**
13491352
** See also: file_canonical_name_dup()
13501353
*/
13511354
void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){
1355
+ char zPwd[2000];
13521356
blob_zero(pOut);
13531357
if( file_is_absolute_path(zOrigName) ){
1354
- blob_appendf(pOut, "%/", zOrigName);
1358
+#if defined(_WIN32)
1359
+ if( fossil_isdirsep(zOrigName[0]) ){
1360
+ /* Add the drive letter to the full pathname */
1361
+ file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName));
1362
+ blob_appendf(pOut, "%.2s%/", zPwd, zOrigName);
1363
+ }else
1364
+#endif
1365
+ {
1366
+ blob_appendf(pOut, "%/", zOrigName);
1367
+ }
13551368
}else{
1356
- char zPwd[2000];
13571369
file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName));
13581370
if( zPwd[0]=='/' && strlen(zPwd)==1 ){
13591371
/* when on '/', don't add an extra '/' */
13601372
if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){
13611373
/* '.' when on '/' mean '/' */
@@ -1679,10 +1691,13 @@
16791691
g.allowSymlinks = !is_false(zAllow);
16801692
}
16811693
if( zRoot==0 ) zRoot = g.zLocalRoot==0 ? "" : g.zLocalRoot;
16821694
fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks());
16831695
fossil_print("local-root = [%s]\n", zRoot);
1696
+ if( g.db==0 ) sqlite3_open(":memory:", &g.db);
1697
+ sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
1698
+ file_inode_sql_func, 0, 0);
16841699
for(i=2; i<g.argc; i++){
16851700
char *z;
16861701
emitFileStat(g.argv[i], slashFlag, resetFlag);
16871702
z = file_canonical_name_dup(g.argv[i]);
16881703
fossil_print(" file_canonical_name = %s\n", z);
@@ -1692,10 +1707,13 @@
16921707
}else{
16931708
int n = file_nondir_objects_on_path(zRoot, z);
16941709
fossil_print("%.*s\n", n, z);
16951710
}
16961711
fossil_free(z);
1712
+ z = db_text(0, "SELECT inode(%Q)", g.argv[i]);
1713
+ fossil_print(" file_inode_sql_func = \"%s\"\n", z);
1714
+ fossil_free(z);
16971715
}
16981716
}
16991717
17001718
/*
17011719
** COMMAND: test-canonical-name
@@ -1734,13 +1752,15 @@
17341752
** Canonical names are full pathnames using "/" not "\" and which
17351753
** contain no "/./" or "/../" terms.
17361754
*/
17371755
int file_is_canonical(const char *z){
17381756
int i;
1739
- if( z[0]!='/'
1757
+ if(
17401758
#if defined(_WIN32) || defined(__CYGWIN__)
1741
- && (!fossil_isupper(z[0]) || z[1]!=':' || z[2]!='/')
1759
+ !fossil_isupper(z[0]) || z[1]!=':' || !fossil_isdirsep(z[2])
1760
+#else
1761
+ z[0]!='/'
17421762
#endif
17431763
) return 0;
17441764
17451765
for(i=0; z[i]; i++){
17461766
if( z[i]=='\\' ) return 0;
@@ -2988,18 +3008,84 @@
29883008
** Returns 1 if the given directory contains a file named .fslckout, 2
29893009
** if it contains a file named _FOSSIL_, else returns 0.
29903010
*/
29913011
int dir_has_ckout_db(const char *zDir){
29923012
int rc = 0;
3013
+ i64 sz;
29933014
char * zCkoutDb = mprintf("%//.fslckout", zDir);
29943015
if(file_isfile(zCkoutDb, ExtFILE)){
29953016
rc = 1;
29963017
}else{
29973018
fossil_free(zCkoutDb);
29983019
zCkoutDb = mprintf("%//_FOSSIL_", zDir);
29993020
if(file_isfile(zCkoutDb, ExtFILE)){
30003021
rc = 2;
30013022
}
3023
+ }
3024
+ if( rc && ((sz = file_size(zCkoutDb, ExtFILE))<1024 || (sz%512)!=0) ){
3025
+ rc = 0;
30023026
}
30033027
fossil_free(zCkoutDb);
30043028
return rc;
30053029
}
3030
+
3031
+/*
3032
+** This is the implementation of inode(FILENAME) SQL function.
3033
+**
3034
+** dev_inode(FILENAME) returns a string. If FILENAME exists and is
3035
+** a regular file, then the return string is of the form:
3036
+**
3037
+** DEV/INODE
3038
+**
3039
+** Where DEV and INODE are the device number and inode number for
3040
+** the file. On Windows, the volume serial number (DEV) and file
3041
+** identifier (INODE) are used to compute the value, see comments
3042
+** on the win32_file_id() function.
3043
+**
3044
+** If FILENAME does not exist, then the return is an empty string.
3045
+**
3046
+** The value of inode() can be used to eliminate files from a list
3047
+** that have duplicates because they have differing names due to links.
3048
+**
3049
+** Code that wants to use this SQL function needs to first register
3050
+** it using a call such as the following:
3051
+**
3052
+** sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
3053
+** file_inode_sql_func, 0, 0);
3054
+*/
3055
+void file_inode_sql_func(
3056
+ sqlite3_context *context,
3057
+ int argc,
3058
+ sqlite3_value **argv
3059
+){
3060
+ const char *zFilename;
3061
+ assert( argc==1 );
3062
+ zFilename = (const char*)sqlite3_value_text(argv[0]);
3063
+ if( zFilename==0 || zFilename[0]==0 || file_access(zFilename,F_OK) ){
3064
+ sqlite3_result_text(context, "", 0, SQLITE_STATIC);
3065
+ return;
3066
+ }
3067
+#if defined(_WIN32)
3068
+ {
3069
+ char *zFileId = win32_file_id(zFilename);
3070
+ if( zFileId ){
3071
+ sqlite3_result_text(context, zFileId, -1, fossil_free);
3072
+ }else{
3073
+ sqlite3_result_text(context, "", 0, SQLITE_STATIC);
3074
+ }
3075
+ }
3076
+#else
3077
+ {
3078
+ struct stat buf;
3079
+ int rc;
3080
+ memset(&buf, 0, sizeof(buf));
3081
+ rc = stat(zFilename, &buf);
3082
+ if( rc ){
3083
+ sqlite3_result_text(context, "", 0, SQLITE_STATIC);
3084
+ }else{
3085
+ sqlite3_result_text(context,
3086
+ mprintf("%lld/%lld", (i64)buf.st_dev, (i64)buf.st_ino), -1,
3087
+ fossil_free);
3088
+ }
3089
+ }
3090
+#endif
3091
+}
30063092
--- src/file.c
+++ src/file.c
@@ -1337,25 +1337,37 @@
1337 }
1338 }
1339
1340 /*
1341 ** Compute a canonical pathname for a file or directory.
1342 ** Make the name absolute if it is relative.
1343 ** Remove redundant / characters
1344 ** Remove all /./ path elements.
1345 ** Convert /A/../ to just /
 
 
 
1346 ** If the slash parameter is non-zero, the trailing slash, if any,
1347 ** is retained.
1348 **
1349 ** See also: file_canonical_name_dup()
1350 */
1351 void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){
 
1352 blob_zero(pOut);
1353 if( file_is_absolute_path(zOrigName) ){
1354 blob_appendf(pOut, "%/", zOrigName);
 
 
 
 
 
 
 
 
 
1355 }else{
1356 char zPwd[2000];
1357 file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName));
1358 if( zPwd[0]=='/' && strlen(zPwd)==1 ){
1359 /* when on '/', don't add an extra '/' */
1360 if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){
1361 /* '.' when on '/' mean '/' */
@@ -1679,10 +1691,13 @@
1679 g.allowSymlinks = !is_false(zAllow);
1680 }
1681 if( zRoot==0 ) zRoot = g.zLocalRoot==0 ? "" : g.zLocalRoot;
1682 fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks());
1683 fossil_print("local-root = [%s]\n", zRoot);
 
 
 
1684 for(i=2; i<g.argc; i++){
1685 char *z;
1686 emitFileStat(g.argv[i], slashFlag, resetFlag);
1687 z = file_canonical_name_dup(g.argv[i]);
1688 fossil_print(" file_canonical_name = %s\n", z);
@@ -1692,10 +1707,13 @@
1692 }else{
1693 int n = file_nondir_objects_on_path(zRoot, z);
1694 fossil_print("%.*s\n", n, z);
1695 }
1696 fossil_free(z);
 
 
 
1697 }
1698 }
1699
1700 /*
1701 ** COMMAND: test-canonical-name
@@ -1734,13 +1752,15 @@
1734 ** Canonical names are full pathnames using "/" not "\" and which
1735 ** contain no "/./" or "/../" terms.
1736 */
1737 int file_is_canonical(const char *z){
1738 int i;
1739 if( z[0]!='/'
1740 #if defined(_WIN32) || defined(__CYGWIN__)
1741 && (!fossil_isupper(z[0]) || z[1]!=':' || z[2]!='/')
 
 
1742 #endif
1743 ) return 0;
1744
1745 for(i=0; z[i]; i++){
1746 if( z[i]=='\\' ) return 0;
@@ -2988,18 +3008,84 @@
2988 ** Returns 1 if the given directory contains a file named .fslckout, 2
2989 ** if it contains a file named _FOSSIL_, else returns 0.
2990 */
2991 int dir_has_ckout_db(const char *zDir){
2992 int rc = 0;
 
2993 char * zCkoutDb = mprintf("%//.fslckout", zDir);
2994 if(file_isfile(zCkoutDb, ExtFILE)){
2995 rc = 1;
2996 }else{
2997 fossil_free(zCkoutDb);
2998 zCkoutDb = mprintf("%//_FOSSIL_", zDir);
2999 if(file_isfile(zCkoutDb, ExtFILE)){
3000 rc = 2;
3001 }
 
 
 
3002 }
3003 fossil_free(zCkoutDb);
3004 return rc;
3005 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3006
--- src/file.c
+++ src/file.c
@@ -1337,25 +1337,37 @@
1337 }
1338 }
1339
1340 /*
1341 ** Compute a canonical pathname for a file or directory.
1342 **
1343 ** * Make the name absolute if it is relative.
1344 ** * Remove redundant / characters
1345 ** * Remove all /./ path elements.
1346 ** * Convert /A/../ to just /
1347 ** * On windows, add the drive letter prefix.
1348 **
1349 ** If the slash parameter is non-zero, the trailing slash, if any,
1350 ** is retained.
1351 **
1352 ** See also: file_canonical_name_dup()
1353 */
1354 void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){
1355 char zPwd[2000];
1356 blob_zero(pOut);
1357 if( file_is_absolute_path(zOrigName) ){
1358 #if defined(_WIN32)
1359 if( fossil_isdirsep(zOrigName[0]) ){
1360 /* Add the drive letter to the full pathname */
1361 file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName));
1362 blob_appendf(pOut, "%.2s%/", zPwd, zOrigName);
1363 }else
1364 #endif
1365 {
1366 blob_appendf(pOut, "%/", zOrigName);
1367 }
1368 }else{
 
1369 file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName));
1370 if( zPwd[0]=='/' && strlen(zPwd)==1 ){
1371 /* when on '/', don't add an extra '/' */
1372 if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){
1373 /* '.' when on '/' mean '/' */
@@ -1679,10 +1691,13 @@
1691 g.allowSymlinks = !is_false(zAllow);
1692 }
1693 if( zRoot==0 ) zRoot = g.zLocalRoot==0 ? "" : g.zLocalRoot;
1694 fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks());
1695 fossil_print("local-root = [%s]\n", zRoot);
1696 if( g.db==0 ) sqlite3_open(":memory:", &g.db);
1697 sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
1698 file_inode_sql_func, 0, 0);
1699 for(i=2; i<g.argc; i++){
1700 char *z;
1701 emitFileStat(g.argv[i], slashFlag, resetFlag);
1702 z = file_canonical_name_dup(g.argv[i]);
1703 fossil_print(" file_canonical_name = %s\n", z);
@@ -1692,10 +1707,13 @@
1707 }else{
1708 int n = file_nondir_objects_on_path(zRoot, z);
1709 fossil_print("%.*s\n", n, z);
1710 }
1711 fossil_free(z);
1712 z = db_text(0, "SELECT inode(%Q)", g.argv[i]);
1713 fossil_print(" file_inode_sql_func = \"%s\"\n", z);
1714 fossil_free(z);
1715 }
1716 }
1717
1718 /*
1719 ** COMMAND: test-canonical-name
@@ -1734,13 +1752,15 @@
1752 ** Canonical names are full pathnames using "/" not "\" and which
1753 ** contain no "/./" or "/../" terms.
1754 */
1755 int file_is_canonical(const char *z){
1756 int i;
1757 if(
1758 #if defined(_WIN32) || defined(__CYGWIN__)
1759 !fossil_isupper(z[0]) || z[1]!=':' || !fossil_isdirsep(z[2])
1760 #else
1761 z[0]!='/'
1762 #endif
1763 ) return 0;
1764
1765 for(i=0; z[i]; i++){
1766 if( z[i]=='\\' ) return 0;
@@ -2988,18 +3008,84 @@
3008 ** Returns 1 if the given directory contains a file named .fslckout, 2
3009 ** if it contains a file named _FOSSIL_, else returns 0.
3010 */
3011 int dir_has_ckout_db(const char *zDir){
3012 int rc = 0;
3013 i64 sz;
3014 char * zCkoutDb = mprintf("%//.fslckout", zDir);
3015 if(file_isfile(zCkoutDb, ExtFILE)){
3016 rc = 1;
3017 }else{
3018 fossil_free(zCkoutDb);
3019 zCkoutDb = mprintf("%//_FOSSIL_", zDir);
3020 if(file_isfile(zCkoutDb, ExtFILE)){
3021 rc = 2;
3022 }
3023 }
3024 if( rc && ((sz = file_size(zCkoutDb, ExtFILE))<1024 || (sz%512)!=0) ){
3025 rc = 0;
3026 }
3027 fossil_free(zCkoutDb);
3028 return rc;
3029 }
3030
3031 /*
3032 ** This is the implementation of inode(FILENAME) SQL function.
3033 **
3034 ** dev_inode(FILENAME) returns a string. If FILENAME exists and is
3035 ** a regular file, then the return string is of the form:
3036 **
3037 ** DEV/INODE
3038 **
3039 ** Where DEV and INODE are the device number and inode number for
3040 ** the file. On Windows, the volume serial number (DEV) and file
3041 ** identifier (INODE) are used to compute the value, see comments
3042 ** on the win32_file_id() function.
3043 **
3044 ** If FILENAME does not exist, then the return is an empty string.
3045 **
3046 ** The value of inode() can be used to eliminate files from a list
3047 ** that have duplicates because they have differing names due to links.
3048 **
3049 ** Code that wants to use this SQL function needs to first register
3050 ** it using a call such as the following:
3051 **
3052 ** sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
3053 ** file_inode_sql_func, 0, 0);
3054 */
3055 void file_inode_sql_func(
3056 sqlite3_context *context,
3057 int argc,
3058 sqlite3_value **argv
3059 ){
3060 const char *zFilename;
3061 assert( argc==1 );
3062 zFilename = (const char*)sqlite3_value_text(argv[0]);
3063 if( zFilename==0 || zFilename[0]==0 || file_access(zFilename,F_OK) ){
3064 sqlite3_result_text(context, "", 0, SQLITE_STATIC);
3065 return;
3066 }
3067 #if defined(_WIN32)
3068 {
3069 char *zFileId = win32_file_id(zFilename);
3070 if( zFileId ){
3071 sqlite3_result_text(context, zFileId, -1, fossil_free);
3072 }else{
3073 sqlite3_result_text(context, "", 0, SQLITE_STATIC);
3074 }
3075 }
3076 #else
3077 {
3078 struct stat buf;
3079 int rc;
3080 memset(&buf, 0, sizeof(buf));
3081 rc = stat(zFilename, &buf);
3082 if( rc ){
3083 sqlite3_result_text(context, "", 0, SQLITE_STATIC);
3084 }else{
3085 sqlite3_result_text(context,
3086 mprintf("%lld/%lld", (i64)buf.st_dev, (i64)buf.st_ino), -1,
3087 fossil_free);
3088 }
3089 }
3090 #endif
3091 }
3092
+94 -8
--- src/file.c
+++ src/file.c
@@ -1337,25 +1337,37 @@
13371337
}
13381338
}
13391339
13401340
/*
13411341
** Compute a canonical pathname for a file or directory.
1342
-** Make the name absolute if it is relative.
1343
-** Remove redundant / characters
1344
-** Remove all /./ path elements.
1345
-** Convert /A/../ to just /
1342
+**
1343
+** * Make the name absolute if it is relative.
1344
+** * Remove redundant / characters
1345
+** * Remove all /./ path elements.
1346
+** * Convert /A/../ to just /
1347
+** * On windows, add the drive letter prefix.
1348
+**
13461349
** If the slash parameter is non-zero, the trailing slash, if any,
13471350
** is retained.
13481351
**
13491352
** See also: file_canonical_name_dup()
13501353
*/
13511354
void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){
1355
+ char zPwd[2000];
13521356
blob_zero(pOut);
13531357
if( file_is_absolute_path(zOrigName) ){
1354
- blob_appendf(pOut, "%/", zOrigName);
1358
+#if defined(_WIN32)
1359
+ if( fossil_isdirsep(zOrigName[0]) ){
1360
+ /* Add the drive letter to the full pathname */
1361
+ file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName));
1362
+ blob_appendf(pOut, "%.2s%/", zPwd, zOrigName);
1363
+ }else
1364
+#endif
1365
+ {
1366
+ blob_appendf(pOut, "%/", zOrigName);
1367
+ }
13551368
}else{
1356
- char zPwd[2000];
13571369
file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName));
13581370
if( zPwd[0]=='/' && strlen(zPwd)==1 ){
13591371
/* when on '/', don't add an extra '/' */
13601372
if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){
13611373
/* '.' when on '/' mean '/' */
@@ -1679,10 +1691,13 @@
16791691
g.allowSymlinks = !is_false(zAllow);
16801692
}
16811693
if( zRoot==0 ) zRoot = g.zLocalRoot==0 ? "" : g.zLocalRoot;
16821694
fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks());
16831695
fossil_print("local-root = [%s]\n", zRoot);
1696
+ if( g.db==0 ) sqlite3_open(":memory:", &g.db);
1697
+ sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
1698
+ file_inode_sql_func, 0, 0);
16841699
for(i=2; i<g.argc; i++){
16851700
char *z;
16861701
emitFileStat(g.argv[i], slashFlag, resetFlag);
16871702
z = file_canonical_name_dup(g.argv[i]);
16881703
fossil_print(" file_canonical_name = %s\n", z);
@@ -1692,10 +1707,13 @@
16921707
}else{
16931708
int n = file_nondir_objects_on_path(zRoot, z);
16941709
fossil_print("%.*s\n", n, z);
16951710
}
16961711
fossil_free(z);
1712
+ z = db_text(0, "SELECT inode(%Q)", g.argv[i]);
1713
+ fossil_print(" file_inode_sql_func = \"%s\"\n", z);
1714
+ fossil_free(z);
16971715
}
16981716
}
16991717
17001718
/*
17011719
** COMMAND: test-canonical-name
@@ -1734,13 +1752,15 @@
17341752
** Canonical names are full pathnames using "/" not "\" and which
17351753
** contain no "/./" or "/../" terms.
17361754
*/
17371755
int file_is_canonical(const char *z){
17381756
int i;
1739
- if( z[0]!='/'
1757
+ if(
17401758
#if defined(_WIN32) || defined(__CYGWIN__)
1741
- && (!fossil_isupper(z[0]) || z[1]!=':' || z[2]!='/')
1759
+ !fossil_isupper(z[0]) || z[1]!=':' || !fossil_isdirsep(z[2])
1760
+#else
1761
+ z[0]!='/'
17421762
#endif
17431763
) return 0;
17441764
17451765
for(i=0; z[i]; i++){
17461766
if( z[i]=='\\' ) return 0;
@@ -2988,18 +3008,84 @@
29883008
** Returns 1 if the given directory contains a file named .fslckout, 2
29893009
** if it contains a file named _FOSSIL_, else returns 0.
29903010
*/
29913011
int dir_has_ckout_db(const char *zDir){
29923012
int rc = 0;
3013
+ i64 sz;
29933014
char * zCkoutDb = mprintf("%//.fslckout", zDir);
29943015
if(file_isfile(zCkoutDb, ExtFILE)){
29953016
rc = 1;
29963017
}else{
29973018
fossil_free(zCkoutDb);
29983019
zCkoutDb = mprintf("%//_FOSSIL_", zDir);
29993020
if(file_isfile(zCkoutDb, ExtFILE)){
30003021
rc = 2;
30013022
}
3023
+ }
3024
+ if( rc && ((sz = file_size(zCkoutDb, ExtFILE))<1024 || (sz%512)!=0) ){
3025
+ rc = 0;
30023026
}
30033027
fossil_free(zCkoutDb);
30043028
return rc;
30053029
}
3030
+
3031
+/*
3032
+** This is the implementation of inode(FILENAME) SQL function.
3033
+**
3034
+** dev_inode(FILENAME) returns a string. If FILENAME exists and is
3035
+** a regular file, then the return string is of the form:
3036
+**
3037
+** DEV/INODE
3038
+**
3039
+** Where DEV and INODE are the device number and inode number for
3040
+** the file. On Windows, the volume serial number (DEV) and file
3041
+** identifier (INODE) are used to compute the value, see comments
3042
+** on the win32_file_id() function.
3043
+**
3044
+** If FILENAME does not exist, then the return is an empty string.
3045
+**
3046
+** The value of inode() can be used to eliminate files from a list
3047
+** that have duplicates because they have differing names due to links.
3048
+**
3049
+** Code that wants to use this SQL function needs to first register
3050
+** it using a call such as the following:
3051
+**
3052
+** sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
3053
+** file_inode_sql_func, 0, 0);
3054
+*/
3055
+void file_inode_sql_func(
3056
+ sqlite3_context *context,
3057
+ int argc,
3058
+ sqlite3_value **argv
3059
+){
3060
+ const char *zFilename;
3061
+ assert( argc==1 );
3062
+ zFilename = (const char*)sqlite3_value_text(argv[0]);
3063
+ if( zFilename==0 || zFilename[0]==0 || file_access(zFilename,F_OK) ){
3064
+ sqlite3_result_text(context, "", 0, SQLITE_STATIC);
3065
+ return;
3066
+ }
3067
+#if defined(_WIN32)
3068
+ {
3069
+ char *zFileId = win32_file_id(zFilename);
3070
+ if( zFileId ){
3071
+ sqlite3_result_text(context, zFileId, -1, fossil_free);
3072
+ }else{
3073
+ sqlite3_result_text(context, "", 0, SQLITE_STATIC);
3074
+ }
3075
+ }
3076
+#else
3077
+ {
3078
+ struct stat buf;
3079
+ int rc;
3080
+ memset(&buf, 0, sizeof(buf));
3081
+ rc = stat(zFilename, &buf);
3082
+ if( rc ){
3083
+ sqlite3_result_text(context, "", 0, SQLITE_STATIC);
3084
+ }else{
3085
+ sqlite3_result_text(context,
3086
+ mprintf("%lld/%lld", (i64)buf.st_dev, (i64)buf.st_ino), -1,
3087
+ fossil_free);
3088
+ }
3089
+ }
3090
+#endif
3091
+}
30063092
--- src/file.c
+++ src/file.c
@@ -1337,25 +1337,37 @@
1337 }
1338 }
1339
1340 /*
1341 ** Compute a canonical pathname for a file or directory.
1342 ** Make the name absolute if it is relative.
1343 ** Remove redundant / characters
1344 ** Remove all /./ path elements.
1345 ** Convert /A/../ to just /
 
 
 
1346 ** If the slash parameter is non-zero, the trailing slash, if any,
1347 ** is retained.
1348 **
1349 ** See also: file_canonical_name_dup()
1350 */
1351 void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){
 
1352 blob_zero(pOut);
1353 if( file_is_absolute_path(zOrigName) ){
1354 blob_appendf(pOut, "%/", zOrigName);
 
 
 
 
 
 
 
 
 
1355 }else{
1356 char zPwd[2000];
1357 file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName));
1358 if( zPwd[0]=='/' && strlen(zPwd)==1 ){
1359 /* when on '/', don't add an extra '/' */
1360 if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){
1361 /* '.' when on '/' mean '/' */
@@ -1679,10 +1691,13 @@
1679 g.allowSymlinks = !is_false(zAllow);
1680 }
1681 if( zRoot==0 ) zRoot = g.zLocalRoot==0 ? "" : g.zLocalRoot;
1682 fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks());
1683 fossil_print("local-root = [%s]\n", zRoot);
 
 
 
1684 for(i=2; i<g.argc; i++){
1685 char *z;
1686 emitFileStat(g.argv[i], slashFlag, resetFlag);
1687 z = file_canonical_name_dup(g.argv[i]);
1688 fossil_print(" file_canonical_name = %s\n", z);
@@ -1692,10 +1707,13 @@
1692 }else{
1693 int n = file_nondir_objects_on_path(zRoot, z);
1694 fossil_print("%.*s\n", n, z);
1695 }
1696 fossil_free(z);
 
 
 
1697 }
1698 }
1699
1700 /*
1701 ** COMMAND: test-canonical-name
@@ -1734,13 +1752,15 @@
1734 ** Canonical names are full pathnames using "/" not "\" and which
1735 ** contain no "/./" or "/../" terms.
1736 */
1737 int file_is_canonical(const char *z){
1738 int i;
1739 if( z[0]!='/'
1740 #if defined(_WIN32) || defined(__CYGWIN__)
1741 && (!fossil_isupper(z[0]) || z[1]!=':' || z[2]!='/')
 
 
1742 #endif
1743 ) return 0;
1744
1745 for(i=0; z[i]; i++){
1746 if( z[i]=='\\' ) return 0;
@@ -2988,18 +3008,84 @@
2988 ** Returns 1 if the given directory contains a file named .fslckout, 2
2989 ** if it contains a file named _FOSSIL_, else returns 0.
2990 */
2991 int dir_has_ckout_db(const char *zDir){
2992 int rc = 0;
 
2993 char * zCkoutDb = mprintf("%//.fslckout", zDir);
2994 if(file_isfile(zCkoutDb, ExtFILE)){
2995 rc = 1;
2996 }else{
2997 fossil_free(zCkoutDb);
2998 zCkoutDb = mprintf("%//_FOSSIL_", zDir);
2999 if(file_isfile(zCkoutDb, ExtFILE)){
3000 rc = 2;
3001 }
 
 
 
3002 }
3003 fossil_free(zCkoutDb);
3004 return rc;
3005 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3006
--- src/file.c
+++ src/file.c
@@ -1337,25 +1337,37 @@
1337 }
1338 }
1339
1340 /*
1341 ** Compute a canonical pathname for a file or directory.
1342 **
1343 ** * Make the name absolute if it is relative.
1344 ** * Remove redundant / characters
1345 ** * Remove all /./ path elements.
1346 ** * Convert /A/../ to just /
1347 ** * On windows, add the drive letter prefix.
1348 **
1349 ** If the slash parameter is non-zero, the trailing slash, if any,
1350 ** is retained.
1351 **
1352 ** See also: file_canonical_name_dup()
1353 */
1354 void file_canonical_name(const char *zOrigName, Blob *pOut, int slash){
1355 char zPwd[2000];
1356 blob_zero(pOut);
1357 if( file_is_absolute_path(zOrigName) ){
1358 #if defined(_WIN32)
1359 if( fossil_isdirsep(zOrigName[0]) ){
1360 /* Add the drive letter to the full pathname */
1361 file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName));
1362 blob_appendf(pOut, "%.2s%/", zPwd, zOrigName);
1363 }else
1364 #endif
1365 {
1366 blob_appendf(pOut, "%/", zOrigName);
1367 }
1368 }else{
 
1369 file_getcwd(zPwd, sizeof(zPwd)-strlen(zOrigName));
1370 if( zPwd[0]=='/' && strlen(zPwd)==1 ){
1371 /* when on '/', don't add an extra '/' */
1372 if( zOrigName[0]=='.' && strlen(zOrigName)==1 ){
1373 /* '.' when on '/' mean '/' */
@@ -1679,10 +1691,13 @@
1691 g.allowSymlinks = !is_false(zAllow);
1692 }
1693 if( zRoot==0 ) zRoot = g.zLocalRoot==0 ? "" : g.zLocalRoot;
1694 fossil_print("db_allow_symlinks() = %d\n", db_allow_symlinks());
1695 fossil_print("local-root = [%s]\n", zRoot);
1696 if( g.db==0 ) sqlite3_open(":memory:", &g.db);
1697 sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
1698 file_inode_sql_func, 0, 0);
1699 for(i=2; i<g.argc; i++){
1700 char *z;
1701 emitFileStat(g.argv[i], slashFlag, resetFlag);
1702 z = file_canonical_name_dup(g.argv[i]);
1703 fossil_print(" file_canonical_name = %s\n", z);
@@ -1692,10 +1707,13 @@
1707 }else{
1708 int n = file_nondir_objects_on_path(zRoot, z);
1709 fossil_print("%.*s\n", n, z);
1710 }
1711 fossil_free(z);
1712 z = db_text(0, "SELECT inode(%Q)", g.argv[i]);
1713 fossil_print(" file_inode_sql_func = \"%s\"\n", z);
1714 fossil_free(z);
1715 }
1716 }
1717
1718 /*
1719 ** COMMAND: test-canonical-name
@@ -1734,13 +1752,15 @@
1752 ** Canonical names are full pathnames using "/" not "\" and which
1753 ** contain no "/./" or "/../" terms.
1754 */
1755 int file_is_canonical(const char *z){
1756 int i;
1757 if(
1758 #if defined(_WIN32) || defined(__CYGWIN__)
1759 !fossil_isupper(z[0]) || z[1]!=':' || !fossil_isdirsep(z[2])
1760 #else
1761 z[0]!='/'
1762 #endif
1763 ) return 0;
1764
1765 for(i=0; z[i]; i++){
1766 if( z[i]=='\\' ) return 0;
@@ -2988,18 +3008,84 @@
3008 ** Returns 1 if the given directory contains a file named .fslckout, 2
3009 ** if it contains a file named _FOSSIL_, else returns 0.
3010 */
3011 int dir_has_ckout_db(const char *zDir){
3012 int rc = 0;
3013 i64 sz;
3014 char * zCkoutDb = mprintf("%//.fslckout", zDir);
3015 if(file_isfile(zCkoutDb, ExtFILE)){
3016 rc = 1;
3017 }else{
3018 fossil_free(zCkoutDb);
3019 zCkoutDb = mprintf("%//_FOSSIL_", zDir);
3020 if(file_isfile(zCkoutDb, ExtFILE)){
3021 rc = 2;
3022 }
3023 }
3024 if( rc && ((sz = file_size(zCkoutDb, ExtFILE))<1024 || (sz%512)!=0) ){
3025 rc = 0;
3026 }
3027 fossil_free(zCkoutDb);
3028 return rc;
3029 }
3030
3031 /*
3032 ** This is the implementation of inode(FILENAME) SQL function.
3033 **
3034 ** dev_inode(FILENAME) returns a string. If FILENAME exists and is
3035 ** a regular file, then the return string is of the form:
3036 **
3037 ** DEV/INODE
3038 **
3039 ** Where DEV and INODE are the device number and inode number for
3040 ** the file. On Windows, the volume serial number (DEV) and file
3041 ** identifier (INODE) are used to compute the value, see comments
3042 ** on the win32_file_id() function.
3043 **
3044 ** If FILENAME does not exist, then the return is an empty string.
3045 **
3046 ** The value of inode() can be used to eliminate files from a list
3047 ** that have duplicates because they have differing names due to links.
3048 **
3049 ** Code that wants to use this SQL function needs to first register
3050 ** it using a call such as the following:
3051 **
3052 ** sqlite3_create_function(g.db, "inode", 1, SQLITE_UTF8, 0,
3053 ** file_inode_sql_func, 0, 0);
3054 */
3055 void file_inode_sql_func(
3056 sqlite3_context *context,
3057 int argc,
3058 sqlite3_value **argv
3059 ){
3060 const char *zFilename;
3061 assert( argc==1 );
3062 zFilename = (const char*)sqlite3_value_text(argv[0]);
3063 if( zFilename==0 || zFilename[0]==0 || file_access(zFilename,F_OK) ){
3064 sqlite3_result_text(context, "", 0, SQLITE_STATIC);
3065 return;
3066 }
3067 #if defined(_WIN32)
3068 {
3069 char *zFileId = win32_file_id(zFilename);
3070 if( zFileId ){
3071 sqlite3_result_text(context, zFileId, -1, fossil_free);
3072 }else{
3073 sqlite3_result_text(context, "", 0, SQLITE_STATIC);
3074 }
3075 }
3076 #else
3077 {
3078 struct stat buf;
3079 int rc;
3080 memset(&buf, 0, sizeof(buf));
3081 rc = stat(zFilename, &buf);
3082 if( rc ){
3083 sqlite3_result_text(context, "", 0, SQLITE_STATIC);
3084 }else{
3085 sqlite3_result_text(context,
3086 mprintf("%lld/%lld", (i64)buf.st_dev, (i64)buf.st_ino), -1,
3087 fossil_free);
3088 }
3089 }
3090 #endif
3091 }
3092
+60 -35
--- src/forum.c
+++ src/forum.c
@@ -1761,20 +1761,35 @@
17611761
@ </form>
17621762
forum_emit_js();
17631763
style_finish_page();
17641764
}
17651765
1766
+/*
1767
+** SETTING: forum-close-policy boolean default=off
1768
+** If true, forum moderators may close/re-open forum posts, and reply
1769
+** to closed posts. If false, only administrators may do so. Note that
1770
+** this only affects the forum web UI, not post-closing tags which
1771
+** arrive via the command-line or from synchronization with a remote.
1772
+*/
1773
+/*
1774
+** SETTING: forum-title width=20 default=Forum
1775
+** This is the name or "title" of the Forum for this repository. The
1776
+** default is just "Forum". But in some setups, admins might want to
1777
+** change it to "Developer Forum" or "User Forum" or whatever other name
1778
+** seems more appropriate for the particular usage.
1779
+*/
1780
+
17661781
/*
17671782
** WEBPAGE: setup_forum
17681783
**
17691784
** Forum configuration and metrics.
17701785
*/
17711786
void forum_setup(void){
17721787
/* boolean config settings specific to the forum. */
1773
- const char * zSettingsBool[] = {
1774
- "forum-close-policy",
1775
- NULL /* sentinel entry */
1788
+ static const char *azForumSettings[] = {
1789
+ "forum-close-policy",
1790
+ "forum-title",
17761791
};
17771792
17781793
login_check_credentials();
17791794
if( !g.perm.Setup ){
17801795
login_needed(g.anon.Setup);
@@ -1789,93 +1804,102 @@
17891804
@ <p><a href='%R/forum'>Forum posts</a>:
17901805
@ <a href='%R/timeline?y=f'>%d(nPosts)</a></p>
17911806
}
17921807
17931808
@ <h2>Supervisors</h2>
1794
- @ <p>Users with capabilities 's', 'a', or '6'.</p>
17951809
{
17961810
Stmt q = empty_Stmt;
1797
- int nRows = 0;
17981811
db_prepare(&q, "SELECT uid, login, cap FROM user "
17991812
"WHERE cap GLOB '*[as6]*' ORDER BY login");
18001813
@ <table class='bordered'>
18011814
@ <thead><tr><th>User</th><th>Capabilities</th></tr></thead>
18021815
@ <tbody>
18031816
while( SQLITE_ROW==db_step(&q) ){
18041817
const int iUid = db_column_int(&q, 0);
18051818
const char *zUser = db_column_text(&q, 1);
18061819
const char *zCap = db_column_text(&q, 2);
1807
- ++nRows;
18081820
@ <tr>
18091821
@ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td>
18101822
@ <td>(%h(zCap))</td>
18111823
@ </tr>
18121824
}
18131825
db_finalize(&q);
18141826
@</tbody></table>
1815
- if( 0==nRows ){
1816
- @ No supervisors
1817
- }else{
1818
- @ %d(nRows) supervisor(s)
1819
- }
18201827
}
18211828
18221829
@ <h2>Moderators</h2>
1823
- @ <p>Users with capability '5'.</p>
1824
- {
1830
+ if( db_int(0, "SELECT count(*) FROM user "
1831
+ " WHERE cap GLOB '*5*' AND cap NOT GLOB '*[as6]*'")==0 ){
1832
+ @ <p>No non-supervisor moderators
1833
+ }else{
18251834
Stmt q = empty_Stmt;
1826
- int nRows = 0;
18271835
db_prepare(&q, "SELECT uid, login, cap FROM user "
1828
- "WHERE cap GLOB '*5*' ORDER BY login");
1836
+ "WHERE cap GLOB '*5*' AND cap NOT GLOB '*[as6]*'"
1837
+ " ORDER BY login");
18291838
@ <table class='bordered'>
18301839
@ <thead><tr><th>User</th><th>Capabilities</th></tr></thead>
18311840
@ <tbody>
18321841
while( SQLITE_ROW==db_step(&q) ){
18331842
const int iUid = db_column_int(&q, 0);
18341843
const char *zUser = db_column_text(&q, 1);
18351844
const char *zCap = db_column_text(&q, 2);
1836
- ++nRows;
18371845
@ <tr>
18381846
@ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td>
18391847
@ <td>(%h(zCap))</td>
18401848
@ </tr>
18411849
}
18421850
db_finalize(&q);
18431851
@ </tbody></table>
1844
- if( 0==nRows ){
1845
- @ No non-supervisor moderators
1846
- }else{
1847
- @ %d(nRows) moderator(s)
1848
- }
18491852
}
18501853
18511854
@ <h2>Settings</h2>
1852
- @ <p>Configuration settings specific to the forum.</p>
18531855
if( P("submit") && cgi_csrf_safe(2) ){
18541856
int i = 0;
1855
- const char *zSetting;
18561857
db_begin_transaction();
1857
- while( (zSetting = zSettingsBool[i++]) ){
1858
- const char *z = P(zSetting);
1859
- if( !z || !z[0] ) z = "off";
1860
- db_set(zSetting/*works-like:"x"*/, z, 0);
1858
+ for(i=0; i<ArraySize(azForumSettings); i++){
1859
+ char zQP[4];
1860
+ const char *z;
1861
+ const Setting *pSetting = setting_find(azForumSettings[i]);
1862
+ if( pSetting==0 ) continue;
1863
+ zQP[0] = 'a'+i;
1864
+ zQP[1] = zQP[0];
1865
+ zQP[2] = 0;
1866
+ z = P(zQP);
1867
+ if( z==0 || z[0]==0 ) continue;
1868
+ db_set(pSetting->name/*works-like:"x"*/, z, 0);
18611869
}
18621870
db_end_transaction(0);
18631871
@ <p><em>Settings saved.</em></p>
18641872
}
18651873
{
18661874
int i = 0;
1867
- const char *zSetting;
18681875
@ <form action="%R/setup_forum" method="post">
18691876
login_insert_csrf_secret();
18701877
@ <table class='forum-settings-list'><tbody>
1871
- while( (zSetting = zSettingsBool[i++]) ){
1872
- @ <tr><td>
1873
- onoff_attribute("", zSetting, zSetting/*works-like:"x"*/, 0, 0);
1874
- @ </td><td>
1875
- @ <a href='%R/help?cmd=%h(zSetting)'>%h(zSetting)</a>
1876
- @ </td></tr>
1878
+ for(i=0; i<ArraySize(azForumSettings); i++){
1879
+ char zQP[4];
1880
+ const Setting *pSetting = setting_find(azForumSettings[i]);
1881
+ if( pSetting==0 ) continue;
1882
+ zQP[0] = 'a'+i;
1883
+ zQP[1] = zQP[0];
1884
+ zQP[2] = 0;
1885
+ if( pSetting->width==0 ){
1886
+ /* Boolean setting */
1887
+ @ <tr><td align="right">
1888
+ @ <a href='%R/help?cmd=%h(pSetting->name)'>%h(pSetting->name)</a>:
1889
+ @ </td><td>
1890
+ onoff_attribute("", zQP, pSetting->name/*works-like:"x"*/, 0, 0);
1891
+ @ </td></tr>
1892
+ }else{
1893
+ /* Text value setting */
1894
+ @ <tr><td align="right">
1895
+ @ <a href='%R/help?cmd=%h(pSetting->name)'>%h(pSetting->name)</a>:
1896
+ @ </td><td>
1897
+ entry_attribute("", 25, pSetting->name, zQP/*works-like:""*/,
1898
+ pSetting->def, 0);
1899
+ @ </td></tr>
1900
+ }
18771901
}
18781902
@ </tbody></table>
18791903
@ <input type='submit' name='submit' value='Apply changes'>
18801904
@ </form>
18811905
}
@@ -1910,11 +1934,12 @@
19101934
login_needed(g.anon.RdForum);
19111935
return;
19121936
}
19131937
cgi_check_for_malice();
19141938
style_set_current_feature("forum");
1915
- style_header( "%s", isSearch ? "Forum Search Results" : "Forum" );
1939
+ style_header("%s%s", db_get("forum-title","Forum"),
1940
+ isSearch ? " Search Results" : "");
19161941
style_submenu_element("Timeline", "%R/timeline?ss=v&y=f&vfx");
19171942
if( g.perm.WrForum ){
19181943
style_submenu_element("New Thread","%R/forumnew");
19191944
}else{
19201945
/* Can't combine this with previous case using the ternary operator
19211946
--- src/forum.c
+++ src/forum.c
@@ -1761,20 +1761,35 @@
1761 @ </form>
1762 forum_emit_js();
1763 style_finish_page();
1764 }
1765
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1766 /*
1767 ** WEBPAGE: setup_forum
1768 **
1769 ** Forum configuration and metrics.
1770 */
1771 void forum_setup(void){
1772 /* boolean config settings specific to the forum. */
1773 const char * zSettingsBool[] = {
1774 "forum-close-policy",
1775 NULL /* sentinel entry */
1776 };
1777
1778 login_check_credentials();
1779 if( !g.perm.Setup ){
1780 login_needed(g.anon.Setup);
@@ -1789,93 +1804,102 @@
1789 @ <p><a href='%R/forum'>Forum posts</a>:
1790 @ <a href='%R/timeline?y=f'>%d(nPosts)</a></p>
1791 }
1792
1793 @ <h2>Supervisors</h2>
1794 @ <p>Users with capabilities 's', 'a', or '6'.</p>
1795 {
1796 Stmt q = empty_Stmt;
1797 int nRows = 0;
1798 db_prepare(&q, "SELECT uid, login, cap FROM user "
1799 "WHERE cap GLOB '*[as6]*' ORDER BY login");
1800 @ <table class='bordered'>
1801 @ <thead><tr><th>User</th><th>Capabilities</th></tr></thead>
1802 @ <tbody>
1803 while( SQLITE_ROW==db_step(&q) ){
1804 const int iUid = db_column_int(&q, 0);
1805 const char *zUser = db_column_text(&q, 1);
1806 const char *zCap = db_column_text(&q, 2);
1807 ++nRows;
1808 @ <tr>
1809 @ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td>
1810 @ <td>(%h(zCap))</td>
1811 @ </tr>
1812 }
1813 db_finalize(&q);
1814 @</tbody></table>
1815 if( 0==nRows ){
1816 @ No supervisors
1817 }else{
1818 @ %d(nRows) supervisor(s)
1819 }
1820 }
1821
1822 @ <h2>Moderators</h2>
1823 @ <p>Users with capability '5'.</p>
1824 {
 
 
1825 Stmt q = empty_Stmt;
1826 int nRows = 0;
1827 db_prepare(&q, "SELECT uid, login, cap FROM user "
1828 "WHERE cap GLOB '*5*' ORDER BY login");
 
1829 @ <table class='bordered'>
1830 @ <thead><tr><th>User</th><th>Capabilities</th></tr></thead>
1831 @ <tbody>
1832 while( SQLITE_ROW==db_step(&q) ){
1833 const int iUid = db_column_int(&q, 0);
1834 const char *zUser = db_column_text(&q, 1);
1835 const char *zCap = db_column_text(&q, 2);
1836 ++nRows;
1837 @ <tr>
1838 @ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td>
1839 @ <td>(%h(zCap))</td>
1840 @ </tr>
1841 }
1842 db_finalize(&q);
1843 @ </tbody></table>
1844 if( 0==nRows ){
1845 @ No non-supervisor moderators
1846 }else{
1847 @ %d(nRows) moderator(s)
1848 }
1849 }
1850
1851 @ <h2>Settings</h2>
1852 @ <p>Configuration settings specific to the forum.</p>
1853 if( P("submit") && cgi_csrf_safe(2) ){
1854 int i = 0;
1855 const char *zSetting;
1856 db_begin_transaction();
1857 while( (zSetting = zSettingsBool[i++]) ){
1858 const char *z = P(zSetting);
1859 if( !z || !z[0] ) z = "off";
1860 db_set(zSetting/*works-like:"x"*/, z, 0);
 
 
 
 
 
 
 
1861 }
1862 db_end_transaction(0);
1863 @ <p><em>Settings saved.</em></p>
1864 }
1865 {
1866 int i = 0;
1867 const char *zSetting;
1868 @ <form action="%R/setup_forum" method="post">
1869 login_insert_csrf_secret();
1870 @ <table class='forum-settings-list'><tbody>
1871 while( (zSetting = zSettingsBool[i++]) ){
1872 @ <tr><td>
1873 onoff_attribute("", zSetting, zSetting/*works-like:"x"*/, 0, 0);
1874 @ </td><td>
1875 @ <a href='%R/help?cmd=%h(zSetting)'>%h(zSetting)</a>
1876 @ </td></tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1877 }
1878 @ </tbody></table>
1879 @ <input type='submit' name='submit' value='Apply changes'>
1880 @ </form>
1881 }
@@ -1910,11 +1934,12 @@
1910 login_needed(g.anon.RdForum);
1911 return;
1912 }
1913 cgi_check_for_malice();
1914 style_set_current_feature("forum");
1915 style_header( "%s", isSearch ? "Forum Search Results" : "Forum" );
 
1916 style_submenu_element("Timeline", "%R/timeline?ss=v&y=f&vfx");
1917 if( g.perm.WrForum ){
1918 style_submenu_element("New Thread","%R/forumnew");
1919 }else{
1920 /* Can't combine this with previous case using the ternary operator
1921
--- src/forum.c
+++ src/forum.c
@@ -1761,20 +1761,35 @@
1761 @ </form>
1762 forum_emit_js();
1763 style_finish_page();
1764 }
1765
1766 /*
1767 ** SETTING: forum-close-policy boolean default=off
1768 ** If true, forum moderators may close/re-open forum posts, and reply
1769 ** to closed posts. If false, only administrators may do so. Note that
1770 ** this only affects the forum web UI, not post-closing tags which
1771 ** arrive via the command-line or from synchronization with a remote.
1772 */
1773 /*
1774 ** SETTING: forum-title width=20 default=Forum
1775 ** This is the name or "title" of the Forum for this repository. The
1776 ** default is just "Forum". But in some setups, admins might want to
1777 ** change it to "Developer Forum" or "User Forum" or whatever other name
1778 ** seems more appropriate for the particular usage.
1779 */
1780
1781 /*
1782 ** WEBPAGE: setup_forum
1783 **
1784 ** Forum configuration and metrics.
1785 */
1786 void forum_setup(void){
1787 /* boolean config settings specific to the forum. */
1788 static const char *azForumSettings[] = {
1789 "forum-close-policy",
1790 "forum-title",
1791 };
1792
1793 login_check_credentials();
1794 if( !g.perm.Setup ){
1795 login_needed(g.anon.Setup);
@@ -1789,93 +1804,102 @@
1804 @ <p><a href='%R/forum'>Forum posts</a>:
1805 @ <a href='%R/timeline?y=f'>%d(nPosts)</a></p>
1806 }
1807
1808 @ <h2>Supervisors</h2>
 
1809 {
1810 Stmt q = empty_Stmt;
 
1811 db_prepare(&q, "SELECT uid, login, cap FROM user "
1812 "WHERE cap GLOB '*[as6]*' ORDER BY login");
1813 @ <table class='bordered'>
1814 @ <thead><tr><th>User</th><th>Capabilities</th></tr></thead>
1815 @ <tbody>
1816 while( SQLITE_ROW==db_step(&q) ){
1817 const int iUid = db_column_int(&q, 0);
1818 const char *zUser = db_column_text(&q, 1);
1819 const char *zCap = db_column_text(&q, 2);
 
1820 @ <tr>
1821 @ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td>
1822 @ <td>(%h(zCap))</td>
1823 @ </tr>
1824 }
1825 db_finalize(&q);
1826 @</tbody></table>
 
 
 
 
 
1827 }
1828
1829 @ <h2>Moderators</h2>
1830 if( db_int(0, "SELECT count(*) FROM user "
1831 " WHERE cap GLOB '*5*' AND cap NOT GLOB '*[as6]*'")==0 ){
1832 @ <p>No non-supervisor moderators
1833 }else{
1834 Stmt q = empty_Stmt;
 
1835 db_prepare(&q, "SELECT uid, login, cap FROM user "
1836 "WHERE cap GLOB '*5*' AND cap NOT GLOB '*[as6]*'"
1837 " ORDER BY login");
1838 @ <table class='bordered'>
1839 @ <thead><tr><th>User</th><th>Capabilities</th></tr></thead>
1840 @ <tbody>
1841 while( SQLITE_ROW==db_step(&q) ){
1842 const int iUid = db_column_int(&q, 0);
1843 const char *zUser = db_column_text(&q, 1);
1844 const char *zCap = db_column_text(&q, 2);
 
1845 @ <tr>
1846 @ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td>
1847 @ <td>(%h(zCap))</td>
1848 @ </tr>
1849 }
1850 db_finalize(&q);
1851 @ </tbody></table>
 
 
 
 
 
1852 }
1853
1854 @ <h2>Settings</h2>
 
1855 if( P("submit") && cgi_csrf_safe(2) ){
1856 int i = 0;
 
1857 db_begin_transaction();
1858 for(i=0; i<ArraySize(azForumSettings); i++){
1859 char zQP[4];
1860 const char *z;
1861 const Setting *pSetting = setting_find(azForumSettings[i]);
1862 if( pSetting==0 ) continue;
1863 zQP[0] = 'a'+i;
1864 zQP[1] = zQP[0];
1865 zQP[2] = 0;
1866 z = P(zQP);
1867 if( z==0 || z[0]==0 ) continue;
1868 db_set(pSetting->name/*works-like:"x"*/, z, 0);
1869 }
1870 db_end_transaction(0);
1871 @ <p><em>Settings saved.</em></p>
1872 }
1873 {
1874 int i = 0;
 
1875 @ <form action="%R/setup_forum" method="post">
1876 login_insert_csrf_secret();
1877 @ <table class='forum-settings-list'><tbody>
1878 for(i=0; i<ArraySize(azForumSettings); i++){
1879 char zQP[4];
1880 const Setting *pSetting = setting_find(azForumSettings[i]);
1881 if( pSetting==0 ) continue;
1882 zQP[0] = 'a'+i;
1883 zQP[1] = zQP[0];
1884 zQP[2] = 0;
1885 if( pSetting->width==0 ){
1886 /* Boolean setting */
1887 @ <tr><td align="right">
1888 @ <a href='%R/help?cmd=%h(pSetting->name)'>%h(pSetting->name)</a>:
1889 @ </td><td>
1890 onoff_attribute("", zQP, pSetting->name/*works-like:"x"*/, 0, 0);
1891 @ </td></tr>
1892 }else{
1893 /* Text value setting */
1894 @ <tr><td align="right">
1895 @ <a href='%R/help?cmd=%h(pSetting->name)'>%h(pSetting->name)</a>:
1896 @ </td><td>
1897 entry_attribute("", 25, pSetting->name, zQP/*works-like:""*/,
1898 pSetting->def, 0);
1899 @ </td></tr>
1900 }
1901 }
1902 @ </tbody></table>
1903 @ <input type='submit' name='submit' value='Apply changes'>
1904 @ </form>
1905 }
@@ -1910,11 +1934,12 @@
1934 login_needed(g.anon.RdForum);
1935 return;
1936 }
1937 cgi_check_for_malice();
1938 style_set_current_feature("forum");
1939 style_header("%s%s", db_get("forum-title","Forum"),
1940 isSearch ? " Search Results" : "");
1941 style_submenu_element("Timeline", "%R/timeline?ss=v&y=f&vfx");
1942 if( g.perm.WrForum ){
1943 style_submenu_element("New Thread","%R/forumnew");
1944 }else{
1945 /* Can't combine this with previous case using the ternary operator
1946
+80 -26
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -1,28 +1,93 @@
11
/**
22
diff-related JS APIs for fossil.
33
*/
44
"use strict";
5
+/* Locate the UI element (if any) into which we can inject some diff-related
6
+ UI controls. */
7
+window.fossil.onPageLoad(function(){
8
+ const potentialParents = window.fossil.page.diffControlContainers = [
9
+ /* CSS selectors for possible parents for injected diff-related UI
10
+ controls. */
11
+ /* Put the most likely pages at the end, as array.pop() is more
12
+ efficient than array.shift() (see loop below). */
13
+ /* /filedit */ 'body.cpage-fileedit #fileedit-tab-diff-buttons',
14
+ /* /wikiedit */ 'body.cpage-wikiedit #wikiedit-tab-diff-buttons',
15
+ /* /fdiff */ 'body.fdiff form div.submenu',
16
+ /* /vdiff */ 'body.vdiff form div.submenu',
17
+ /* /info, /vinfo, /ckout */ 'body.vinfo div.sectionmenu.info-changes-menu'
18
+ ];
19
+ window.fossil.page.diffControlContainer = undefined;
20
+ while( potentialParents.length ){
21
+ if( (window.fossil.page.diffControlContainer
22
+ = document.querySelector(potentialParents.pop())) ){
23
+ break;
24
+ }
25
+ }
26
+});
27
+
528
window.fossil.onPageLoad(function(){
629
/**
730
Adds toggle checkboxes to each file entry in the diff views for
831
/info and similar pages.
932
*/
33
+ if( !window.fossil.page.diffControlContainer ){
34
+ return;
35
+ }
1036
const D = window.fossil.dom;
11
- const isFdiff = !!document.querySelector('body.fdiff');
37
+ const allToggles = [/*collection of all diff-toggle checkboxes*/];
38
+ let checkedCount =
39
+ 0 /* When showing more than one diff, keep track of how many
40
+ "show/hide" checkboxes are are checked so we can update the
41
+ "show/hide all" label dynamically. */;
42
+ let btnAll /* UI control to show/hide all diffs */;
43
+ /* Install a diff-toggle button for the given diff table element. */
1244
const addToggle = function(diffElem){
1345
const sib = diffElem.previousElementSibling,
14
- btn = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
46
+ ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
1547
if(!sib) return;
16
- if(isFdiff) sib.parentElement.insertBefore(
17
- D.append(D.div(),btn),sib.nextElementSibling);
18
- else D.append(sib,btn);
19
- btn.addEventListener('click', function(){
20
- diffElem.classList.toggle('hidden');
48
+ const lblToggle = D.label();
49
+ D.append(lblToggle, ckbox, D.text(" show/hide "));
50
+ const wrapper = D.append(D.span(), lblToggle);
51
+ allToggles.push(ckbox);
52
+ ++checkedCount;
53
+ D.append(sib, D.append(wrapper, lblToggle));
54
+ ckbox.addEventListener('change', function(){
55
+ diffElem.classList[this.checked ? 'remove' : 'add']('hidden');
56
+ if(btnAll){
57
+ checkedCount += (this.checked ? 1 : -1);
58
+ btnAll.innerText = (checkedCount < allToggles.length)
59
+ ? "Show diffs" : "Hide diffs";
60
+ }
61
+ }, false);
62
+ };
63
+ if( !document.querySelector('body.fdiff') ){
64
+ /* Don't show the diff toggle button for /fdiff because it only
65
+ has a single file to show (and also a different DOM layout). */
66
+ document.querySelectorAll('table.diff').forEach(addToggle);
67
+ }
68
+ /**
69
+ Set up a "toggle all diffs" button which toggles all of the
70
+ above-installed checkboxes, but only if more than one diff is
71
+ rendered.
72
+ */
73
+ const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0;
74
+ if(icm) {
75
+ btnAll = D.addClass(D.a("#", "Hide diffs"), "button");
76
+ D.append( icm, btnAll );
77
+ btnAll.addEventListener('click', function(ev){
78
+ ev.preventDefault();
79
+ ev.stopPropagation();
80
+ const show = checkedCount < allToggles.length;
81
+ for( const ckbox of allToggles ){
82
+ /* Toggle all entries to match this new state. We use click()
83
+ instead of ckbox.checked=... so that the on-change event handler
84
+ fires. */
85
+ if(ckbox.checked!==show) ckbox.click();
86
+ }
2187
}, false);
22
- };
23
- document.querySelectorAll('table.diff').forEach(addToggle);
88
+ }
2489
});
2590
2691
window.fossil.onPageLoad(function(){
2792
const F = window.fossil, D = F.dom;
2893
const Diff = F.diff = {
@@ -635,26 +700,15 @@
635700
/* Look for a parent element to hold the sbs-sync-scroll toggle
636701
checkbox. This differs per page. If we don't find one, simply
637702
elide that toggle and use whatever preference the user last
638703
specified (defaulting to on). */
639704
let cbSync /* scroll-sync checkbox */;
640
- let eToggleParent /* element to put the sync-scroll checkbox in */;
641
- const potentialParents = [ /* possible parents for the checkbox */
642
- /* Put the most likely pages at the end, as array.pop() is more
643
- efficient than array.shift() (see loop below). */
644
- /* /filedit */ 'body.cpage-fileedit #fileedit-tab-diff-buttons',
645
- /* /wikiedit */ 'body.cpage-wikiedit #wikiedit-tab-diff-buttons',
646
- /* /fdiff */ 'body.fdiff form div.submenu',
647
- /* /vdiff */ 'body.vdiff form div.submenu',
648
- /* /info, /vinfo */ 'body.vinfo div.sectionmenu.info-changes-menu'
649
- ];
650
- while( potentialParents.length ){
651
- if( (eToggleParent = document.querySelector(potentialParents.pop())) ){
652
- break;
653
- }
654
- }
655
- const keySbsScroll = 'sync-diff-scroll' /* F.storage key */;
705
+ let eToggleParent = /* element to put the sync-scroll checkbox in */
706
+ document.querySelector('table.diff.splitdiff')
707
+ ? window.fossil.page.diffControlContainer
708
+ : undefined;
709
+ const keySbsScroll = 'sync-diff-scroll' /* F.storage key for persistent user preference */;
656710
if( eToggleParent ){
657711
/* Add a checkbox to toggle sbs scroll sync. Remember that in
658712
order to be UI-consistent in the /vdiff page we have to ensure
659713
that the checkbox is to the LEFT of of its label. We store the
660714
sync-scroll preference in F.storage (not a cookie) so that it
@@ -661,11 +715,11 @@
661715
persists across page loads and different apps. */
662716
cbSync = D.checkbox(keySbsScroll, F.storage.getBool(keySbsScroll,true));
663717
D.append(eToggleParent, D.append(
664718
D.addClass(D.create('span'), 'input-with-label'),
665719
D.append(D.create('label'),
666
- cbSync, "Sync side-by-side scrolling")
720
+ cbSync, "Scroll Sync")
667721
));
668722
cbSync.addEventListener('change', function(e){
669723
F.storage.set(keySbsScroll, e.target.checked);
670724
});
671725
}
672726
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -1,28 +1,93 @@
1 /**
2 diff-related JS APIs for fossil.
3 */
4 "use strict";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5 window.fossil.onPageLoad(function(){
6 /**
7 Adds toggle checkboxes to each file entry in the diff views for
8 /info and similar pages.
9 */
 
 
 
10 const D = window.fossil.dom;
11 const isFdiff = !!document.querySelector('body.fdiff');
 
 
 
 
 
 
12 const addToggle = function(diffElem){
13 const sib = diffElem.previousElementSibling,
14 btn = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
15 if(!sib) return;
16 if(isFdiff) sib.parentElement.insertBefore(
17 D.append(D.div(),btn),sib.nextElementSibling);
18 else D.append(sib,btn);
19 btn.addEventListener('click', function(){
20 diffElem.classList.toggle('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21 }, false);
22 };
23 document.querySelectorAll('table.diff').forEach(addToggle);
24 });
25
26 window.fossil.onPageLoad(function(){
27 const F = window.fossil, D = F.dom;
28 const Diff = F.diff = {
@@ -635,26 +700,15 @@
635 /* Look for a parent element to hold the sbs-sync-scroll toggle
636 checkbox. This differs per page. If we don't find one, simply
637 elide that toggle and use whatever preference the user last
638 specified (defaulting to on). */
639 let cbSync /* scroll-sync checkbox */;
640 let eToggleParent /* element to put the sync-scroll checkbox in */;
641 const potentialParents = [ /* possible parents for the checkbox */
642 /* Put the most likely pages at the end, as array.pop() is more
643 efficient than array.shift() (see loop below). */
644 /* /filedit */ 'body.cpage-fileedit #fileedit-tab-diff-buttons',
645 /* /wikiedit */ 'body.cpage-wikiedit #wikiedit-tab-diff-buttons',
646 /* /fdiff */ 'body.fdiff form div.submenu',
647 /* /vdiff */ 'body.vdiff form div.submenu',
648 /* /info, /vinfo */ 'body.vinfo div.sectionmenu.info-changes-menu'
649 ];
650 while( potentialParents.length ){
651 if( (eToggleParent = document.querySelector(potentialParents.pop())) ){
652 break;
653 }
654 }
655 const keySbsScroll = 'sync-diff-scroll' /* F.storage key */;
656 if( eToggleParent ){
657 /* Add a checkbox to toggle sbs scroll sync. Remember that in
658 order to be UI-consistent in the /vdiff page we have to ensure
659 that the checkbox is to the LEFT of of its label. We store the
660 sync-scroll preference in F.storage (not a cookie) so that it
@@ -661,11 +715,11 @@
661 persists across page loads and different apps. */
662 cbSync = D.checkbox(keySbsScroll, F.storage.getBool(keySbsScroll,true));
663 D.append(eToggleParent, D.append(
664 D.addClass(D.create('span'), 'input-with-label'),
665 D.append(D.create('label'),
666 cbSync, "Sync side-by-side scrolling")
667 ));
668 cbSync.addEventListener('change', function(e){
669 F.storage.set(keySbsScroll, e.target.checked);
670 });
671 }
672
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -1,28 +1,93 @@
1 /**
2 diff-related JS APIs for fossil.
3 */
4 "use strict";
5 /* Locate the UI element (if any) into which we can inject some diff-related
6 UI controls. */
7 window.fossil.onPageLoad(function(){
8 const potentialParents = window.fossil.page.diffControlContainers = [
9 /* CSS selectors for possible parents for injected diff-related UI
10 controls. */
11 /* Put the most likely pages at the end, as array.pop() is more
12 efficient than array.shift() (see loop below). */
13 /* /filedit */ 'body.cpage-fileedit #fileedit-tab-diff-buttons',
14 /* /wikiedit */ 'body.cpage-wikiedit #wikiedit-tab-diff-buttons',
15 /* /fdiff */ 'body.fdiff form div.submenu',
16 /* /vdiff */ 'body.vdiff form div.submenu',
17 /* /info, /vinfo, /ckout */ 'body.vinfo div.sectionmenu.info-changes-menu'
18 ];
19 window.fossil.page.diffControlContainer = undefined;
20 while( potentialParents.length ){
21 if( (window.fossil.page.diffControlContainer
22 = document.querySelector(potentialParents.pop())) ){
23 break;
24 }
25 }
26 });
27
28 window.fossil.onPageLoad(function(){
29 /**
30 Adds toggle checkboxes to each file entry in the diff views for
31 /info and similar pages.
32 */
33 if( !window.fossil.page.diffControlContainer ){
34 return;
35 }
36 const D = window.fossil.dom;
37 const allToggles = [/*collection of all diff-toggle checkboxes*/];
38 let checkedCount =
39 0 /* When showing more than one diff, keep track of how many
40 "show/hide" checkboxes are are checked so we can update the
41 "show/hide all" label dynamically. */;
42 let btnAll /* UI control to show/hide all diffs */;
43 /* Install a diff-toggle button for the given diff table element. */
44 const addToggle = function(diffElem){
45 const sib = diffElem.previousElementSibling,
46 ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
47 if(!sib) return;
48 const lblToggle = D.label();
49 D.append(lblToggle, ckbox, D.text(" show/hide "));
50 const wrapper = D.append(D.span(), lblToggle);
51 allToggles.push(ckbox);
52 ++checkedCount;
53 D.append(sib, D.append(wrapper, lblToggle));
54 ckbox.addEventListener('change', function(){
55 diffElem.classList[this.checked ? 'remove' : 'add']('hidden');
56 if(btnAll){
57 checkedCount += (this.checked ? 1 : -1);
58 btnAll.innerText = (checkedCount < allToggles.length)
59 ? "Show diffs" : "Hide diffs";
60 }
61 }, false);
62 };
63 if( !document.querySelector('body.fdiff') ){
64 /* Don't show the diff toggle button for /fdiff because it only
65 has a single file to show (and also a different DOM layout). */
66 document.querySelectorAll('table.diff').forEach(addToggle);
67 }
68 /**
69 Set up a "toggle all diffs" button which toggles all of the
70 above-installed checkboxes, but only if more than one diff is
71 rendered.
72 */
73 const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0;
74 if(icm) {
75 btnAll = D.addClass(D.a("#", "Hide diffs"), "button");
76 D.append( icm, btnAll );
77 btnAll.addEventListener('click', function(ev){
78 ev.preventDefault();
79 ev.stopPropagation();
80 const show = checkedCount < allToggles.length;
81 for( const ckbox of allToggles ){
82 /* Toggle all entries to match this new state. We use click()
83 instead of ckbox.checked=... so that the on-change event handler
84 fires. */
85 if(ckbox.checked!==show) ckbox.click();
86 }
87 }, false);
88 }
 
89 });
90
91 window.fossil.onPageLoad(function(){
92 const F = window.fossil, D = F.dom;
93 const Diff = F.diff = {
@@ -635,26 +700,15 @@
700 /* Look for a parent element to hold the sbs-sync-scroll toggle
701 checkbox. This differs per page. If we don't find one, simply
702 elide that toggle and use whatever preference the user last
703 specified (defaulting to on). */
704 let cbSync /* scroll-sync checkbox */;
705 let eToggleParent = /* element to put the sync-scroll checkbox in */
706 document.querySelector('table.diff.splitdiff')
707 ? window.fossil.page.diffControlContainer
708 : undefined;
709 const keySbsScroll = 'sync-diff-scroll' /* F.storage key for persistent user preference */;
 
 
 
 
 
 
 
 
 
 
 
710 if( eToggleParent ){
711 /* Add a checkbox to toggle sbs scroll sync. Remember that in
712 order to be UI-consistent in the /vdiff page we have to ensure
713 that the checkbox is to the LEFT of of its label. We store the
714 sync-scroll preference in F.storage (not a cookie) so that it
@@ -661,11 +715,11 @@
715 persists across page loads and different apps. */
716 cbSync = D.checkbox(keySbsScroll, F.storage.getBool(keySbsScroll,true));
717 D.append(eToggleParent, D.append(
718 D.addClass(D.create('span'), 'input-with-label'),
719 D.append(D.create('label'),
720 cbSync, "Scroll Sync")
721 ));
722 cbSync.addEventListener('change', function(e){
723 F.storage.set(keySbsScroll, e.target.checked);
724 });
725 }
726
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -87,11 +87,13 @@
8787
const rc = document.createElement('label');
8888
if(forElem){
8989
if(forElem instanceof HTMLElement){
9090
forElem = this.attr(forElem, 'id');
9191
}
92
- dom.attr(rc, 'for', forElem);
92
+ if(forElem){
93
+ dom.attr(rc, 'for', forElem);
94
+ }
9395
}
9496
if(text) this.append(rc, text);
9597
return rc;
9698
};
9799
/**
98100
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -87,11 +87,13 @@
87 const rc = document.createElement('label');
88 if(forElem){
89 if(forElem instanceof HTMLElement){
90 forElem = this.attr(forElem, 'id');
91 }
92 dom.attr(rc, 'for', forElem);
 
 
93 }
94 if(text) this.append(rc, text);
95 return rc;
96 };
97 /**
98
--- src/fossil.dom.js
+++ src/fossil.dom.js
@@ -87,11 +87,13 @@
87 const rc = document.createElement('label');
88 if(forElem){
89 if(forElem instanceof HTMLElement){
90 forElem = this.attr(forElem, 'id');
91 }
92 if(forElem){
93 dom.attr(rc, 'for', forElem);
94 }
95 }
96 if(text) this.append(rc, text);
97 return rc;
98 };
99 /**
100
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -2132,11 +2132,11 @@
21322132
s.value ? 'add' : 'remove'
21332133
]('compact');
21342134
Chat.e.inputFields[Chat.e.inputFields.$currentIndex].focus();
21352135
});
21362136
Chat.settings.addListener('edit-ctrl-send',function(s){
2137
- const label = (s.value ? "Ctrl-" : "")+"Enter submits message";
2137
+ const label = (s.value ? "Ctrl-" : "")+"Enter submits message.";
21382138
Chat.e.inputFields.forEach((e)=>{
21392139
const v = e.dataset.placeholder0 + " " +label;
21402140
if(e.isContentEditable) e.dataset.placeholder = v;
21412141
else D.attr(e,'placeholder',v);
21422142
});
21432143
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -2132,11 +2132,11 @@
2132 s.value ? 'add' : 'remove'
2133 ]('compact');
2134 Chat.e.inputFields[Chat.e.inputFields.$currentIndex].focus();
2135 });
2136 Chat.settings.addListener('edit-ctrl-send',function(s){
2137 const label = (s.value ? "Ctrl-" : "")+"Enter submits message";
2138 Chat.e.inputFields.forEach((e)=>{
2139 const v = e.dataset.placeholder0 + " " +label;
2140 if(e.isContentEditable) e.dataset.placeholder = v;
2141 else D.attr(e,'placeholder',v);
2142 });
2143
--- src/fossil.page.chat.js
+++ src/fossil.page.chat.js
@@ -2132,11 +2132,11 @@
2132 s.value ? 'add' : 'remove'
2133 ]('compact');
2134 Chat.e.inputFields[Chat.e.inputFields.$currentIndex].focus();
2135 });
2136 Chat.settings.addListener('edit-ctrl-send',function(s){
2137 const label = (s.value ? "Ctrl-" : "")+"Enter submits message.";
2138 Chat.e.inputFields.forEach((e)=>{
2139 const v = e.dataset.placeholder0 + " " +label;
2140 if(e.isContentEditable) e.dataset.placeholder = v;
2141 else D.attr(e,'placeholder',v);
2142 });
2143
+28 -12
--- src/graph.c
+++ src/graph.c
@@ -495,11 +495,15 @@
495495
**
496496
** TIMELINE_DISJOINT: Omit descenders
497497
** TIMELINE_FILLGAPS: Use step-children
498498
** TIMELINE_XMERGE: Omit off-graph merge lines
499499
*/
500
-void graph_finish(GraphContext *p, const char *zLeftBranch, u32 tmFlags){
500
+void graph_finish(
501
+ GraphContext *p, /* The graph to be laid out */
502
+ Matcher *pLeftBranch, /* Compares true for left-most branch */
503
+ u32 tmFlags /* TIMELINE flags */
504
+){
501505
GraphRow *pRow, *pDesc, *pDup, *pLoop, *pParent;
502506
int i, j;
503507
u64 mask;
504508
int hasDup = 0; /* True if one or more isDup entries */
505509
const char *zTrunk;
@@ -963,12 +967,12 @@
963967
}
964968
}
965969
966970
/*
967971
** Compute the rail mapping that tries to put the branch named
968
- ** zLeftBranch at the left margin. Other branches that merge
969
- ** with zLeftBranch are to the right with merge rails in between.
972
+ ** pLeftBranch at the left margin. Other branches that merge
973
+ ** with pLeftBranch are to the right with merge rails in between.
970974
**
971975
** aMap[X]=Y means that the X-th rail is drawn as the Y-th rail.
972976
**
973977
** Do not move rails around if there are timewarps, because that can
974978
** seriously mess up the display of timewarps. Timewarps should be
@@ -975,10 +979,11 @@
975979
** rare so this should not be a serious limitation to the algorithm.
976980
*/
977981
aMap = p->aiRailMap;
978982
for(i=0; i<=p->mxRail; i++) aMap[i] = i; /* Set up a default mapping */
979983
if( nTimewarp==0 ){
984
+ int kk;
980985
/* Priority bits:
981986
**
982987
** 0x04 The preferred branch
983988
**
984989
** 0x02 A merge rail - a rail that contains merge lines into
@@ -986,17 +991,20 @@
986991
** is defined. This improves the display of r=BRANCH
987992
** options to /timeline.
988993
**
989994
** 0x01 A rail that merges with the preferred branch
990995
*/
991
- u8 aPriority[GR_MAX_RAIL];
992
- memset(aPriority, 0, p->mxRail+1);
993
- if( zLeftBranch ){
994
- char *zLeft = persistBranchName(p, zLeftBranch);
996
+ u16 aPriority[GR_MAX_RAIL];
997
+ int mxMatch = 0;
998
+ memset(aPriority, 0, (p->mxRail+1)*sizeof(aPriority[0]));
999
+ if( pLeftBranch ){
9951000
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
996
- if( pRow->zBranch==zLeft ){
997
- aPriority[pRow->iRail] |= 4;
1001
+ int iMatch = match_text(pLeftBranch, pRow->zBranch);
1002
+ if( iMatch>0 ){
1003
+ if( iMatch>10 ) iMatch = 10;
1004
+ aPriority[pRow->iRail] |= 1<<(iMatch+1);
1005
+ if( mxMatch<iMatch ) mxMatch = iMatch;
9981006
for(i=0; i<=p->mxRail; i++){
9991007
if( pRow->mergeIn[i] ) aPriority[i] |= 1;
10001008
}
10011009
if( pRow->mergeOut>=0 ) aPriority[pRow->mergeOut] |= 1;
10021010
}
@@ -1007,10 +1015,11 @@
10071015
}
10081016
}
10091017
}else{
10101018
j = 1;
10111019
aPriority[0] = 4;
1020
+ mxMatch = 1;
10121021
for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
10131022
if( pRow->iRail==0 ){
10141023
for(i=0; i<=p->mxRail; i++){
10151024
if( pRow->mergeIn[i] ) aPriority[i] |= 1;
10161025
}
@@ -1020,17 +1029,24 @@
10201029
}
10211030
10221031
#if 0
10231032
fprintf(stderr,"mergeRail: 0x%llx\n", p->mergeRail);
10241033
fprintf(stderr,"Priority:");
1025
- for(i=0; i<=p->mxRail; i++) fprintf(stderr," %d", aPriority[i]);
1034
+ for(i=0; i<=p->mxRail; i++){
1035
+ fprintf(stderr," %x.%x",
1036
+ aPriority[i]/4, aPriority[i]&3);
1037
+ }
10261038
fprintf(stderr,"\n");
10271039
#endif
10281040
10291041
j = 0;
1030
- for(i=0; i<=p->mxRail; i++){
1031
- if( aPriority[i]>=4 ) aMap[i] = j++;
1042
+ for(kk=4; kk<=1<<(mxMatch+1); kk*=2){
1043
+ for(i=0; i<=p->mxRail; i++){
1044
+ if( aPriority[i]>=kk && aPriority[i]<kk*2 ){
1045
+ aMap[i] = j++;
1046
+ }
1047
+ }
10321048
}
10331049
for(i=p->mxRail; i>=0; i--){
10341050
if( aPriority[i]==3 ) aMap[i] = j++;
10351051
}
10361052
for(i=0; i<=p->mxRail; i++){
10371053
--- src/graph.c
+++ src/graph.c
@@ -495,11 +495,15 @@
495 **
496 ** TIMELINE_DISJOINT: Omit descenders
497 ** TIMELINE_FILLGAPS: Use step-children
498 ** TIMELINE_XMERGE: Omit off-graph merge lines
499 */
500 void graph_finish(GraphContext *p, const char *zLeftBranch, u32 tmFlags){
 
 
 
 
501 GraphRow *pRow, *pDesc, *pDup, *pLoop, *pParent;
502 int i, j;
503 u64 mask;
504 int hasDup = 0; /* True if one or more isDup entries */
505 const char *zTrunk;
@@ -963,12 +967,12 @@
963 }
964 }
965
966 /*
967 ** Compute the rail mapping that tries to put the branch named
968 ** zLeftBranch at the left margin. Other branches that merge
969 ** with zLeftBranch are to the right with merge rails in between.
970 **
971 ** aMap[X]=Y means that the X-th rail is drawn as the Y-th rail.
972 **
973 ** Do not move rails around if there are timewarps, because that can
974 ** seriously mess up the display of timewarps. Timewarps should be
@@ -975,10 +979,11 @@
975 ** rare so this should not be a serious limitation to the algorithm.
976 */
977 aMap = p->aiRailMap;
978 for(i=0; i<=p->mxRail; i++) aMap[i] = i; /* Set up a default mapping */
979 if( nTimewarp==0 ){
 
980 /* Priority bits:
981 **
982 ** 0x04 The preferred branch
983 **
984 ** 0x02 A merge rail - a rail that contains merge lines into
@@ -986,17 +991,20 @@
986 ** is defined. This improves the display of r=BRANCH
987 ** options to /timeline.
988 **
989 ** 0x01 A rail that merges with the preferred branch
990 */
991 u8 aPriority[GR_MAX_RAIL];
992 memset(aPriority, 0, p->mxRail+1);
993 if( zLeftBranch ){
994 char *zLeft = persistBranchName(p, zLeftBranch);
995 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
996 if( pRow->zBranch==zLeft ){
997 aPriority[pRow->iRail] |= 4;
 
 
 
998 for(i=0; i<=p->mxRail; i++){
999 if( pRow->mergeIn[i] ) aPriority[i] |= 1;
1000 }
1001 if( pRow->mergeOut>=0 ) aPriority[pRow->mergeOut] |= 1;
1002 }
@@ -1007,10 +1015,11 @@
1007 }
1008 }
1009 }else{
1010 j = 1;
1011 aPriority[0] = 4;
 
1012 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
1013 if( pRow->iRail==0 ){
1014 for(i=0; i<=p->mxRail; i++){
1015 if( pRow->mergeIn[i] ) aPriority[i] |= 1;
1016 }
@@ -1020,17 +1029,24 @@
1020 }
1021
1022 #if 0
1023 fprintf(stderr,"mergeRail: 0x%llx\n", p->mergeRail);
1024 fprintf(stderr,"Priority:");
1025 for(i=0; i<=p->mxRail; i++) fprintf(stderr," %d", aPriority[i]);
 
 
 
1026 fprintf(stderr,"\n");
1027 #endif
1028
1029 j = 0;
1030 for(i=0; i<=p->mxRail; i++){
1031 if( aPriority[i]>=4 ) aMap[i] = j++;
 
 
 
 
1032 }
1033 for(i=p->mxRail; i>=0; i--){
1034 if( aPriority[i]==3 ) aMap[i] = j++;
1035 }
1036 for(i=0; i<=p->mxRail; i++){
1037
--- src/graph.c
+++ src/graph.c
@@ -495,11 +495,15 @@
495 **
496 ** TIMELINE_DISJOINT: Omit descenders
497 ** TIMELINE_FILLGAPS: Use step-children
498 ** TIMELINE_XMERGE: Omit off-graph merge lines
499 */
500 void graph_finish(
501 GraphContext *p, /* The graph to be laid out */
502 Matcher *pLeftBranch, /* Compares true for left-most branch */
503 u32 tmFlags /* TIMELINE flags */
504 ){
505 GraphRow *pRow, *pDesc, *pDup, *pLoop, *pParent;
506 int i, j;
507 u64 mask;
508 int hasDup = 0; /* True if one or more isDup entries */
509 const char *zTrunk;
@@ -963,12 +967,12 @@
967 }
968 }
969
970 /*
971 ** Compute the rail mapping that tries to put the branch named
972 ** pLeftBranch at the left margin. Other branches that merge
973 ** with pLeftBranch are to the right with merge rails in between.
974 **
975 ** aMap[X]=Y means that the X-th rail is drawn as the Y-th rail.
976 **
977 ** Do not move rails around if there are timewarps, because that can
978 ** seriously mess up the display of timewarps. Timewarps should be
@@ -975,10 +979,11 @@
979 ** rare so this should not be a serious limitation to the algorithm.
980 */
981 aMap = p->aiRailMap;
982 for(i=0; i<=p->mxRail; i++) aMap[i] = i; /* Set up a default mapping */
983 if( nTimewarp==0 ){
984 int kk;
985 /* Priority bits:
986 **
987 ** 0x04 The preferred branch
988 **
989 ** 0x02 A merge rail - a rail that contains merge lines into
@@ -986,17 +991,20 @@
991 ** is defined. This improves the display of r=BRANCH
992 ** options to /timeline.
993 **
994 ** 0x01 A rail that merges with the preferred branch
995 */
996 u16 aPriority[GR_MAX_RAIL];
997 int mxMatch = 0;
998 memset(aPriority, 0, (p->mxRail+1)*sizeof(aPriority[0]));
999 if( pLeftBranch ){
1000 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
1001 int iMatch = match_text(pLeftBranch, pRow->zBranch);
1002 if( iMatch>0 ){
1003 if( iMatch>10 ) iMatch = 10;
1004 aPriority[pRow->iRail] |= 1<<(iMatch+1);
1005 if( mxMatch<iMatch ) mxMatch = iMatch;
1006 for(i=0; i<=p->mxRail; i++){
1007 if( pRow->mergeIn[i] ) aPriority[i] |= 1;
1008 }
1009 if( pRow->mergeOut>=0 ) aPriority[pRow->mergeOut] |= 1;
1010 }
@@ -1007,10 +1015,11 @@
1015 }
1016 }
1017 }else{
1018 j = 1;
1019 aPriority[0] = 4;
1020 mxMatch = 1;
1021 for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
1022 if( pRow->iRail==0 ){
1023 for(i=0; i<=p->mxRail; i++){
1024 if( pRow->mergeIn[i] ) aPriority[i] |= 1;
1025 }
@@ -1020,17 +1029,24 @@
1029 }
1030
1031 #if 0
1032 fprintf(stderr,"mergeRail: 0x%llx\n", p->mergeRail);
1033 fprintf(stderr,"Priority:");
1034 for(i=0; i<=p->mxRail; i++){
1035 fprintf(stderr," %x.%x",
1036 aPriority[i]/4, aPriority[i]&3);
1037 }
1038 fprintf(stderr,"\n");
1039 #endif
1040
1041 j = 0;
1042 for(kk=4; kk<=1<<(mxMatch+1); kk*=2){
1043 for(i=0; i<=p->mxRail; i++){
1044 if( aPriority[i]>=kk && aPriority[i]<kk*2 ){
1045 aMap[i] = j++;
1046 }
1047 }
1048 }
1049 for(i=p->mxRail; i>=0; i--){
1050 if( aPriority[i]==3 ) aMap[i] = j++;
1051 }
1052 for(i=0; i<=p->mxRail; i++){
1053
+6
--- src/http.c
+++ src/http.c
@@ -768,10 +768,11 @@
768768
** a GET request where there is no PAYLOAD.
769769
**
770770
** Options:
771771
** --compress Use ZLIB compression on the payload
772772
** --mimetype TYPE Mimetype of the payload
773
+** --no-cert-verify Disable TLS cert verification
773774
** --out FILE Store the reply in FILE
774775
** -v Verbose output
775776
** --xfer PAYLOAD in a Fossil xfer protocol message
776777
*/
777778
void test_httpmsg_command(void){
@@ -783,10 +784,15 @@
783784
784785
zMimetype = find_option("mimetype",0,1);
785786
zOutFile = find_option("out","o",1);
786787
if( find_option("verbose","v",0)!=0 ) mHttpFlags |= HTTP_VERBOSE;
787788
if( find_option("compress",0,0)!=0 ) mHttpFlags &= ~HTTP_NOCOMPRESS;
789
+ if( find_option("no-cert-verify",0,0)!=0 ){
790
+ #ifdef FOSSIL_ENABLE_SSL
791
+ ssl_disable_cert_verification();
792
+ #endif
793
+ }
788794
if( find_option("xfer",0,0)!=0 ){
789795
mHttpFlags |= HTTP_USE_LOGIN;
790796
mHttpFlags &= ~HTTP_GENERIC;
791797
}
792798
verify_all_options();
793799
--- src/http.c
+++ src/http.c
@@ -768,10 +768,11 @@
768 ** a GET request where there is no PAYLOAD.
769 **
770 ** Options:
771 ** --compress Use ZLIB compression on the payload
772 ** --mimetype TYPE Mimetype of the payload
 
773 ** --out FILE Store the reply in FILE
774 ** -v Verbose output
775 ** --xfer PAYLOAD in a Fossil xfer protocol message
776 */
777 void test_httpmsg_command(void){
@@ -783,10 +784,15 @@
783
784 zMimetype = find_option("mimetype",0,1);
785 zOutFile = find_option("out","o",1);
786 if( find_option("verbose","v",0)!=0 ) mHttpFlags |= HTTP_VERBOSE;
787 if( find_option("compress",0,0)!=0 ) mHttpFlags &= ~HTTP_NOCOMPRESS;
 
 
 
 
 
788 if( find_option("xfer",0,0)!=0 ){
789 mHttpFlags |= HTTP_USE_LOGIN;
790 mHttpFlags &= ~HTTP_GENERIC;
791 }
792 verify_all_options();
793
--- src/http.c
+++ src/http.c
@@ -768,10 +768,11 @@
768 ** a GET request where there is no PAYLOAD.
769 **
770 ** Options:
771 ** --compress Use ZLIB compression on the payload
772 ** --mimetype TYPE Mimetype of the payload
773 ** --no-cert-verify Disable TLS cert verification
774 ** --out FILE Store the reply in FILE
775 ** -v Verbose output
776 ** --xfer PAYLOAD in a Fossil xfer protocol message
777 */
778 void test_httpmsg_command(void){
@@ -783,10 +784,15 @@
784
785 zMimetype = find_option("mimetype",0,1);
786 zOutFile = find_option("out","o",1);
787 if( find_option("verbose","v",0)!=0 ) mHttpFlags |= HTTP_VERBOSE;
788 if( find_option("compress",0,0)!=0 ) mHttpFlags &= ~HTTP_NOCOMPRESS;
789 if( find_option("no-cert-verify",0,0)!=0 ){
790 #ifdef FOSSIL_ENABLE_SSL
791 ssl_disable_cert_verification();
792 #endif
793 }
794 if( find_option("xfer",0,0)!=0 ){
795 mHttpFlags |= HTTP_USE_LOGIN;
796 mHttpFlags &= ~HTTP_GENERIC;
797 }
798 verify_all_options();
799
--- src/http_transport.c
+++ src/http_transport.c
@@ -103,11 +103,14 @@
103103
/*
104104
** Initialize a Blob to the name of the configured SSH command.
105105
*/
106106
void transport_ssh_command(Blob *p){
107107
char *zSsh; /* The base SSH command */
108
- zSsh = db_get("ssh-command", zDefaultSshCmd);
108
+ zSsh = g.zSshCmd;
109
+ if( zSsh==0 || zSsh[0]==0 ){
110
+ zSsh = db_get("ssh-command", zDefaultSshCmd);
111
+ }
109112
blob_init(p, zSsh, -1);
110113
}
111114
112115
/*
113116
** SSH initialization of the transport layer
114117
--- src/http_transport.c
+++ src/http_transport.c
@@ -103,11 +103,14 @@
103 /*
104 ** Initialize a Blob to the name of the configured SSH command.
105 */
106 void transport_ssh_command(Blob *p){
107 char *zSsh; /* The base SSH command */
108 zSsh = db_get("ssh-command", zDefaultSshCmd);
 
 
 
109 blob_init(p, zSsh, -1);
110 }
111
112 /*
113 ** SSH initialization of the transport layer
114
--- src/http_transport.c
+++ src/http_transport.c
@@ -103,11 +103,14 @@
103 /*
104 ** Initialize a Blob to the name of the configured SSH command.
105 */
106 void transport_ssh_command(Blob *p){
107 char *zSsh; /* The base SSH command */
108 zSsh = g.zSshCmd;
109 if( zSsh==0 || zSsh[0]==0 ){
110 zSsh = db_get("ssh-command", zDefaultSshCmd);
111 }
112 blob_init(p, zSsh, -1);
113 }
114
115 /*
116 ** SSH initialization of the transport layer
117
+466 -36
--- src/info.c
+++ src/info.c
@@ -320,10 +320,11 @@
320320
|TIMELINE_NOSCROLL
321321
|TIMELINE_XMERGE
322322
|TIMELINE_CHPICK,
323323
0, 0, 0, rid, rid2, 0);
324324
db_finalize(&q);
325
+ blob_reset(&sql);
325326
}
326327
327328
328329
/*
329330
** Append the difference between artifacts to the output
@@ -372,11 +373,13 @@
372373
const char *zNew, /* blob.uuid after change. NULL for deletes */
373374
const char *zOldName, /* Prior name. NULL if no name change. */
374375
DiffConfig *pCfg, /* Flags for text_diff() or NULL to omit all */
375376
int mperm /* executable or symlink permission for zNew */
376377
){
377
- @ <p>
378
+ @ <div class='file-change-line'><span>
379
+ /* Maintenance reminder: the extra level of SPAN is for
380
+ ** arranging new elements via JS. */
378381
if( !g.perm.Hyperlink ){
379382
if( zNew==0 ){
380383
@ Deleted %h(zName).
381384
}else if( zOld==0 ){
382385
@ Added %h(zName).
@@ -391,10 +394,11 @@
391394
@ %h(zName) became a regular file.
392395
}
393396
}else{
394397
@ Changes to %h(zName).
395398
}
399
+ @ </span></div>
396400
if( pCfg ){
397401
append_diff(zOld, zNew, pCfg);
398402
}
399403
}else{
400404
if( zOld && zNew ){
@@ -438,18 +442,20 @@
438442
@ Added %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\
439443
@ %h(zName)</a> version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
440444
}
441445
if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
442446
if( pCfg ){
447
+ @ </span></div>
443448
append_diff(zOld, zNew, pCfg);
444
- }else{
445
- @ &nbsp;&nbsp;
449
+ }else{
446450
@ %z(href("%R/fdiff?v1=%!S&v2=%!S",zOld,zNew))[diff]</a>
451
+ @ </span></div>
447452
}
453
+ }else{
454
+ @ </span></div>
448455
}
449456
}
450
- @ </p>
451457
}
452458
453459
/*
454460
** Generate javascript to enhance HTML diffs.
455461
*/
@@ -602,10 +608,277 @@
602608
www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL,
603609
0, 0, 0, rid, 0, 0);
604610
db_finalize(&q);
605611
style_finish_page();
606612
}
613
+
614
+/*
615
+** Render a web-page diff of the changes in the working check-out
616
+*/
617
+static void ckout_normal_diff(int vid){
618
+ int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
619
+ DiffConfig DCfg,*pCfg; /* Diff details */
620
+ const char *zW; /* The "w" query parameter */
621
+ int nChng; /* Number of changes */
622
+ Stmt q;
623
+
624
+ diffType = preferred_diff_type();
625
+ pCfg = construct_diff_flags(diffType, &DCfg);
626
+ nChng = db_int(0, "SELECT count(*) FROM vfile"
627
+ " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid);
628
+ if( nChng==0 ){
629
+ @ <p>No uncommitted changes</p>
630
+ return;
631
+ }
632
+ db_prepare(&q,
633
+ /* 0 1 2 3 4 5 6 */
634
+ "SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid"
635
+ " FROM vfile LEFT JOIN blob USING(rid)"
636
+ " WHERE vid=%d"
637
+ " AND (deleted OR chnged OR rid==0)"
638
+ " ORDER BY pathname /*scan*/",
639
+ vid
640
+ );
641
+ if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
642
+ DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
643
+ }else{
644
+ DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
645
+ }
646
+ @ <div class="sectionmenu info-changes-menu">
647
+ zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
648
+ if( diffType!=1 ){
649
+ @ %z(chref("button","%R?diff=1%s",zW))Unified&nbsp;Diff</a>
650
+ }
651
+ if( diffType!=2 ){
652
+ @ %z(chref("button","%R?diff=2%s",zW))Side-by-Side&nbsp;Diff</a>
653
+ }
654
+ if( diffType!=0 ){
655
+ if( *zW ){
656
+ @ %z(chref("button","%R?diff=%d",diffType))\
657
+ @ Show&nbsp;Whitespace&nbsp;Changes</a>
658
+ }else{
659
+ @ %z(chref("button","%R?diff=%d&w",diffType))Ignore&nbsp;Whitespace</a>
660
+ }
661
+ }
662
+ @ </div>
663
+ while( db_step(&q)==SQLITE_ROW ){
664
+ const char *zTreename = db_column_text(&q,0);
665
+ int isDeleted = db_column_int(&q, 1);
666
+ int isChnged = db_column_int(&q,2);
667
+ int isNew = db_column_int(&q,3);
668
+ int srcid = db_column_int(&q, 4);
669
+ int isLink = db_column_int(&q, 5);
670
+ const char *zUuid = db_column_text(&q, 6);
671
+ int showDiff = 1;
672
+
673
+ DCfg.diffFlags &= (~DIFF_FILE_MASK);
674
+ @ <div class='file-change-line'><span>
675
+ if( isDeleted ){
676
+ @ DELETED %h(zTreename)
677
+ DCfg.diffFlags |= DIFF_FILE_DELETED;
678
+ showDiff = 0;
679
+ }else if( file_access(zTreename, F_OK) ){
680
+ @ MISSING %h(zTreename)
681
+ showDiff = 0;
682
+ }else if( isNew ){
683
+ @ ADDED %h(zTreename)
684
+ DCfg.diffFlags |= DIFF_FILE_ADDED;
685
+ srcid = 0;
686
+ showDiff = 0;
687
+ }else if( isChnged==3 ){
688
+ @ ADDED_BY_MERGE %h(zTreename)
689
+ DCfg.diffFlags |= DIFF_FILE_ADDED;
690
+ srcid = 0;
691
+ showDiff = 0;
692
+ }else if( isChnged==5 ){
693
+ @ ADDED_BY_INTEGRATE %h(zTreename)
694
+ DCfg.diffFlags |= DIFF_FILE_ADDED;
695
+ srcid = 0;
696
+ showDiff = 0;
697
+ }else{
698
+ @ CHANGED %h(zTreename)
699
+ }
700
+ @ </span></div>
701
+ if( showDiff && pCfg ){
702
+ Blob old, new;
703
+ if( !isLink != !file_islink(zTreename) ){
704
+ @ %s(DIFF_CANNOT_COMPUTE_SYMLINK)
705
+ continue;
706
+ }
707
+ if( srcid>0 ){
708
+ content_get(srcid, &old);
709
+ pCfg->zLeftHash = zUuid;
710
+ }else{
711
+ blob_zero(&old);
712
+ pCfg->zLeftHash = 0;
713
+ }
714
+ blob_read_from_file(&new, zTreename, ExtFILE);
715
+ text_diff(&old, &new, cgi_output_blob(), pCfg);
716
+ blob_reset(&old);
717
+ blob_reset(&new);
718
+ }
719
+ }
720
+ db_finalize(&q);
721
+ append_diff_javascript(diffType);
722
+}
723
+
724
+/*
725
+** Render a web-page diff of the changes in the working check-out to
726
+** an external reference.
727
+*/
728
+static void ckout_external_base_diff(int vid, const char *zExBase){
729
+ int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
730
+ DiffConfig DCfg,*pCfg; /* Diff details */
731
+ const char *zW; /* The "w" query parameter */
732
+ Stmt q;
733
+
734
+ diffType = preferred_diff_type();
735
+ pCfg = construct_diff_flags(diffType, &DCfg);
736
+ db_prepare(&q,
737
+ "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", vid
738
+ );
739
+ if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
740
+ DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
741
+ }else{
742
+ DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
743
+ }
744
+ @ <div class="sectionmenu info-changes-menu">
745
+ zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
746
+ if( diffType!=1 ){
747
+ @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\
748
+ @ Unified&nbsp;Diff</a>
749
+ }
750
+ if( diffType!=2 ){
751
+ @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\
752
+ @ Side-by-Side&nbsp;Diff</a>
753
+ }
754
+ if( diffType!=0 ){
755
+ if( *zW ){
756
+ @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\
757
+ @ Show&nbsp;Whitespace&nbsp;Changes</a>
758
+ }else{
759
+ @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\
760
+ @ Ignore&nbsp;Whitespace</a>
761
+ }
762
+ }
763
+ @ </div>
764
+ while( db_step(&q)==SQLITE_ROW ){
765
+ const char *zFile; /* Name of file in the repository */
766
+ char *zLhs; /* Full name of left-hand side file */
767
+ char *zRhs; /* Full name of right-hand side file */
768
+ Blob rhs; /* Full text of RHS */
769
+ Blob lhs; /* Full text of LHS */
770
+
771
+ zFile = db_column_text(&q,0);
772
+ zLhs = mprintf("%s/%s", zExBase, zFile);
773
+ zRhs = mprintf("%s%s", g.zLocalRoot, zFile);
774
+ if( file_size(zLhs, ExtFILE)<0 ){
775
+ @ <div class='file-change-line'><span>
776
+ @ Missing from external baseline: %h(zFile)
777
+ @ </span></div>
778
+ }else{
779
+ blob_read_from_file(&lhs, zLhs, ExtFILE);
780
+ blob_read_from_file(&rhs, zRhs, ExtFILE);
781
+ if( blob_size(&lhs)!=blob_size(&rhs)
782
+ || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0
783
+ ){
784
+ @ <div class='file-change-line'><span>
785
+ @ Changes to %h(zFile)
786
+ @ </span></div>
787
+ if( pCfg ){
788
+ char *zFullFN;
789
+ char *zHexFN;
790
+ zFullFN = file_canonical_name_dup(zLhs);
791
+ zHexFN = mprintf("x%H", zFullFN);
792
+ fossil_free(zFullFN);
793
+ pCfg->zLeftHash = zHexFN;
794
+ text_diff(&lhs, &rhs, cgi_output_blob(), pCfg);
795
+ pCfg->zLeftHash = 0;
796
+ fossil_free(zHexFN);
797
+ }
798
+ }
799
+ blob_reset(&lhs);
800
+ blob_reset(&rhs);
801
+ }
802
+ fossil_free(zLhs);
803
+ fossil_free(zRhs);
804
+ }
805
+ db_finalize(&q);
806
+ append_diff_javascript(diffType);
807
+}
808
+
809
+/*
810
+** WEBPAGE: ckout
811
+**
812
+** Show information about the current checkout. This page only functions
813
+** if the web server is run on a loopback interface (in other words, was
814
+** started using "fossil ui" or similar) from within an open check-out.
815
+**
816
+** If the "exbase=PATH" query parameter is provided, then the diff shown
817
+** uses the files in PATH as the baseline. This is the same as using
818
+** the "--from PATH" argument to the "fossil diff" command-line. In fact,
819
+** when using "fossil ui --from PATH", the --from argument becomes the value
820
+** of the exbase query parameter for the start page. Note that if PATH
821
+** is a pure hexadecimal string, it is decoded first before being used as
822
+** the pathname. Real pathnames should contain at least one directory
823
+** separator character.
824
+**
825
+** Other query parameters related to diffs are also accepted.
826
+*/
827
+void ckout_page(void){
828
+ int vid;
829
+ const char *zHome; /* Home directory */
830
+ int nHome;
831
+ const char *zExBase;
832
+ char *zHostname;
833
+ char *zCwd;
834
+
835
+ if( !cgi_is_loopback(g.zIpAddr) || !db_open_local(0) ){
836
+ cgi_redirectf("%R/home");
837
+ return;
838
+ }
839
+ file_chdir(g.zLocalRoot, 0);
840
+ vid = db_lget_int("checkout", 0);
841
+ db_unprotect(PROTECT_ALL);
842
+ vfile_check_signature(vid, CKSIG_ENOTFILE);
843
+ db_protect_pop();
844
+ style_set_current_feature("vinfo");
845
+ zHostname = fossil_hostname();
846
+ zCwd = file_getcwd(0,0);
847
+ zHome = fossil_getenv("HOME");
848
+ if( zHome ){
849
+ nHome = (int)strlen(zHome);
850
+ if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){
851
+ zCwd = mprintf("~%s", zCwd+nHome);
852
+ }
853
+ }else{
854
+ nHome = 0;
855
+ }
856
+ if( zHostname ){
857
+ style_header("Checkout Status: %h on %h", zCwd, zHostname);
858
+ }else{
859
+ style_header("Checkout Status: %h", zCwd);
860
+ }
861
+ render_checkin_context(vid, 0, 0, 0);
862
+ @ <hr>
863
+ zExBase = P("exbase");
864
+ if( zExBase && zExBase[0] ){
865
+ char *zPath = decode16_dup(zExBase);
866
+ char *zCBase = file_canonical_name_dup(zPath?zPath:zExBase);
867
+ if( nHome && strncmp(zCBase, zHome, nHome)==0 && zCBase[nHome]=='/' ){
868
+ @ <p>Using external baseline: ~%h(zCBase+nHome)</p>
869
+ }else{
870
+ @ <p>Using external baseline: %h(zCBase)</p>
871
+ }
872
+ ckout_external_base_diff(vid, zCBase);
873
+ fossil_free(zCBase);
874
+ fossil_free(zPath);
875
+ }else{
876
+ ckout_normal_diff(vid);
877
+ }
878
+ style_finish_page();
879
+}
607880
608881
/*
609882
** WEBPAGE: vinfo
610883
** WEBPAGE: ci
611884
** URL: /ci/ARTIFACTID
@@ -628,11 +901,10 @@
628901
const char *zParent; /* Hash of the parent check-in (if any) */
629902
const char *zRe; /* regex parameter */
630903
ReCompiled *pRe = 0; /* regex */
631904
const char *zW; /* URL param for ignoring whitespace */
632905
const char *zPage = "vinfo"; /* Page that shows diffs */
633
- const char *zPageHide = "ci"; /* Page that hides diffs */
634906
const char *zBrName; /* Branch name */
635907
DiffConfig DCfg,*pCfg; /* Type of diff */
636908
637909
login_check_credentials();
638910
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
@@ -898,21 +1170,17 @@
8981170
@ <div class="sectionmenu info-changes-menu">
8991171
/* ^^^ .info-changes-menu is used by diff scroll sync */
9001172
pCfg = construct_diff_flags(diffType, &DCfg);
9011173
DCfg.pRe = pRe;
9021174
zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
903
- if( diffType!=0 ){
904
- @ %z(chref("button","%R/%s/%T?diff=0",zPageHide,zName))\
905
- @ Hide&nbsp;Diffs</a>
906
- }
9071175
if( diffType!=1 ){
9081176
@ %z(chref("button","%R/%s/%T?diff=1%s",zPage,zName,zW))\
909
- @ Unified&nbsp;Diffs</a>
1177
+ @ Unified&nbsp;Diff</a>
9101178
}
9111179
if( diffType!=2 ){
9121180
@ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
913
- @ Side-by-Side&nbsp;Diffs</a>
1181
+ @ Side-by-Side&nbsp;Diff</a>
9141182
}
9151183
if( diffType!=0 ){
9161184
if( *zW ){
9171185
@ %z(chref("button","%R/%s/%T",zPage,zName))
9181186
@ Show&nbsp;Whitespace&nbsp;Changes</a>
@@ -1268,13 +1536,10 @@
12681536
cgi_check_for_malice();
12691537
style_set_current_feature("vdiff");
12701538
if( zBranch==0 ){
12711539
style_submenu_element("Path", "%R/timeline?me=%T&you=%T", zFrom, zTo);
12721540
}
1273
- if( diffType!=0 ){
1274
- style_submenu_element("Hide Diff", "%R/vdiff?diff=0&%b%b", &qp, &qpGlob);
1275
- }
12761541
if( diffType!=2 ){
12771542
style_submenu_element("Side-by-Side Diff", "%R/vdiff?diff=2&%b%b", &qp,
12781543
&qpGlob);
12791544
}
12801545
if( diffType!=1 ) {
@@ -1668,11 +1933,11 @@
16681933
tag_private_status(rid);
16691934
}
16701935
db_finalize(&q);
16711936
if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d AND tagid=%d",
16721937
rid, TAG_CLUSTER) ){
1673
- @ Cluster
1938
+ @ Cluster %z(href("%R/info/%S",zUuid))%S(zUuid)</a>.
16741939
cnt++;
16751940
}
16761941
if( cnt==0 ){
16771942
@ Unrecognized artifact
16781943
if( pDownloadName && blob_size(pDownloadName)==0 ){
@@ -1933,10 +2198,16 @@
19332198
** WEBPAGE: jchunk hidden
19342199
** URL: /jchunk/HASH?from=N&to=M
19352200
**
19362201
** Return lines of text from a file as a JSON array - one entry in the
19372202
** array for each line of text.
2203
+**
2204
+** The HASH is normally a sha1 or sha3 hash that identifies an artifact
2205
+** in the BLOB table of the database. However, if HASH starts with an "x"
2206
+** and is followed by valid hexadecimal, and if we are running in a
2207
+** "fossil ui" situation (locally and with privilege), then decode the hex
2208
+** into a filename and read the file content from that name.
19382209
**
19392210
** **Warning:** This is an internal-use-only interface that is subject to
19402211
** change at any moment. External application should not use this interface
19412212
** since the application will break when this interface changes, and this
19422213
** interface will undoubtedly change.
@@ -1948,10 +2219,11 @@
19482219
** ajax_route_error().
19492220
*/
19502221
void jchunk_page(void){
19512222
int rid = 0;
19522223
const char *zName = PD("name", "");
2224
+ int nName = (int)(strlen(zName)&0x7fffffff);
19532225
int iFrom = atoi(PD("from","0"));
19542226
int iTo = atoi(PD("to","0"));
19552227
int ln;
19562228
int go = 1;
19572229
const char *zSep;
@@ -1968,36 +2240,57 @@
19682240
cgi_check_for_malice();
19692241
if( !g.perm.Read ){
19702242
ajax_route_error(403, "Access requires Read permissions.");
19712243
return;
19722244
}
1973
-#if 1
1974
- /* Re-enable this block once this code is integrated somewhere into
1975
- the UI. */
1976
- rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
1977
- if( rid==0 ){
1978
- ajax_route_error(404, "Unknown artifact: %h", zName);
1979
- return;
1980
- }
1981
-#else
1982
- /* This impl is only to simplify "manual" testing via the JS
1983
- console. */
1984
- rid = symbolic_name_to_rid(zName, "*");
1985
- if( rid==0 ){
1986
- ajax_route_error(404, "Unknown artifact: %h", zName);
1987
- return;
1988
- }else if( rid<0 ){
1989
- ajax_route_error(418, "Ambiguous artifact name: %h", zName);
1990
- return;
1991
- }
1992
-#endif
19932245
if( iFrom<1 || iTo<iFrom ){
19942246
ajax_route_error(500, "Invalid line range from=%d, to=%d.",
19952247
iFrom, iTo);
19962248
return;
19972249
}
1998
- content_get(rid, &content);
2250
+ if( zName[0]=='x'
2251
+ && ((nName-1)&1)==0
2252
+ && validate16(&zName[1],nName-1)
2253
+ && g.perm.Admin
2254
+ && cgi_is_loopback(g.zIpAddr)
2255
+ && db_open_local(0)
2256
+ ){
2257
+ /* Treat the HASH as a hex-encoded filename */
2258
+ int n = (nName-1)/2;
2259
+ char *zFN = fossil_malloc(n+1);
2260
+ decode16((const u8*)&zName[1], (u8*)zFN, nName-1);
2261
+ zFN[n] = 0;
2262
+ if( file_size(zFN, ExtFILE)<0 ){
2263
+ blob_zero(&content);
2264
+ }else{
2265
+ blob_read_from_file(&content, zFN, ExtFILE);
2266
+ }
2267
+ fossil_free(zFN);
2268
+ }else{
2269
+ /* Treat the HASH as an artifact hash matching BLOB.UUID */
2270
+#if 1
2271
+ /* Re-enable this block once this code is integrated somewhere into
2272
+ the UI. */
2273
+ rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
2274
+ if( rid==0 ){
2275
+ ajax_route_error(404, "Unknown artifact: %h", zName);
2276
+ return;
2277
+ }
2278
+#else
2279
+ /* This impl is only to simplify "manual" testing via the JS
2280
+ console. */
2281
+ rid = symbolic_name_to_rid(zName, "*");
2282
+ if( rid==0 ){
2283
+ ajax_route_error(404, "Unknown artifact: %h", zName);
2284
+ return;
2285
+ }else if( rid<0 ){
2286
+ ajax_route_error(418, "Ambiguous artifact name: %h", zName);
2287
+ return;
2288
+ }
2289
+#endif
2290
+ content_get(rid, &content);
2291
+ }
19992292
g.isConst = 1;
20002293
cgi_set_content_type("application/json");
20012294
ln = 0;
20022295
while( go && ln<iFrom ){
20032296
go = blob_line(&content, &line);
@@ -2875,10 +3168,143 @@
28753168
ticket_output_change_artifact(pTktChng, 0, 1, 0);
28763169
manifest_destroy(pTktChng);
28773170
style_finish_page();
28783171
}
28793172
3173
+/*
3174
+** rid is a cluster. Paint a page that contains detailed information
3175
+** about that cluster.
3176
+*/
3177
+static void cluster_info(int rid, const char *zName){
3178
+ Manifest *pCluster;
3179
+ int i;
3180
+ Blob where = BLOB_INITIALIZER;
3181
+ Blob unks = BLOB_INITIALIZER;
3182
+ Stmt q;
3183
+ char *zSha1Bg;
3184
+ char *zSha3Bg;
3185
+ int badRid = 0;
3186
+ int rcvid;
3187
+ int hashClr = PB("hclr");
3188
+ const char *zDate;
3189
+
3190
+ pCluster = manifest_get(rid, CFTYPE_CLUSTER, 0);
3191
+ if( pCluster==0 ){
3192
+ artifact_page();
3193
+ return;
3194
+ }
3195
+ style_header("Cluster %S", zName);
3196
+ rcvid = db_int(0, "SELECT rcvid FROM blob WHERE rid=%d", rid);
3197
+ if( rcvid==0 ){
3198
+ zDate = 0;
3199
+ }else{
3200
+ zDate = db_text(0, "SELECT datetime(mtime) FROM rcvfrom WHERE rcvid=%d",
3201
+ rcvid);
3202
+ }
3203
+ @ <p>Artifact %z(href("%R/artifact/%h",zName))%S(zName)</a> is a cluster
3204
+ @ with %d(pCluster->nCChild) entries
3205
+ if( g.perm.Admin ){
3206
+ @ received <a href="%R/rcvfrom?rcvid=%d(rcvid)">%h(zDate)</a>:
3207
+ }else{
3208
+ @ received %h(zDate):
3209
+ }
3210
+ blob_appendf(&where,"IN(0");
3211
+ for(i=0; i<pCluster->nCChild; i++){
3212
+ int rid = fast_uuid_to_rid(pCluster->azCChild[i]);
3213
+ if( rid ){
3214
+ blob_appendf(&where,",%d", rid);
3215
+ }else{
3216
+ if( blob_size(&unks)>0 ) blob_append_char(&unks, ',');
3217
+ badRid++;
3218
+ blob_append_sql(&unks,"(%d,%Q)",-badRid,pCluster->azCChild[i]);
3219
+ }
3220
+ }
3221
+ blob_append_char(&where,')');
3222
+ describe_artifacts(blob_str(&where));
3223
+ blob_reset(&where);
3224
+ if( badRid>0 ){
3225
+ db_multi_exec(
3226
+ "WITH unks(rx,hx) AS (VALUES %s)\n"
3227
+ "INSERT INTO description(rid,uuid,type,summary) "
3228
+ " SELECT rx, hx, 'phantom', '' FROM unks;",
3229
+ blob_sql_text(&unks)
3230
+ );
3231
+ }
3232
+ blob_reset(&unks);
3233
+ db_prepare(&q,
3234
+ "SELECT rid, uuid, summary, isPrivate, type='phantom', rcvid, ref"
3235
+ " FROM description ORDER BY uuid"
3236
+ );
3237
+ if( skin_detail_boolean("white-foreground") ){
3238
+ zSha1Bg = "#714417";
3239
+ zSha3Bg = "#177117";
3240
+ }else{
3241
+ zSha1Bg = "#ebffb0";
3242
+ zSha3Bg = "#b0ffb0";
3243
+ }
3244
+ @ <table cellpadding="2" cellspacing="0" border="1">
3245
+ if( g.perm.Admin ){
3246
+ @ <tr><th>RID<th>Hash<th>Rcvid<th>Description<th>Ref<th>Remarks
3247
+ }else{
3248
+ @ <tr><th>RID<th>Hash<th>Description<th>Ref<th>Remarks
3249
+ }
3250
+ while( db_step(&q)==SQLITE_ROW ){
3251
+ int rid = db_column_int(&q,0);
3252
+ const char *zUuid = db_column_text(&q, 1);
3253
+ const char *zDesc = db_column_text(&q, 2);
3254
+ int isPriv = db_column_int(&q,3);
3255
+ int isPhantom = db_column_int(&q,4);
3256
+ const char *zRef = db_column_text(&q,6);
3257
+ if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){
3258
+ /* Don't show private artifacts to users without Private (x) permission */
3259
+ continue;
3260
+ }
3261
+ if( rid<=0 ){
3262
+ @ <tr><td>&nbsp;</td>
3263
+ }else if( hashClr ){
3264
+ const char *zClr = db_column_bytes(&q,1)>40 ? zSha3Bg : zSha1Bg;
3265
+ @ <tr style='background-color:%s(zClr);'><td align="right">%d(rid)</td>
3266
+ }else{
3267
+ @ <tr><td align="right">%d(rid)</td>
3268
+ }
3269
+ if( rid<=0 ){
3270
+ @ <td>&nbsp;%S(zUuid)&nbsp;</td>
3271
+ }else{
3272
+ @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
3273
+ }
3274
+ if( g.perm.Admin ){
3275
+ int rcvid = db_column_int(&q,5);
3276
+ if( rcvid<=0 ){
3277
+ @ <td>&nbsp;
3278
+ }else{
3279
+ @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%d(rcvid)</a>
3280
+ }
3281
+ }
3282
+ @ <td align="left">%h(zDesc)</td>
3283
+ if( zRef && zRef[0] ){
3284
+ @ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a>
3285
+ }else{
3286
+ @ <td>&nbsp;
3287
+ }
3288
+ if( isPriv || isPhantom ){
3289
+ if( isPriv==0 ){
3290
+ @ <td>phantom</td>
3291
+ }else if( isPhantom==0 ){
3292
+ @ <td>private</td>
3293
+ }else{
3294
+ @ <td>private,phantom</td>
3295
+ }
3296
+ }else{
3297
+ @ <td>&nbsp;
3298
+ }
3299
+ @ </tr>
3300
+ }
3301
+ @ </table>
3302
+ db_finalize(&q);
3303
+ style_finish_page();
3304
+}
3305
+
28803306
28813307
/*
28823308
** WEBPAGE: info
28833309
** URL: info/NAME
28843310
**
@@ -2963,10 +3389,14 @@
29633389
if( db_exists("SELECT 1 FROM plink WHERE pid=%d", rid) ){
29643390
ci_page();
29653391
}else
29663392
if( db_exists("SELECT 1 FROM attachment WHERE attachid=%d", rid) ){
29673393
ainfo_page();
3394
+ }else
3395
+ if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d AND tagid=%d",
3396
+ rid, TAG_CLUSTER) ){
3397
+ cluster_info(rid, zName);
29683398
}else
29693399
{
29703400
artifact_page();
29713401
}
29723402
}
29733403
--- src/info.c
+++ src/info.c
@@ -320,10 +320,11 @@
320 |TIMELINE_NOSCROLL
321 |TIMELINE_XMERGE
322 |TIMELINE_CHPICK,
323 0, 0, 0, rid, rid2, 0);
324 db_finalize(&q);
 
325 }
326
327
328 /*
329 ** Append the difference between artifacts to the output
@@ -372,11 +373,13 @@
372 const char *zNew, /* blob.uuid after change. NULL for deletes */
373 const char *zOldName, /* Prior name. NULL if no name change. */
374 DiffConfig *pCfg, /* Flags for text_diff() or NULL to omit all */
375 int mperm /* executable or symlink permission for zNew */
376 ){
377 @ <p>
 
 
378 if( !g.perm.Hyperlink ){
379 if( zNew==0 ){
380 @ Deleted %h(zName).
381 }else if( zOld==0 ){
382 @ Added %h(zName).
@@ -391,10 +394,11 @@
391 @ %h(zName) became a regular file.
392 }
393 }else{
394 @ Changes to %h(zName).
395 }
 
396 if( pCfg ){
397 append_diff(zOld, zNew, pCfg);
398 }
399 }else{
400 if( zOld && zNew ){
@@ -438,18 +442,20 @@
438 @ Added %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\
439 @ %h(zName)</a> version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
440 }
441 if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
442 if( pCfg ){
 
443 append_diff(zOld, zNew, pCfg);
444 }else{
445 @ &nbsp;&nbsp;
446 @ %z(href("%R/fdiff?v1=%!S&v2=%!S",zOld,zNew))[diff]</a>
 
447 }
 
 
448 }
449 }
450 @ </p>
451 }
452
453 /*
454 ** Generate javascript to enhance HTML diffs.
455 */
@@ -602,10 +608,277 @@
602 www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL,
603 0, 0, 0, rid, 0, 0);
604 db_finalize(&q);
605 style_finish_page();
606 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
607
608 /*
609 ** WEBPAGE: vinfo
610 ** WEBPAGE: ci
611 ** URL: /ci/ARTIFACTID
@@ -628,11 +901,10 @@
628 const char *zParent; /* Hash of the parent check-in (if any) */
629 const char *zRe; /* regex parameter */
630 ReCompiled *pRe = 0; /* regex */
631 const char *zW; /* URL param for ignoring whitespace */
632 const char *zPage = "vinfo"; /* Page that shows diffs */
633 const char *zPageHide = "ci"; /* Page that hides diffs */
634 const char *zBrName; /* Branch name */
635 DiffConfig DCfg,*pCfg; /* Type of diff */
636
637 login_check_credentials();
638 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
@@ -898,21 +1170,17 @@
898 @ <div class="sectionmenu info-changes-menu">
899 /* ^^^ .info-changes-menu is used by diff scroll sync */
900 pCfg = construct_diff_flags(diffType, &DCfg);
901 DCfg.pRe = pRe;
902 zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
903 if( diffType!=0 ){
904 @ %z(chref("button","%R/%s/%T?diff=0",zPageHide,zName))\
905 @ Hide&nbsp;Diffs</a>
906 }
907 if( diffType!=1 ){
908 @ %z(chref("button","%R/%s/%T?diff=1%s",zPage,zName,zW))\
909 @ Unified&nbsp;Diffs</a>
910 }
911 if( diffType!=2 ){
912 @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
913 @ Side-by-Side&nbsp;Diffs</a>
914 }
915 if( diffType!=0 ){
916 if( *zW ){
917 @ %z(chref("button","%R/%s/%T",zPage,zName))
918 @ Show&nbsp;Whitespace&nbsp;Changes</a>
@@ -1268,13 +1536,10 @@
1268 cgi_check_for_malice();
1269 style_set_current_feature("vdiff");
1270 if( zBranch==0 ){
1271 style_submenu_element("Path", "%R/timeline?me=%T&you=%T", zFrom, zTo);
1272 }
1273 if( diffType!=0 ){
1274 style_submenu_element("Hide Diff", "%R/vdiff?diff=0&%b%b", &qp, &qpGlob);
1275 }
1276 if( diffType!=2 ){
1277 style_submenu_element("Side-by-Side Diff", "%R/vdiff?diff=2&%b%b", &qp,
1278 &qpGlob);
1279 }
1280 if( diffType!=1 ) {
@@ -1668,11 +1933,11 @@
1668 tag_private_status(rid);
1669 }
1670 db_finalize(&q);
1671 if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d AND tagid=%d",
1672 rid, TAG_CLUSTER) ){
1673 @ Cluster
1674 cnt++;
1675 }
1676 if( cnt==0 ){
1677 @ Unrecognized artifact
1678 if( pDownloadName && blob_size(pDownloadName)==0 ){
@@ -1933,10 +2198,16 @@
1933 ** WEBPAGE: jchunk hidden
1934 ** URL: /jchunk/HASH?from=N&to=M
1935 **
1936 ** Return lines of text from a file as a JSON array - one entry in the
1937 ** array for each line of text.
 
 
 
 
 
 
1938 **
1939 ** **Warning:** This is an internal-use-only interface that is subject to
1940 ** change at any moment. External application should not use this interface
1941 ** since the application will break when this interface changes, and this
1942 ** interface will undoubtedly change.
@@ -1948,10 +2219,11 @@
1948 ** ajax_route_error().
1949 */
1950 void jchunk_page(void){
1951 int rid = 0;
1952 const char *zName = PD("name", "");
 
1953 int iFrom = atoi(PD("from","0"));
1954 int iTo = atoi(PD("to","0"));
1955 int ln;
1956 int go = 1;
1957 const char *zSep;
@@ -1968,36 +2240,57 @@
1968 cgi_check_for_malice();
1969 if( !g.perm.Read ){
1970 ajax_route_error(403, "Access requires Read permissions.");
1971 return;
1972 }
1973 #if 1
1974 /* Re-enable this block once this code is integrated somewhere into
1975 the UI. */
1976 rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
1977 if( rid==0 ){
1978 ajax_route_error(404, "Unknown artifact: %h", zName);
1979 return;
1980 }
1981 #else
1982 /* This impl is only to simplify "manual" testing via the JS
1983 console. */
1984 rid = symbolic_name_to_rid(zName, "*");
1985 if( rid==0 ){
1986 ajax_route_error(404, "Unknown artifact: %h", zName);
1987 return;
1988 }else if( rid<0 ){
1989 ajax_route_error(418, "Ambiguous artifact name: %h", zName);
1990 return;
1991 }
1992 #endif
1993 if( iFrom<1 || iTo<iFrom ){
1994 ajax_route_error(500, "Invalid line range from=%d, to=%d.",
1995 iFrom, iTo);
1996 return;
1997 }
1998 content_get(rid, &content);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1999 g.isConst = 1;
2000 cgi_set_content_type("application/json");
2001 ln = 0;
2002 while( go && ln<iFrom ){
2003 go = blob_line(&content, &line);
@@ -2875,10 +3168,143 @@
2875 ticket_output_change_artifact(pTktChng, 0, 1, 0);
2876 manifest_destroy(pTktChng);
2877 style_finish_page();
2878 }
2879
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2880
2881 /*
2882 ** WEBPAGE: info
2883 ** URL: info/NAME
2884 **
@@ -2963,10 +3389,14 @@
2963 if( db_exists("SELECT 1 FROM plink WHERE pid=%d", rid) ){
2964 ci_page();
2965 }else
2966 if( db_exists("SELECT 1 FROM attachment WHERE attachid=%d", rid) ){
2967 ainfo_page();
 
 
 
 
2968 }else
2969 {
2970 artifact_page();
2971 }
2972 }
2973
--- src/info.c
+++ src/info.c
@@ -320,10 +320,11 @@
320 |TIMELINE_NOSCROLL
321 |TIMELINE_XMERGE
322 |TIMELINE_CHPICK,
323 0, 0, 0, rid, rid2, 0);
324 db_finalize(&q);
325 blob_reset(&sql);
326 }
327
328
329 /*
330 ** Append the difference between artifacts to the output
@@ -372,11 +373,13 @@
373 const char *zNew, /* blob.uuid after change. NULL for deletes */
374 const char *zOldName, /* Prior name. NULL if no name change. */
375 DiffConfig *pCfg, /* Flags for text_diff() or NULL to omit all */
376 int mperm /* executable or symlink permission for zNew */
377 ){
378 @ <div class='file-change-line'><span>
379 /* Maintenance reminder: the extra level of SPAN is for
380 ** arranging new elements via JS. */
381 if( !g.perm.Hyperlink ){
382 if( zNew==0 ){
383 @ Deleted %h(zName).
384 }else if( zOld==0 ){
385 @ Added %h(zName).
@@ -391,10 +394,11 @@
394 @ %h(zName) became a regular file.
395 }
396 }else{
397 @ Changes to %h(zName).
398 }
399 @ </span></div>
400 if( pCfg ){
401 append_diff(zOld, zNew, pCfg);
402 }
403 }else{
404 if( zOld && zNew ){
@@ -438,18 +442,20 @@
442 @ Added %z(href("%R/finfo?name=%T&m=%!S&ci=%!S",zName,zNew,zCkin))\
443 @ %h(zName)</a> version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
444 }
445 if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
446 if( pCfg ){
447 @ </span></div>
448 append_diff(zOld, zNew, pCfg);
449 }else{
 
450 @ %z(href("%R/fdiff?v1=%!S&v2=%!S",zOld,zNew))[diff]</a>
451 @ </span></div>
452 }
453 }else{
454 @ </span></div>
455 }
456 }
 
457 }
458
459 /*
460 ** Generate javascript to enhance HTML diffs.
461 */
@@ -602,10 +608,277 @@
608 www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH|TIMELINE_NOSCROLL,
609 0, 0, 0, rid, 0, 0);
610 db_finalize(&q);
611 style_finish_page();
612 }
613
614 /*
615 ** Render a web-page diff of the changes in the working check-out
616 */
617 static void ckout_normal_diff(int vid){
618 int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
619 DiffConfig DCfg,*pCfg; /* Diff details */
620 const char *zW; /* The "w" query parameter */
621 int nChng; /* Number of changes */
622 Stmt q;
623
624 diffType = preferred_diff_type();
625 pCfg = construct_diff_flags(diffType, &DCfg);
626 nChng = db_int(0, "SELECT count(*) FROM vfile"
627 " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid);
628 if( nChng==0 ){
629 @ <p>No uncommitted changes</p>
630 return;
631 }
632 db_prepare(&q,
633 /* 0 1 2 3 4 5 6 */
634 "SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid"
635 " FROM vfile LEFT JOIN blob USING(rid)"
636 " WHERE vid=%d"
637 " AND (deleted OR chnged OR rid==0)"
638 " ORDER BY pathname /*scan*/",
639 vid
640 );
641 if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
642 DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
643 }else{
644 DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
645 }
646 @ <div class="sectionmenu info-changes-menu">
647 zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
648 if( diffType!=1 ){
649 @ %z(chref("button","%R?diff=1%s",zW))Unified&nbsp;Diff</a>
650 }
651 if( diffType!=2 ){
652 @ %z(chref("button","%R?diff=2%s",zW))Side-by-Side&nbsp;Diff</a>
653 }
654 if( diffType!=0 ){
655 if( *zW ){
656 @ %z(chref("button","%R?diff=%d",diffType))\
657 @ Show&nbsp;Whitespace&nbsp;Changes</a>
658 }else{
659 @ %z(chref("button","%R?diff=%d&w",diffType))Ignore&nbsp;Whitespace</a>
660 }
661 }
662 @ </div>
663 while( db_step(&q)==SQLITE_ROW ){
664 const char *zTreename = db_column_text(&q,0);
665 int isDeleted = db_column_int(&q, 1);
666 int isChnged = db_column_int(&q,2);
667 int isNew = db_column_int(&q,3);
668 int srcid = db_column_int(&q, 4);
669 int isLink = db_column_int(&q, 5);
670 const char *zUuid = db_column_text(&q, 6);
671 int showDiff = 1;
672
673 DCfg.diffFlags &= (~DIFF_FILE_MASK);
674 @ <div class='file-change-line'><span>
675 if( isDeleted ){
676 @ DELETED %h(zTreename)
677 DCfg.diffFlags |= DIFF_FILE_DELETED;
678 showDiff = 0;
679 }else if( file_access(zTreename, F_OK) ){
680 @ MISSING %h(zTreename)
681 showDiff = 0;
682 }else if( isNew ){
683 @ ADDED %h(zTreename)
684 DCfg.diffFlags |= DIFF_FILE_ADDED;
685 srcid = 0;
686 showDiff = 0;
687 }else if( isChnged==3 ){
688 @ ADDED_BY_MERGE %h(zTreename)
689 DCfg.diffFlags |= DIFF_FILE_ADDED;
690 srcid = 0;
691 showDiff = 0;
692 }else if( isChnged==5 ){
693 @ ADDED_BY_INTEGRATE %h(zTreename)
694 DCfg.diffFlags |= DIFF_FILE_ADDED;
695 srcid = 0;
696 showDiff = 0;
697 }else{
698 @ CHANGED %h(zTreename)
699 }
700 @ </span></div>
701 if( showDiff && pCfg ){
702 Blob old, new;
703 if( !isLink != !file_islink(zTreename) ){
704 @ %s(DIFF_CANNOT_COMPUTE_SYMLINK)
705 continue;
706 }
707 if( srcid>0 ){
708 content_get(srcid, &old);
709 pCfg->zLeftHash = zUuid;
710 }else{
711 blob_zero(&old);
712 pCfg->zLeftHash = 0;
713 }
714 blob_read_from_file(&new, zTreename, ExtFILE);
715 text_diff(&old, &new, cgi_output_blob(), pCfg);
716 blob_reset(&old);
717 blob_reset(&new);
718 }
719 }
720 db_finalize(&q);
721 append_diff_javascript(diffType);
722 }
723
724 /*
725 ** Render a web-page diff of the changes in the working check-out to
726 ** an external reference.
727 */
728 static void ckout_external_base_diff(int vid, const char *zExBase){
729 int diffType; /* 0: no diff, 1: unified, 2: side-by-side */
730 DiffConfig DCfg,*pCfg; /* Diff details */
731 const char *zW; /* The "w" query parameter */
732 Stmt q;
733
734 diffType = preferred_diff_type();
735 pCfg = construct_diff_flags(diffType, &DCfg);
736 db_prepare(&q,
737 "SELECT pathname FROM vfile WHERE vid=%d ORDER BY pathname", vid
738 );
739 if( DCfg.diffFlags & DIFF_SIDEBYSIDE ){
740 DCfg.diffFlags |= DIFF_HTML | DIFF_NOTTOOBIG;
741 }else{
742 DCfg.diffFlags |= DIFF_LINENO | DIFF_HTML | DIFF_NOTTOOBIG;
743 }
744 @ <div class="sectionmenu info-changes-menu">
745 zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
746 if( diffType!=1 ){
747 @ %z(chref("button","%R?diff=1&exbase=%h%s",zExBase,zW))\
748 @ Unified&nbsp;Diff</a>
749 }
750 if( diffType!=2 ){
751 @ %z(chref("button","%R?diff=2&exbase=%h%s",zExBase,zW))\
752 @ Side-by-Side&nbsp;Diff</a>
753 }
754 if( diffType!=0 ){
755 if( *zW ){
756 @ %z(chref("button","%R?diff=%d&exbase=%h",diffType,zExBase))\
757 @ Show&nbsp;Whitespace&nbsp;Changes</a>
758 }else{
759 @ %z(chref("button","%R?diff=%d&exbase=%h&w",diffType,zExBase))\
760 @ Ignore&nbsp;Whitespace</a>
761 }
762 }
763 @ </div>
764 while( db_step(&q)==SQLITE_ROW ){
765 const char *zFile; /* Name of file in the repository */
766 char *zLhs; /* Full name of left-hand side file */
767 char *zRhs; /* Full name of right-hand side file */
768 Blob rhs; /* Full text of RHS */
769 Blob lhs; /* Full text of LHS */
770
771 zFile = db_column_text(&q,0);
772 zLhs = mprintf("%s/%s", zExBase, zFile);
773 zRhs = mprintf("%s%s", g.zLocalRoot, zFile);
774 if( file_size(zLhs, ExtFILE)<0 ){
775 @ <div class='file-change-line'><span>
776 @ Missing from external baseline: %h(zFile)
777 @ </span></div>
778 }else{
779 blob_read_from_file(&lhs, zLhs, ExtFILE);
780 blob_read_from_file(&rhs, zRhs, ExtFILE);
781 if( blob_size(&lhs)!=blob_size(&rhs)
782 || memcmp(blob_buffer(&lhs), blob_buffer(&rhs), blob_size(&lhs))!=0
783 ){
784 @ <div class='file-change-line'><span>
785 @ Changes to %h(zFile)
786 @ </span></div>
787 if( pCfg ){
788 char *zFullFN;
789 char *zHexFN;
790 zFullFN = file_canonical_name_dup(zLhs);
791 zHexFN = mprintf("x%H", zFullFN);
792 fossil_free(zFullFN);
793 pCfg->zLeftHash = zHexFN;
794 text_diff(&lhs, &rhs, cgi_output_blob(), pCfg);
795 pCfg->zLeftHash = 0;
796 fossil_free(zHexFN);
797 }
798 }
799 blob_reset(&lhs);
800 blob_reset(&rhs);
801 }
802 fossil_free(zLhs);
803 fossil_free(zRhs);
804 }
805 db_finalize(&q);
806 append_diff_javascript(diffType);
807 }
808
809 /*
810 ** WEBPAGE: ckout
811 **
812 ** Show information about the current checkout. This page only functions
813 ** if the web server is run on a loopback interface (in other words, was
814 ** started using "fossil ui" or similar) from within an open check-out.
815 **
816 ** If the "exbase=PATH" query parameter is provided, then the diff shown
817 ** uses the files in PATH as the baseline. This is the same as using
818 ** the "--from PATH" argument to the "fossil diff" command-line. In fact,
819 ** when using "fossil ui --from PATH", the --from argument becomes the value
820 ** of the exbase query parameter for the start page. Note that if PATH
821 ** is a pure hexadecimal string, it is decoded first before being used as
822 ** the pathname. Real pathnames should contain at least one directory
823 ** separator character.
824 **
825 ** Other query parameters related to diffs are also accepted.
826 */
827 void ckout_page(void){
828 int vid;
829 const char *zHome; /* Home directory */
830 int nHome;
831 const char *zExBase;
832 char *zHostname;
833 char *zCwd;
834
835 if( !cgi_is_loopback(g.zIpAddr) || !db_open_local(0) ){
836 cgi_redirectf("%R/home");
837 return;
838 }
839 file_chdir(g.zLocalRoot, 0);
840 vid = db_lget_int("checkout", 0);
841 db_unprotect(PROTECT_ALL);
842 vfile_check_signature(vid, CKSIG_ENOTFILE);
843 db_protect_pop();
844 style_set_current_feature("vinfo");
845 zHostname = fossil_hostname();
846 zCwd = file_getcwd(0,0);
847 zHome = fossil_getenv("HOME");
848 if( zHome ){
849 nHome = (int)strlen(zHome);
850 if( strncmp(zCwd, zHome, nHome)==0 && zCwd[nHome]=='/' ){
851 zCwd = mprintf("~%s", zCwd+nHome);
852 }
853 }else{
854 nHome = 0;
855 }
856 if( zHostname ){
857 style_header("Checkout Status: %h on %h", zCwd, zHostname);
858 }else{
859 style_header("Checkout Status: %h", zCwd);
860 }
861 render_checkin_context(vid, 0, 0, 0);
862 @ <hr>
863 zExBase = P("exbase");
864 if( zExBase && zExBase[0] ){
865 char *zPath = decode16_dup(zExBase);
866 char *zCBase = file_canonical_name_dup(zPath?zPath:zExBase);
867 if( nHome && strncmp(zCBase, zHome, nHome)==0 && zCBase[nHome]=='/' ){
868 @ <p>Using external baseline: ~%h(zCBase+nHome)</p>
869 }else{
870 @ <p>Using external baseline: %h(zCBase)</p>
871 }
872 ckout_external_base_diff(vid, zCBase);
873 fossil_free(zCBase);
874 fossil_free(zPath);
875 }else{
876 ckout_normal_diff(vid);
877 }
878 style_finish_page();
879 }
880
881 /*
882 ** WEBPAGE: vinfo
883 ** WEBPAGE: ci
884 ** URL: /ci/ARTIFACTID
@@ -628,11 +901,10 @@
901 const char *zParent; /* Hash of the parent check-in (if any) */
902 const char *zRe; /* regex parameter */
903 ReCompiled *pRe = 0; /* regex */
904 const char *zW; /* URL param for ignoring whitespace */
905 const char *zPage = "vinfo"; /* Page that shows diffs */
 
906 const char *zBrName; /* Branch name */
907 DiffConfig DCfg,*pCfg; /* Type of diff */
908
909 login_check_credentials();
910 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
@@ -898,21 +1170,17 @@
1170 @ <div class="sectionmenu info-changes-menu">
1171 /* ^^^ .info-changes-menu is used by diff scroll sync */
1172 pCfg = construct_diff_flags(diffType, &DCfg);
1173 DCfg.pRe = pRe;
1174 zW = (DCfg.diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
 
 
 
 
1175 if( diffType!=1 ){
1176 @ %z(chref("button","%R/%s/%T?diff=1%s",zPage,zName,zW))\
1177 @ Unified&nbsp;Diff</a>
1178 }
1179 if( diffType!=2 ){
1180 @ %z(chref("button","%R/%s/%T?diff=2%s",zPage,zName,zW))\
1181 @ Side-by-Side&nbsp;Diff</a>
1182 }
1183 if( diffType!=0 ){
1184 if( *zW ){
1185 @ %z(chref("button","%R/%s/%T",zPage,zName))
1186 @ Show&nbsp;Whitespace&nbsp;Changes</a>
@@ -1268,13 +1536,10 @@
1536 cgi_check_for_malice();
1537 style_set_current_feature("vdiff");
1538 if( zBranch==0 ){
1539 style_submenu_element("Path", "%R/timeline?me=%T&you=%T", zFrom, zTo);
1540 }
 
 
 
1541 if( diffType!=2 ){
1542 style_submenu_element("Side-by-Side Diff", "%R/vdiff?diff=2&%b%b", &qp,
1543 &qpGlob);
1544 }
1545 if( diffType!=1 ) {
@@ -1668,11 +1933,11 @@
1933 tag_private_status(rid);
1934 }
1935 db_finalize(&q);
1936 if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d AND tagid=%d",
1937 rid, TAG_CLUSTER) ){
1938 @ Cluster %z(href("%R/info/%S",zUuid))%S(zUuid)</a>.
1939 cnt++;
1940 }
1941 if( cnt==0 ){
1942 @ Unrecognized artifact
1943 if( pDownloadName && blob_size(pDownloadName)==0 ){
@@ -1933,10 +2198,16 @@
2198 ** WEBPAGE: jchunk hidden
2199 ** URL: /jchunk/HASH?from=N&to=M
2200 **
2201 ** Return lines of text from a file as a JSON array - one entry in the
2202 ** array for each line of text.
2203 **
2204 ** The HASH is normally a sha1 or sha3 hash that identifies an artifact
2205 ** in the BLOB table of the database. However, if HASH starts with an "x"
2206 ** and is followed by valid hexadecimal, and if we are running in a
2207 ** "fossil ui" situation (locally and with privilege), then decode the hex
2208 ** into a filename and read the file content from that name.
2209 **
2210 ** **Warning:** This is an internal-use-only interface that is subject to
2211 ** change at any moment. External application should not use this interface
2212 ** since the application will break when this interface changes, and this
2213 ** interface will undoubtedly change.
@@ -1948,10 +2219,11 @@
2219 ** ajax_route_error().
2220 */
2221 void jchunk_page(void){
2222 int rid = 0;
2223 const char *zName = PD("name", "");
2224 int nName = (int)(strlen(zName)&0x7fffffff);
2225 int iFrom = atoi(PD("from","0"));
2226 int iTo = atoi(PD("to","0"));
2227 int ln;
2228 int go = 1;
2229 const char *zSep;
@@ -1968,36 +2240,57 @@
2240 cgi_check_for_malice();
2241 if( !g.perm.Read ){
2242 ajax_route_error(403, "Access requires Read permissions.");
2243 return;
2244 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2245 if( iFrom<1 || iTo<iFrom ){
2246 ajax_route_error(500, "Invalid line range from=%d, to=%d.",
2247 iFrom, iTo);
2248 return;
2249 }
2250 if( zName[0]=='x'
2251 && ((nName-1)&1)==0
2252 && validate16(&zName[1],nName-1)
2253 && g.perm.Admin
2254 && cgi_is_loopback(g.zIpAddr)
2255 && db_open_local(0)
2256 ){
2257 /* Treat the HASH as a hex-encoded filename */
2258 int n = (nName-1)/2;
2259 char *zFN = fossil_malloc(n+1);
2260 decode16((const u8*)&zName[1], (u8*)zFN, nName-1);
2261 zFN[n] = 0;
2262 if( file_size(zFN, ExtFILE)<0 ){
2263 blob_zero(&content);
2264 }else{
2265 blob_read_from_file(&content, zFN, ExtFILE);
2266 }
2267 fossil_free(zFN);
2268 }else{
2269 /* Treat the HASH as an artifact hash matching BLOB.UUID */
2270 #if 1
2271 /* Re-enable this block once this code is integrated somewhere into
2272 the UI. */
2273 rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%Q", zName);
2274 if( rid==0 ){
2275 ajax_route_error(404, "Unknown artifact: %h", zName);
2276 return;
2277 }
2278 #else
2279 /* This impl is only to simplify "manual" testing via the JS
2280 console. */
2281 rid = symbolic_name_to_rid(zName, "*");
2282 if( rid==0 ){
2283 ajax_route_error(404, "Unknown artifact: %h", zName);
2284 return;
2285 }else if( rid<0 ){
2286 ajax_route_error(418, "Ambiguous artifact name: %h", zName);
2287 return;
2288 }
2289 #endif
2290 content_get(rid, &content);
2291 }
2292 g.isConst = 1;
2293 cgi_set_content_type("application/json");
2294 ln = 0;
2295 while( go && ln<iFrom ){
2296 go = blob_line(&content, &line);
@@ -2875,10 +3168,143 @@
3168 ticket_output_change_artifact(pTktChng, 0, 1, 0);
3169 manifest_destroy(pTktChng);
3170 style_finish_page();
3171 }
3172
3173 /*
3174 ** rid is a cluster. Paint a page that contains detailed information
3175 ** about that cluster.
3176 */
3177 static void cluster_info(int rid, const char *zName){
3178 Manifest *pCluster;
3179 int i;
3180 Blob where = BLOB_INITIALIZER;
3181 Blob unks = BLOB_INITIALIZER;
3182 Stmt q;
3183 char *zSha1Bg;
3184 char *zSha3Bg;
3185 int badRid = 0;
3186 int rcvid;
3187 int hashClr = PB("hclr");
3188 const char *zDate;
3189
3190 pCluster = manifest_get(rid, CFTYPE_CLUSTER, 0);
3191 if( pCluster==0 ){
3192 artifact_page();
3193 return;
3194 }
3195 style_header("Cluster %S", zName);
3196 rcvid = db_int(0, "SELECT rcvid FROM blob WHERE rid=%d", rid);
3197 if( rcvid==0 ){
3198 zDate = 0;
3199 }else{
3200 zDate = db_text(0, "SELECT datetime(mtime) FROM rcvfrom WHERE rcvid=%d",
3201 rcvid);
3202 }
3203 @ <p>Artifact %z(href("%R/artifact/%h",zName))%S(zName)</a> is a cluster
3204 @ with %d(pCluster->nCChild) entries
3205 if( g.perm.Admin ){
3206 @ received <a href="%R/rcvfrom?rcvid=%d(rcvid)">%h(zDate)</a>:
3207 }else{
3208 @ received %h(zDate):
3209 }
3210 blob_appendf(&where,"IN(0");
3211 for(i=0; i<pCluster->nCChild; i++){
3212 int rid = fast_uuid_to_rid(pCluster->azCChild[i]);
3213 if( rid ){
3214 blob_appendf(&where,",%d", rid);
3215 }else{
3216 if( blob_size(&unks)>0 ) blob_append_char(&unks, ',');
3217 badRid++;
3218 blob_append_sql(&unks,"(%d,%Q)",-badRid,pCluster->azCChild[i]);
3219 }
3220 }
3221 blob_append_char(&where,')');
3222 describe_artifacts(blob_str(&where));
3223 blob_reset(&where);
3224 if( badRid>0 ){
3225 db_multi_exec(
3226 "WITH unks(rx,hx) AS (VALUES %s)\n"
3227 "INSERT INTO description(rid,uuid,type,summary) "
3228 " SELECT rx, hx, 'phantom', '' FROM unks;",
3229 blob_sql_text(&unks)
3230 );
3231 }
3232 blob_reset(&unks);
3233 db_prepare(&q,
3234 "SELECT rid, uuid, summary, isPrivate, type='phantom', rcvid, ref"
3235 " FROM description ORDER BY uuid"
3236 );
3237 if( skin_detail_boolean("white-foreground") ){
3238 zSha1Bg = "#714417";
3239 zSha3Bg = "#177117";
3240 }else{
3241 zSha1Bg = "#ebffb0";
3242 zSha3Bg = "#b0ffb0";
3243 }
3244 @ <table cellpadding="2" cellspacing="0" border="1">
3245 if( g.perm.Admin ){
3246 @ <tr><th>RID<th>Hash<th>Rcvid<th>Description<th>Ref<th>Remarks
3247 }else{
3248 @ <tr><th>RID<th>Hash<th>Description<th>Ref<th>Remarks
3249 }
3250 while( db_step(&q)==SQLITE_ROW ){
3251 int rid = db_column_int(&q,0);
3252 const char *zUuid = db_column_text(&q, 1);
3253 const char *zDesc = db_column_text(&q, 2);
3254 int isPriv = db_column_int(&q,3);
3255 int isPhantom = db_column_int(&q,4);
3256 const char *zRef = db_column_text(&q,6);
3257 if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){
3258 /* Don't show private artifacts to users without Private (x) permission */
3259 continue;
3260 }
3261 if( rid<=0 ){
3262 @ <tr><td>&nbsp;</td>
3263 }else if( hashClr ){
3264 const char *zClr = db_column_bytes(&q,1)>40 ? zSha3Bg : zSha1Bg;
3265 @ <tr style='background-color:%s(zClr);'><td align="right">%d(rid)</td>
3266 }else{
3267 @ <tr><td align="right">%d(rid)</td>
3268 }
3269 if( rid<=0 ){
3270 @ <td>&nbsp;%S(zUuid)&nbsp;</td>
3271 }else{
3272 @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
3273 }
3274 if( g.perm.Admin ){
3275 int rcvid = db_column_int(&q,5);
3276 if( rcvid<=0 ){
3277 @ <td>&nbsp;
3278 }else{
3279 @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%d(rcvid)</a>
3280 }
3281 }
3282 @ <td align="left">%h(zDesc)</td>
3283 if( zRef && zRef[0] ){
3284 @ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a>
3285 }else{
3286 @ <td>&nbsp;
3287 }
3288 if( isPriv || isPhantom ){
3289 if( isPriv==0 ){
3290 @ <td>phantom</td>
3291 }else if( isPhantom==0 ){
3292 @ <td>private</td>
3293 }else{
3294 @ <td>private,phantom</td>
3295 }
3296 }else{
3297 @ <td>&nbsp;
3298 }
3299 @ </tr>
3300 }
3301 @ </table>
3302 db_finalize(&q);
3303 style_finish_page();
3304 }
3305
3306
3307 /*
3308 ** WEBPAGE: info
3309 ** URL: info/NAME
3310 **
@@ -2963,10 +3389,14 @@
3389 if( db_exists("SELECT 1 FROM plink WHERE pid=%d", rid) ){
3390 ci_page();
3391 }else
3392 if( db_exists("SELECT 1 FROM attachment WHERE attachid=%d", rid) ){
3393 ainfo_page();
3394 }else
3395 if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d AND tagid=%d",
3396 rid, TAG_CLUSTER) ){
3397 cluster_info(rid, zName);
3398 }else
3399 {
3400 artifact_page();
3401 }
3402 }
3403
+2 -1
--- src/interwiki.c
+++ src/interwiki.c
@@ -275,13 +275,14 @@
275275
db_prepare(&q,
276276
"SELECT substr(name,11), value->>'base'"
277277
" FROM config WHERE name glob 'interwiki:*' AND json_valid(value)"
278278
" ORDER BY name;"
279279
);
280
+ blob_append(out, "<blockquote>", -1);
280281
while( db_step(&q)==SQLITE_ROW ){
281282
if( n==0 ){
282
- blob_appendf(out, "<blockquote><table>\n");
283
+ blob_appendf(out, "<table>\n");
283284
}
284285
blob_appendf(out,"<tr><td>%h</td><td>&nbsp;&rarr;&nbsp;</td>",
285286
db_column_text(&q,0));
286287
blob_appendf(out,"<td>%h</td></tr>\n",
287288
db_column_text(&q,1));
288289
--- src/interwiki.c
+++ src/interwiki.c
@@ -275,13 +275,14 @@
275 db_prepare(&q,
276 "SELECT substr(name,11), value->>'base'"
277 " FROM config WHERE name glob 'interwiki:*' AND json_valid(value)"
278 " ORDER BY name;"
279 );
 
280 while( db_step(&q)==SQLITE_ROW ){
281 if( n==0 ){
282 blob_appendf(out, "<blockquote><table>\n");
283 }
284 blob_appendf(out,"<tr><td>%h</td><td>&nbsp;&rarr;&nbsp;</td>",
285 db_column_text(&q,0));
286 blob_appendf(out,"<td>%h</td></tr>\n",
287 db_column_text(&q,1));
288
--- src/interwiki.c
+++ src/interwiki.c
@@ -275,13 +275,14 @@
275 db_prepare(&q,
276 "SELECT substr(name,11), value->>'base'"
277 " FROM config WHERE name glob 'interwiki:*' AND json_valid(value)"
278 " ORDER BY name;"
279 );
280 blob_append(out, "<blockquote>", -1);
281 while( db_step(&q)==SQLITE_ROW ){
282 if( n==0 ){
283 blob_appendf(out, "<table>\n");
284 }
285 blob_appendf(out,"<tr><td>%h</td><td>&nbsp;&rarr;&nbsp;</td>",
286 db_column_text(&q,0));
287 blob_appendf(out,"<td>%h</td></tr>\n",
288 db_column_text(&q,1));
289
+13 -1
--- src/login.c
+++ src/login.c
@@ -1302,20 +1302,32 @@
13021302
*/
13031303
void login_restrict_robot_access(void){
13041304
const char *zReferer;
13051305
const char *zGlob;
13061306
int isMatch = 1;
1307
+ int nQP; /* Number of query parameters other than name= */
13071308
if( g.zLogin!=0 ) return;
13081309
zGlob = db_get("robot-restrict",0);
13091310
if( zGlob==0 || zGlob[0]==0 ) return;
13101311
if( g.isHuman ){
13111312
zReferer = P("HTTP_REFERER");
13121313
if( zReferer && zReferer[0]!=0 ) return;
13131314
}
1314
- if( cgi_qp_count()<1 ) return;
1315
+ nQP = cgi_qp_count();
1316
+ if( nQP<1 ) return;
13151317
isMatch = glob_multi_match(zGlob, g.zPath);
13161318
if( !isMatch ) return;
1319
+
1320
+ /* Check for exceptions to the restriction on the number of query
1321
+ ** parameters. */
1322
+ zGlob = db_get("robot-restrict-qp",0);
1323
+ if( zGlob && zGlob[0] ){
1324
+ char *zPath = mprintf("%s/%d", g.zPath, nQP);
1325
+ isMatch = glob_multi_match(zGlob, zPath);
1326
+ fossil_free(zPath);
1327
+ if( isMatch ) return;
1328
+ }
13171329
13181330
/* If we reach this point, it means we have a situation where we
13191331
** want to restrict the activity of a robot.
13201332
*/
13211333
g.isHuman = 0;
13221334
--- src/login.c
+++ src/login.c
@@ -1302,20 +1302,32 @@
1302 */
1303 void login_restrict_robot_access(void){
1304 const char *zReferer;
1305 const char *zGlob;
1306 int isMatch = 1;
 
1307 if( g.zLogin!=0 ) return;
1308 zGlob = db_get("robot-restrict",0);
1309 if( zGlob==0 || zGlob[0]==0 ) return;
1310 if( g.isHuman ){
1311 zReferer = P("HTTP_REFERER");
1312 if( zReferer && zReferer[0]!=0 ) return;
1313 }
1314 if( cgi_qp_count()<1 ) return;
 
1315 isMatch = glob_multi_match(zGlob, g.zPath);
1316 if( !isMatch ) return;
 
 
 
 
 
 
 
 
 
 
1317
1318 /* If we reach this point, it means we have a situation where we
1319 ** want to restrict the activity of a robot.
1320 */
1321 g.isHuman = 0;
1322
--- src/login.c
+++ src/login.c
@@ -1302,20 +1302,32 @@
1302 */
1303 void login_restrict_robot_access(void){
1304 const char *zReferer;
1305 const char *zGlob;
1306 int isMatch = 1;
1307 int nQP; /* Number of query parameters other than name= */
1308 if( g.zLogin!=0 ) return;
1309 zGlob = db_get("robot-restrict",0);
1310 if( zGlob==0 || zGlob[0]==0 ) return;
1311 if( g.isHuman ){
1312 zReferer = P("HTTP_REFERER");
1313 if( zReferer && zReferer[0]!=0 ) return;
1314 }
1315 nQP = cgi_qp_count();
1316 if( nQP<1 ) return;
1317 isMatch = glob_multi_match(zGlob, g.zPath);
1318 if( !isMatch ) return;
1319
1320 /* Check for exceptions to the restriction on the number of query
1321 ** parameters. */
1322 zGlob = db_get("robot-restrict-qp",0);
1323 if( zGlob && zGlob[0] ){
1324 char *zPath = mprintf("%s/%d", g.zPath, nQP);
1325 isMatch = glob_multi_match(zGlob, zPath);
1326 fossil_free(zPath);
1327 if( isMatch ) return;
1328 }
1329
1330 /* If we reach this point, it means we have a situation where we
1331 ** want to restrict the activity of a robot.
1332 */
1333 g.isHuman = 0;
1334
+24 -10
--- src/main.c
+++ src/main.c
@@ -1330,10 +1330,11 @@
13301330
13311331
/* We should be done with options.. */
13321332
verify_all_options();
13331333
fossil_version_blob(&versionInfo, verboseFlag);
13341334
fossil_print("%s", blob_str(&versionInfo));
1335
+ blob_reset(&versionInfo);
13351336
}
13361337
13371338
13381339
/*
13391340
** WEBPAGE: version
@@ -2040,20 +2041,27 @@
20402041
*/
20412042
set_base_url(0);
20422043
if( fossil_redirect_to_https_if_needed(2) ) return;
20432044
if( zPathInfo==0 || zPathInfo[0]==0
20442045
|| (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
2045
- /* Second special case: If the PATH_INFO is blank, issue a redirect to
2046
- ** the home page identified by the "index-page" setting in the repository
2047
- ** CONFIG table, to "/index" if there no "index-page" setting. */
2046
+ /* Second special case: If the PATH_INFO is blank, issue a redirect:
2047
+ ** (1) to "/ckout" if g.useLocalauth and g.localOpen are both set.
2048
+ ** (2) to the home page identified by the "index-page" setting
2049
+ ** in the repository CONFIG table
2050
+ ** (3) to "/index" if there no "index-page" setting in CONFIG
2051
+ */
20482052
#ifdef FOSSIL_ENABLE_JSON
20492053
if(g.json.isJsonMode){
20502054
json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
20512055
fossil_exit(0);
20522056
}
20532057
#endif
2054
- fossil_redirect_home() /*does not return*/;
2058
+ if( g.useLocalauth && g.localOpen ){
2059
+ cgi_redirectf("%R/ckout");
2060
+ }else{
2061
+ fossil_redirect_home() /*does not return*/;
2062
+ }
20552063
}else{
20562064
zPath = mprintf("%s", zPathInfo);
20572065
}
20582066
20592067
/* Make g.zPath point to the first element of the path. Make
@@ -3171,10 +3179,11 @@
31713179
** --errorlog FILE Append HTTP error messages to FILE
31723180
** --extroot DIR Document root for the /ext extension mechanism
31733181
** --files GLOBLIST Comma-separated list of glob patterns for static files
31743182
** --fossilcmd PATH The pathname of the "fossil" executable on the remote
31753183
** system when REPOSITORY is remote.
3184
+** --from PATH Use PATH as the diff baseline for the /ckout page
31763185
** --localauth Enable automatic login for requests from localhost
31773186
** --localhost Listen on 127.0.0.1 only (always true for "ui")
31783187
** --https Indicates that the input is coming through a reverse
31793188
** proxy that has already translated HTTPS into HTTP.
31803189
** --jsmode MODE Determine how JavaScript is delivered with pages.
@@ -3243,10 +3252,11 @@
32433252
const char *zInitPage = 0; /* Start on this page. --page option */
32443253
int findServerArg = 2; /* argv index for find_server_repository() */
32453254
char *zRemote = 0; /* Remote host on which to run "fossil ui" */
32463255
const char *zJsMode; /* The --jsmode parameter */
32473256
const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */
3257
+ const char *zFrom; /* Value for --from */
32483258
32493259
32503260
#if USE_SEE
32513261
db_setup_for_saved_encryption_key();
32523262
#endif
@@ -3279,13 +3289,21 @@
32793289
g.useLocalauth = find_option("localauth", 0, 0)!=0;
32803290
Th_InitTraceLog();
32813291
zPort = find_option("port", "P", 1);
32823292
isUiCmd = g.argv[1][0]=='u';
32833293
if( isUiCmd ){
3294
+ zFrom = find_option("from", 0, 1);
3295
+ if( zFrom && zFrom==file_tail(zFrom) ){
3296
+ fossil_fatal("the argument to --from must be a pathname for"
3297
+ " the \"ui\" command");
3298
+ }
32843299
zInitPage = find_option("page", "p", 1);
32853300
if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
32863301
zFossilCmd = find_option("fossilcmd", 0, 1);
3302
+ if( zFrom && zInitPage==0 ){
3303
+ zInitPage = mprintf("ckout?exbase=%H", zFrom);
3304
+ }
32873305
}
32883306
zNotFound = find_option("notfound", 0, 1);
32893307
allowRepoList = find_option("repolist",0,0)!=0;
32903308
if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1;
32913309
zAltBase = find_option("baseurl", 0, 1);
@@ -3356,11 +3374,11 @@
33563374
const char * zDir = g.argv[2];
33573375
if(dir_has_ckout_db(zDir)){
33583376
if(0!=file_chdir(zDir, 0)){
33593377
fossil_fatal("Cannot chdir to %s", zDir);
33603378
}
3361
- findServerArg = 99;
3379
+ findServerArg = g.argc;
33623380
fCreate = 0;
33633381
g.argv[2] = 0;
33643382
--g.argc;
33653383
}
33663384
}
@@ -3384,15 +3402,11 @@
33843402
}
33853403
if( !zRemote ){
33863404
find_server_repository(findServerArg, fCreate);
33873405
}
33883406
if( zInitPage==0 ){
3389
- if( isUiCmd && g.localOpen ){
3390
- zInitPage = "timeline?c=current";
3391
- }else{
3392
- zInitPage = "";
3393
- }
3407
+ zInitPage = "";
33943408
}
33953409
if( zPort ){
33963410
if( strchr(zPort,':') ){
33973411
int i;
33983412
for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){}
33993413
--- src/main.c
+++ src/main.c
@@ -1330,10 +1330,11 @@
1330
1331 /* We should be done with options.. */
1332 verify_all_options();
1333 fossil_version_blob(&versionInfo, verboseFlag);
1334 fossil_print("%s", blob_str(&versionInfo));
 
1335 }
1336
1337
1338 /*
1339 ** WEBPAGE: version
@@ -2040,20 +2041,27 @@
2040 */
2041 set_base_url(0);
2042 if( fossil_redirect_to_https_if_needed(2) ) return;
2043 if( zPathInfo==0 || zPathInfo[0]==0
2044 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
2045 /* Second special case: If the PATH_INFO is blank, issue a redirect to
2046 ** the home page identified by the "index-page" setting in the repository
2047 ** CONFIG table, to "/index" if there no "index-page" setting. */
 
 
 
2048 #ifdef FOSSIL_ENABLE_JSON
2049 if(g.json.isJsonMode){
2050 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
2051 fossil_exit(0);
2052 }
2053 #endif
2054 fossil_redirect_home() /*does not return*/;
 
 
 
 
2055 }else{
2056 zPath = mprintf("%s", zPathInfo);
2057 }
2058
2059 /* Make g.zPath point to the first element of the path. Make
@@ -3171,10 +3179,11 @@
3171 ** --errorlog FILE Append HTTP error messages to FILE
3172 ** --extroot DIR Document root for the /ext extension mechanism
3173 ** --files GLOBLIST Comma-separated list of glob patterns for static files
3174 ** --fossilcmd PATH The pathname of the "fossil" executable on the remote
3175 ** system when REPOSITORY is remote.
 
3176 ** --localauth Enable automatic login for requests from localhost
3177 ** --localhost Listen on 127.0.0.1 only (always true for "ui")
3178 ** --https Indicates that the input is coming through a reverse
3179 ** proxy that has already translated HTTPS into HTTP.
3180 ** --jsmode MODE Determine how JavaScript is delivered with pages.
@@ -3243,10 +3252,11 @@
3243 const char *zInitPage = 0; /* Start on this page. --page option */
3244 int findServerArg = 2; /* argv index for find_server_repository() */
3245 char *zRemote = 0; /* Remote host on which to run "fossil ui" */
3246 const char *zJsMode; /* The --jsmode parameter */
3247 const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */
 
3248
3249
3250 #if USE_SEE
3251 db_setup_for_saved_encryption_key();
3252 #endif
@@ -3279,13 +3289,21 @@
3279 g.useLocalauth = find_option("localauth", 0, 0)!=0;
3280 Th_InitTraceLog();
3281 zPort = find_option("port", "P", 1);
3282 isUiCmd = g.argv[1][0]=='u';
3283 if( isUiCmd ){
 
 
 
 
 
3284 zInitPage = find_option("page", "p", 1);
3285 if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
3286 zFossilCmd = find_option("fossilcmd", 0, 1);
 
 
 
3287 }
3288 zNotFound = find_option("notfound", 0, 1);
3289 allowRepoList = find_option("repolist",0,0)!=0;
3290 if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1;
3291 zAltBase = find_option("baseurl", 0, 1);
@@ -3356,11 +3374,11 @@
3356 const char * zDir = g.argv[2];
3357 if(dir_has_ckout_db(zDir)){
3358 if(0!=file_chdir(zDir, 0)){
3359 fossil_fatal("Cannot chdir to %s", zDir);
3360 }
3361 findServerArg = 99;
3362 fCreate = 0;
3363 g.argv[2] = 0;
3364 --g.argc;
3365 }
3366 }
@@ -3384,15 +3402,11 @@
3384 }
3385 if( !zRemote ){
3386 find_server_repository(findServerArg, fCreate);
3387 }
3388 if( zInitPage==0 ){
3389 if( isUiCmd && g.localOpen ){
3390 zInitPage = "timeline?c=current";
3391 }else{
3392 zInitPage = "";
3393 }
3394 }
3395 if( zPort ){
3396 if( strchr(zPort,':') ){
3397 int i;
3398 for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){}
3399
--- src/main.c
+++ src/main.c
@@ -1330,10 +1330,11 @@
1330
1331 /* We should be done with options.. */
1332 verify_all_options();
1333 fossil_version_blob(&versionInfo, verboseFlag);
1334 fossil_print("%s", blob_str(&versionInfo));
1335 blob_reset(&versionInfo);
1336 }
1337
1338
1339 /*
1340 ** WEBPAGE: version
@@ -2040,20 +2041,27 @@
2041 */
2042 set_base_url(0);
2043 if( fossil_redirect_to_https_if_needed(2) ) return;
2044 if( zPathInfo==0 || zPathInfo[0]==0
2045 || (zPathInfo[0]=='/' && zPathInfo[1]==0) ){
2046 /* Second special case: If the PATH_INFO is blank, issue a redirect:
2047 ** (1) to "/ckout" if g.useLocalauth and g.localOpen are both set.
2048 ** (2) to the home page identified by the "index-page" setting
2049 ** in the repository CONFIG table
2050 ** (3) to "/index" if there no "index-page" setting in CONFIG
2051 */
2052 #ifdef FOSSIL_ENABLE_JSON
2053 if(g.json.isJsonMode){
2054 json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
2055 fossil_exit(0);
2056 }
2057 #endif
2058 if( g.useLocalauth && g.localOpen ){
2059 cgi_redirectf("%R/ckout");
2060 }else{
2061 fossil_redirect_home() /*does not return*/;
2062 }
2063 }else{
2064 zPath = mprintf("%s", zPathInfo);
2065 }
2066
2067 /* Make g.zPath point to the first element of the path. Make
@@ -3171,10 +3179,11 @@
3179 ** --errorlog FILE Append HTTP error messages to FILE
3180 ** --extroot DIR Document root for the /ext extension mechanism
3181 ** --files GLOBLIST Comma-separated list of glob patterns for static files
3182 ** --fossilcmd PATH The pathname of the "fossil" executable on the remote
3183 ** system when REPOSITORY is remote.
3184 ** --from PATH Use PATH as the diff baseline for the /ckout page
3185 ** --localauth Enable automatic login for requests from localhost
3186 ** --localhost Listen on 127.0.0.1 only (always true for "ui")
3187 ** --https Indicates that the input is coming through a reverse
3188 ** proxy that has already translated HTTPS into HTTP.
3189 ** --jsmode MODE Determine how JavaScript is delivered with pages.
@@ -3243,10 +3252,11 @@
3252 const char *zInitPage = 0; /* Start on this page. --page option */
3253 int findServerArg = 2; /* argv index for find_server_repository() */
3254 char *zRemote = 0; /* Remote host on which to run "fossil ui" */
3255 const char *zJsMode; /* The --jsmode parameter */
3256 const char *zFossilCmd =0; /* Name of "fossil" binary on remote system */
3257 const char *zFrom; /* Value for --from */
3258
3259
3260 #if USE_SEE
3261 db_setup_for_saved_encryption_key();
3262 #endif
@@ -3279,13 +3289,21 @@
3289 g.useLocalauth = find_option("localauth", 0, 0)!=0;
3290 Th_InitTraceLog();
3291 zPort = find_option("port", "P", 1);
3292 isUiCmd = g.argv[1][0]=='u';
3293 if( isUiCmd ){
3294 zFrom = find_option("from", 0, 1);
3295 if( zFrom && zFrom==file_tail(zFrom) ){
3296 fossil_fatal("the argument to --from must be a pathname for"
3297 " the \"ui\" command");
3298 }
3299 zInitPage = find_option("page", "p", 1);
3300 if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
3301 zFossilCmd = find_option("fossilcmd", 0, 1);
3302 if( zFrom && zInitPage==0 ){
3303 zInitPage = mprintf("ckout?exbase=%H", zFrom);
3304 }
3305 }
3306 zNotFound = find_option("notfound", 0, 1);
3307 allowRepoList = find_option("repolist",0,0)!=0;
3308 if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1;
3309 zAltBase = find_option("baseurl", 0, 1);
@@ -3356,11 +3374,11 @@
3374 const char * zDir = g.argv[2];
3375 if(dir_has_ckout_db(zDir)){
3376 if(0!=file_chdir(zDir, 0)){
3377 fossil_fatal("Cannot chdir to %s", zDir);
3378 }
3379 findServerArg = g.argc;
3380 fCreate = 0;
3381 g.argv[2] = 0;
3382 --g.argc;
3383 }
3384 }
@@ -3384,15 +3402,11 @@
3402 }
3403 if( !zRemote ){
3404 find_server_repository(findServerArg, fCreate);
3405 }
3406 if( zInitPage==0 ){
3407 zInitPage = "";
 
 
 
 
3408 }
3409 if( zPort ){
3410 if( strchr(zPort,':') ){
3411 int i;
3412 for(i=strlen(zPort)-1; i>=0 && zPort[i]!=':'; i--){}
3413
+14 -1
--- src/main.mk
+++ src/main.mk
@@ -99,10 +99,11 @@
9999
$(SRCDIR)/lookslike.c \
100100
$(SRCDIR)/main.c \
101101
$(SRCDIR)/manifest.c \
102102
$(SRCDIR)/markdown.c \
103103
$(SRCDIR)/markdown_html.c \
104
+ $(SRCDIR)/match.c \
104105
$(SRCDIR)/md5.c \
105106
$(SRCDIR)/merge.c \
106107
$(SRCDIR)/merge3.c \
107108
$(SRCDIR)/moderate.c \
108109
$(SRCDIR)/name.c \
@@ -248,10 +249,11 @@
248249
$(SRCDIR)/hbmenu.js \
249250
$(SRCDIR)/href.js \
250251
$(SRCDIR)/login.js \
251252
$(SRCDIR)/markdown.md \
252253
$(SRCDIR)/menu.js \
254
+ $(SRCDIR)/merge.tcl \
253255
$(SRCDIR)/scroll.js \
254256
$(SRCDIR)/skin.js \
255257
$(SRCDIR)/sorttable.js \
256258
$(SRCDIR)/sounds/0.wav \
257259
$(SRCDIR)/sounds/1.wav \
@@ -363,10 +365,11 @@
363365
$(OBJDIR)/lookslike_.c \
364366
$(OBJDIR)/main_.c \
365367
$(OBJDIR)/manifest_.c \
366368
$(OBJDIR)/markdown_.c \
367369
$(OBJDIR)/markdown_html_.c \
370
+ $(OBJDIR)/match_.c \
368371
$(OBJDIR)/md5_.c \
369372
$(OBJDIR)/merge_.c \
370373
$(OBJDIR)/merge3_.c \
371374
$(OBJDIR)/moderate_.c \
372375
$(OBJDIR)/name_.c \
@@ -512,10 +515,11 @@
512515
$(OBJDIR)/lookslike.o \
513516
$(OBJDIR)/main.o \
514517
$(OBJDIR)/manifest.o \
515518
$(OBJDIR)/markdown.o \
516519
$(OBJDIR)/markdown_html.o \
520
+ $(OBJDIR)/match.o \
517521
$(OBJDIR)/md5.o \
518522
$(OBJDIR)/merge.o \
519523
$(OBJDIR)/merge3.o \
520524
$(OBJDIR)/moderate.o \
521525
$(OBJDIR)/name.o \
@@ -704,11 +708,11 @@
704708
705709
# The USE_LINENOISE variable may be undefined, set to 0, or set
706710
# to 1. If it is set to 0, then there is no need to build or link
707711
# the linenoise.o object.
708712
LINENOISE_DEF.0 =
709
-LINENOISE_DEF.1 = -DHAVE_LINENOISE
713
+LINENOISE_DEF.1 = -DHAVE_LINENOISE=2
710714
LINENOISE_DEF. = $(LINENOISE_DEF.0)
711715
LINENOISE_OBJ.0 =
712716
LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o
713717
LINENOISE_OBJ. = $(LINENOISE_OBJ.0)
714718
@@ -847,10 +851,11 @@
847851
$(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \
848852
$(OBJDIR)/main_.c:$(OBJDIR)/main.h \
849853
$(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \
850854
$(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \
851855
$(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \
856
+ $(OBJDIR)/match_.c:$(OBJDIR)/match.h \
852857
$(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \
853858
$(OBJDIR)/merge_.c:$(OBJDIR)/merge.h \
854859
$(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h \
855860
$(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h \
856861
$(OBJDIR)/name_.c:$(OBJDIR)/name.h \
@@ -1596,10 +1601,18 @@
15961601
15971602
$(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h
15981603
$(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c
15991604
16001605
$(OBJDIR)/markdown_html.h: $(OBJDIR)/headers
1606
+
1607
+$(OBJDIR)/match_.c: $(SRCDIR)/match.c $(OBJDIR)/translate
1608
+ $(OBJDIR)/translate $(SRCDIR)/match.c >$@
1609
+
1610
+$(OBJDIR)/match.o: $(OBJDIR)/match_.c $(OBJDIR)/match.h $(SRCDIR)/config.h
1611
+ $(XTCC) -o $(OBJDIR)/match.o -c $(OBJDIR)/match_.c
1612
+
1613
+$(OBJDIR)/match.h: $(OBJDIR)/headers
16011614
16021615
$(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(OBJDIR)/translate
16031616
$(OBJDIR)/translate $(SRCDIR)/md5.c >$@
16041617
16051618
$(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h
16061619
16071620
ADDED src/match.c
--- src/main.mk
+++ src/main.mk
@@ -99,10 +99,11 @@
99 $(SRCDIR)/lookslike.c \
100 $(SRCDIR)/main.c \
101 $(SRCDIR)/manifest.c \
102 $(SRCDIR)/markdown.c \
103 $(SRCDIR)/markdown_html.c \
 
104 $(SRCDIR)/md5.c \
105 $(SRCDIR)/merge.c \
106 $(SRCDIR)/merge3.c \
107 $(SRCDIR)/moderate.c \
108 $(SRCDIR)/name.c \
@@ -248,10 +249,11 @@
248 $(SRCDIR)/hbmenu.js \
249 $(SRCDIR)/href.js \
250 $(SRCDIR)/login.js \
251 $(SRCDIR)/markdown.md \
252 $(SRCDIR)/menu.js \
 
253 $(SRCDIR)/scroll.js \
254 $(SRCDIR)/skin.js \
255 $(SRCDIR)/sorttable.js \
256 $(SRCDIR)/sounds/0.wav \
257 $(SRCDIR)/sounds/1.wav \
@@ -363,10 +365,11 @@
363 $(OBJDIR)/lookslike_.c \
364 $(OBJDIR)/main_.c \
365 $(OBJDIR)/manifest_.c \
366 $(OBJDIR)/markdown_.c \
367 $(OBJDIR)/markdown_html_.c \
 
368 $(OBJDIR)/md5_.c \
369 $(OBJDIR)/merge_.c \
370 $(OBJDIR)/merge3_.c \
371 $(OBJDIR)/moderate_.c \
372 $(OBJDIR)/name_.c \
@@ -512,10 +515,11 @@
512 $(OBJDIR)/lookslike.o \
513 $(OBJDIR)/main.o \
514 $(OBJDIR)/manifest.o \
515 $(OBJDIR)/markdown.o \
516 $(OBJDIR)/markdown_html.o \
 
517 $(OBJDIR)/md5.o \
518 $(OBJDIR)/merge.o \
519 $(OBJDIR)/merge3.o \
520 $(OBJDIR)/moderate.o \
521 $(OBJDIR)/name.o \
@@ -704,11 +708,11 @@
704
705 # The USE_LINENOISE variable may be undefined, set to 0, or set
706 # to 1. If it is set to 0, then there is no need to build or link
707 # the linenoise.o object.
708 LINENOISE_DEF.0 =
709 LINENOISE_DEF.1 = -DHAVE_LINENOISE
710 LINENOISE_DEF. = $(LINENOISE_DEF.0)
711 LINENOISE_OBJ.0 =
712 LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o
713 LINENOISE_OBJ. = $(LINENOISE_OBJ.0)
714
@@ -847,10 +851,11 @@
847 $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \
848 $(OBJDIR)/main_.c:$(OBJDIR)/main.h \
849 $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \
850 $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \
851 $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \
 
852 $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \
853 $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h \
854 $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h \
855 $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h \
856 $(OBJDIR)/name_.c:$(OBJDIR)/name.h \
@@ -1596,10 +1601,18 @@
1596
1597 $(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h
1598 $(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c
1599
1600 $(OBJDIR)/markdown_html.h: $(OBJDIR)/headers
 
 
 
 
 
 
 
 
1601
1602 $(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(OBJDIR)/translate
1603 $(OBJDIR)/translate $(SRCDIR)/md5.c >$@
1604
1605 $(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h
1606
1607 DDED src/match.c
--- src/main.mk
+++ src/main.mk
@@ -99,10 +99,11 @@
99 $(SRCDIR)/lookslike.c \
100 $(SRCDIR)/main.c \
101 $(SRCDIR)/manifest.c \
102 $(SRCDIR)/markdown.c \
103 $(SRCDIR)/markdown_html.c \
104 $(SRCDIR)/match.c \
105 $(SRCDIR)/md5.c \
106 $(SRCDIR)/merge.c \
107 $(SRCDIR)/merge3.c \
108 $(SRCDIR)/moderate.c \
109 $(SRCDIR)/name.c \
@@ -248,10 +249,11 @@
249 $(SRCDIR)/hbmenu.js \
250 $(SRCDIR)/href.js \
251 $(SRCDIR)/login.js \
252 $(SRCDIR)/markdown.md \
253 $(SRCDIR)/menu.js \
254 $(SRCDIR)/merge.tcl \
255 $(SRCDIR)/scroll.js \
256 $(SRCDIR)/skin.js \
257 $(SRCDIR)/sorttable.js \
258 $(SRCDIR)/sounds/0.wav \
259 $(SRCDIR)/sounds/1.wav \
@@ -363,10 +365,11 @@
365 $(OBJDIR)/lookslike_.c \
366 $(OBJDIR)/main_.c \
367 $(OBJDIR)/manifest_.c \
368 $(OBJDIR)/markdown_.c \
369 $(OBJDIR)/markdown_html_.c \
370 $(OBJDIR)/match_.c \
371 $(OBJDIR)/md5_.c \
372 $(OBJDIR)/merge_.c \
373 $(OBJDIR)/merge3_.c \
374 $(OBJDIR)/moderate_.c \
375 $(OBJDIR)/name_.c \
@@ -512,10 +515,11 @@
515 $(OBJDIR)/lookslike.o \
516 $(OBJDIR)/main.o \
517 $(OBJDIR)/manifest.o \
518 $(OBJDIR)/markdown.o \
519 $(OBJDIR)/markdown_html.o \
520 $(OBJDIR)/match.o \
521 $(OBJDIR)/md5.o \
522 $(OBJDIR)/merge.o \
523 $(OBJDIR)/merge3.o \
524 $(OBJDIR)/moderate.o \
525 $(OBJDIR)/name.o \
@@ -704,11 +708,11 @@
708
709 # The USE_LINENOISE variable may be undefined, set to 0, or set
710 # to 1. If it is set to 0, then there is no need to build or link
711 # the linenoise.o object.
712 LINENOISE_DEF.0 =
713 LINENOISE_DEF.1 = -DHAVE_LINENOISE=2
714 LINENOISE_DEF. = $(LINENOISE_DEF.0)
715 LINENOISE_OBJ.0 =
716 LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o
717 LINENOISE_OBJ. = $(LINENOISE_OBJ.0)
718
@@ -847,10 +851,11 @@
851 $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \
852 $(OBJDIR)/main_.c:$(OBJDIR)/main.h \
853 $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \
854 $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \
855 $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \
856 $(OBJDIR)/match_.c:$(OBJDIR)/match.h \
857 $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \
858 $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h \
859 $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h \
860 $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h \
861 $(OBJDIR)/name_.c:$(OBJDIR)/name.h \
@@ -1596,10 +1601,18 @@
1601
1602 $(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h
1603 $(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c
1604
1605 $(OBJDIR)/markdown_html.h: $(OBJDIR)/headers
1606
1607 $(OBJDIR)/match_.c: $(SRCDIR)/match.c $(OBJDIR)/translate
1608 $(OBJDIR)/translate $(SRCDIR)/match.c >$@
1609
1610 $(OBJDIR)/match.o: $(OBJDIR)/match_.c $(OBJDIR)/match.h $(SRCDIR)/config.h
1611 $(XTCC) -o $(OBJDIR)/match.o -c $(OBJDIR)/match_.c
1612
1613 $(OBJDIR)/match.h: $(OBJDIR)/headers
1614
1615 $(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(OBJDIR)/translate
1616 $(OBJDIR)/translate $(SRCDIR)/md5.c >$@
1617
1618 $(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h
1619
1620 DDED src/match.c
+386
--- a/src/match.c
+++ b/src/match.c
@@ -0,0 +1,386 @@
1
+/*
2
+** Copyright (c) 2007 D. Richard Hipp
3
+**
4
+** This program is free software; you can redistribute it and/or
5
+** modify it under the terms of the Simplified BSD License (also
6
+** known as the "2-Clause License" or "FreeBSD License".)
7
+
8
+** This program is distributed in the hope that it will be useful,
9
+** but without any warranty; without even the implied warranty of
10
+** merchantability or fitness for a particular purpose.
11
+**
12
+** Author contact information:
13
+** [email protected]
14
+** http://www.hwaci.com/drh/
15
+**
16
+*******************************************************************************
17
+**
18
+** This file contains code to implement string comparisons using a
19
+** variety of algorithm. The comparison algorithm can be any of:
20
+**
21
+** MS_EXACT The string must exactly match the pattern.
22
+**
23
+** MS_BRLIST The pattern is a space- and/or comma-separated
24
+** list of strings, any one of which may match
25
+** the input string.
26
+**
27
+** MS_GLOB Like BRLIST, except each component of the pattern
28
+** is a GLOB expression.
29
+**
30
+** MS_LIKE Like BRLIST, except each component of the pattern
31
+** is an SQL LIKE expression.
32
+**
33
+** MS_REGEXP Like BRLIST, except each component of the pattern
34
+** is a regular expression.
35
+**
36
+*/
37
+#include "config.h"
38
+#include <string.h>
39
+#include "match.h"
40
+
41
+#if INTERFACE
42
+/*
43
+** Types of comparisons that we are able to perform:
44
+*/
45
+typedef enum {
46
+ MS_EXACT=1, /* Exact string comparison */
47
+ MS_GLOB=2, /* Matches against a list of GLOB patterns. */
48
+ MS_LIKE=3, /* Matches against a list of LIKE patterns. */
49
+ MS_REGEXP=4, /* Matches against a list of regular expressions. */
50
+ MS_BRLIST=5, /* Matches any element of a list */
51
+} MatchStyle;
52
+
53
+/*
54
+** The following object represents a precompiled pattern to use for
55
+** string matching.
56
+**
57
+** * Create an instance of this object using match_create().
58
+** * Do comparisons using match_text().
59
+** * Destroy using match_free() when you are done.
60
+**
61
+*/
62
+struct Matcher {
63
+ MatchStyle style; /* Which algorithm to use */
64
+ int nPattern; /* How many patterns are their */
65
+ char **azPattern; /* List of patterns */
66
+ ReCompiled **aRe; /* List of compiled regular expressions */
67
+};
68
+
69
+#endif /*INTERFACE*/
70
+
71
+/*
72
+** Translate a "match style" text name into the MS_* enum value.
73
+** Return eDflt if no match is found.
74
+*/
75
+MatchStyle match_style(const char *zStyle, MatchStyle eDflt){
76
+ if( zStyle==0 ) return eDflt;
77
+ if( fossil_stricmp(zStyle, "brlist")==0 ) return MS_BRLIST;
78
+ if( fossil_stricmp(zStyle, "list")==0 ) return MS_BRLIST;
79
+ if( fossil_stricmp(zStyle, "regexp")==0 ) return MS_REGEXP;
80
+ if( fossil_stricmp(zStyle, "re")==0 ) return MS_REGEXP;
81
+ if( fossil_stricmp(zStyle, "glob")==0 ) return MS_GLOB;
82
+ if( fossil_stricmp(zStyle, "like")==0 ) return MS_LIKE;
83
+ if( fossil_stricmp(zStyle, "exact")==0 ) return MS_EXACT;
84
+ return eDflt;
85
+}
86
+
87
+
88
+/*
89
+** Create a new Matcher object using the pattern provided.
90
+*/
91
+Matcher *match_create(MatchStyle style, const char *zPat){
92
+ char cDel; /* Delimiter character */
93
+ int i; /* Loop counter */
94
+ Matcher *p; /* The new Matcher to be constructed */
95
+ char *zOne; /* One element of the pattern */
96
+
97
+ if( zPat==0 ) return 0;
98
+ p = fossil_malloc( sizeof(*p) );
99
+ memset(p, 0, sizeof(*p));
100
+ p->style = style;
101
+
102
+ if( style==MS_EXACT ){
103
+ p->nPattern = 1;
104
+ p->azPattern = fossil_malloc( sizeof(p->azPattern[0]) );
105
+ p->azPattern[0] = fossil_strdup(zPat);
106
+ return p;
107
+ }
108
+
109
+ while( 1 ){
110
+ /* Skip leading delimiters. */
111
+ for( ; fossil_isspace(*zPat) || *zPat==','; ++zPat );
112
+
113
+ /* Next non-delimiter character determines quoting. */
114
+ if( zPat[0]==0 ){
115
+ /* Terminate loop at end of string. */
116
+ break;
117
+ }else if( zPat[0]=='\'' || zPat[0]=='"' ){
118
+ /* If word is quoted, prepare to stop at end quote. */
119
+ cDel = zPat[0];
120
+ ++zPat;
121
+ }else{
122
+ /* If word is not quoted, prepare to stop at delimiter. */
123
+ cDel = ',';
124
+ }
125
+
126
+ /* Find the next delimiter character or end of string. */
127
+ for( i=0; zPat[i] && zPat[i]!=cDel; ++i ){
128
+ /* If delimiter is comma, also recognize spaces as delimiters. */
129
+ if( cDel==',' && fossil_isspace(zPat[i]) ){
130
+ break;
131
+ }
132
+
133
+ /* In regexp mode, ignore delimiters following backslashes. */
134
+ if( style==MS_REGEXP && zPat[i]=='\\' && zPat[i+1] ){
135
+ ++i;
136
+ }
137
+ }
138
+
139
+ /* zOne is a zero-terminated copy of the pattern, without delimiters */
140
+ zOne = fossil_strndup(zPat, i);
141
+ zPat += i;
142
+ if( zPat[0] ) zPat++;
143
+
144
+ /* Check for regular expression syntax errors. */
145
+ if( style==MS_REGEXP ){
146
+ ReCompiled *regexp;
147
+ const char *zFail = re_compile(&regexp, zOne, 0);
148
+ if( zFail ){
149
+ re_free(regexp);
150
+ continue;
151
+ }
152
+ p->nPattern++;
153
+ p->aRe = fossil_realloc(p->aRe, sizeof(p->aRe)*p->nPattern);
154
+ p->aRe[p->nPattern-1] = regexp;
155
+ fossil_free(zOne);
156
+ }else{
157
+ p->nPattern++;
158
+ p->azPattern = fossil_realloc(p->azPattern, sizeof(char*)*p->nPattern);
159
+ p->azPattern[p->nPattern-1] = zOne;
160
+ }
161
+ }
162
+ return p;
163
+}
164
+
165
+/*
166
+** Return non-zero (true) if the input string matches the pattern
167
+** described by the matcher.
168
+**
169
+** The return value is really the 1-based index of the particular
170
+** pattern that matched.
171
+*/
172
+int match_text(Matcher *p, const char *zText){
173
+ int i;
174
+ if( p==0 ){
175
+ return zText==0;
176
+ }
177
+ switch( p->style ){
178
+ case MS_BRLIST:
179
+ case MS_EXACT: {
180
+ for(i=0; i<p->nPattern; i++){
181
+ if( strcmp(p->azPattern[i], zText)==0 ) return i+1;
182
+ }
183
+ break;
184
+ }
185
+ case MS_GLOB: {
186
+ for(i=0; i<p->nPattern; i++){
187
+ if( sqlite3_strglob(p->azPattern[i], zText)==0 ) return i+1;
188
+ }
189
+ break;
190
+ }
191
+ case MS_LIKE: {
192
+ for(i=0; i<p->nPattern; i++){
193
+ if( sqlite3_strlike(p->azPattern[i], zText, 0)==0 ) return i+1;
194
+ }
195
+ break;
196
+ }
197
+ case MS_REGEXP: {
198
+ int nText = (int)strlen(zText);
199
+ for(i=0; i<p->nPattern; i++){
200
+ if( re_match(p->aRe[i], (const u8*)zText, nText) ) return i+1;
201
+ }
202
+ break;
203
+ }
204
+ }
205
+ return 0n 0;
206
+}
207
+
208
+
209
+/*
210
+** Destroy a previously allocated Matcher object.
211
+*/
212
+void match_free(Matcher *p){
213
+ int i;
214
+ if( p==0 ) return;
215
+ if( p->style==MS_REGEXP ){
216
+ for(i=0; i<p->nPattern; i++) re_free(p->aRe[i]);
217
+ fossil_free(p->aRe);
218
+ }else{
219
+ for(i=0; i<p->nPattern; i++) fossil_free(p->azPattern[i]);
220
+ fossil_free(p->azPattern);
221
+ }
222
+ memset(p, 0, sizeof(*p));
223
+ fossil_free(p);
224
+}
225
+
226
+
227
+
228
+/*
229
+** Quote a tag string by surrounding it with double quotes and preceding
230
+** internal double quotes and backslashes with backslashes.
231
+*/
232
+static const char *tagQuote(
233
+ int len, /* Maximum length of zTag, or negative for unlimited */
234
+ const char *zTag /* Tag string */
235
+){
236
+ Blob blob = BLOB_INITIALIZER;
237
+ int i, j;
238
+ blob_zero(&blob);
239
+ blob_append(&blob, "\"", 1);
240
+ for( i=j=0; zTag[j] && (len<0 || j<len); ++j ){
241
+ if( zTag[j]=='\"' || zTag[j]=='\\' ){
242
+ if( j>i ){
243
+ blob_append(&blob, zTag+i, j-i);
244
+ }
245
+ blob_append(&blob, "\\", 1);
246
+ i = j;
247
+ }
248
+ }
249
+ if( j>i ){
250
+ blob_append(&blob, zTag+i, j-i);
251
+ }
252
+ blob_append(&blob, "\"", 1);
253
+ return blob_str(&blob);
254
+}
255
+
256
+/*
257
+** Construct the SQL expression that goes into the WHERE clause of a join
258
+** that involves the TAG table and that selects a particular tag out of
259
+** that table.
260
+**
261
+** This function is adapted from glob_expr() to support the MS_EXACT, MS_GLOB,
262
+** MS_LIKE, MS_REGEXP, and MS_BRLIST match styles.
263
+**
264
+** For MS_EXACT, the returned expression
265
+** checks for integer match against the tag ID which is looked up directly by
266
+** this function. For the other modes, the returned SQL expression performs
267
+** string comparisons against the tag names, so it is necessary to join against
268
+** the tag table to access the "tagname" column.
269
+**
270
+** Each pattern is adjusted to start with "sym-" and be anchored at end.
271
+**
272
+** In MS_REGEXP mode, backslash can be used to protect delimiter characters.
273
+** The backslashes are not removed from the regular expression.
274
+**
275
+** In addition to assembling and returning an SQL expression, this function
276
+** makes an English-language description of the patterns being matched, suitable
277
+** for display in the web interface.
278
+**
279
+** If any errors arise during processing, *zError is set to an error message.
280
+** Otherwise it is set to NULL.
281
+*/
282
+const char *match_tag_sqlexpr(
283
+ MatchStyle matchStyle, /* Match style code */
284
+ const char *zTag, /* Tag name, match pattern, or pattern list */
285
+ const char **zDesc, /* Output expression description string */
286
+ const char **zError /* Output error string */
287
+){
288
+ Blob expr = BLOB_INITIALIZER; /* SQL expression string assembly buffer */
289
+ Blob desc = BLOB_INITIALIZER; /* English description of match patterns */
290
+ Blob err = BLOB_INITIALIZER; /* Error text assembly buffer */
291
+ const char *zStart; /* Text at start of expression */
292
+ const char *zDelimiter; /* Text between expression terms */
293
+ const char *zEnd; /* Text at end of expression */
294
+ const char *zPrefix; /* Text before each match pattern */
295
+ const char *zSuffix; /* Text after each match pattern */
296
+ const char *zIntro; /* Text introducing pattern description */
297
+ const char *zPattern = 0; /* Previous quoted pattern */
298
+ const char *zFail = 0; /* Current failure message or NULL if okay */
299
+ const char *zOr = " or "; /* Text before final quoted pattern */
300
+ char cDel; /* Input delimiter character */
301
+ int i; /* Input match pattern length counter */
302
+
303
+ /* Optimize exact matches by looking up the ID in advance to create a simple
304
+ * numeric comparison. Bypass the remainder of this function. */
305
+ if( matchStyle==MS_EXACT ){
306
+ *zDesc = tagQuote(-1, zTag);
307
+ return mprintf("(tagid=%d)", db_int(-1,
308
+ "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag));
309
+ }
310
+
311
+ /* Decide pattern prefix and suffix strings according to match style. */
312
+ if( matchStyle==MS_GLOB ){
313
+ zStart = "(";
314
+ zDelimiter = " OR ";
315
+ zEnd = ")";
316
+ zPrefix = "tagname GLOB 'sym-";
317
+ zSuffix = "'";
318
+ zIntro = "glob pattern ";
319
+ }else irt = "(";
320
+ zDelimiter = " OR ";
321
+ zEnd = ")";
322
+ zPrefix = "tagname LIKE 'sym-";
323
+ zSuffix = "'";
324
+ zIntro = "SQL LIKE pattern ";
325
+ }else if( matchStyle==MS_REGEXP ){
326
+ zStart = "(tagname REGEXP '^sym-(";
327
+ zDelimiter = "|";
328
+ zEnd = ")$')";
329
+ zPrefix = "";
330
+ zSuffix = "";
331
+ zIntro = "regular expression ";
332
+ }else/* if( matchStyle==MS_BRLIST )*/{
333
+ zStart = "tagname IN ('sym-";
334
+ zDelimiter = "','sym-";
335
+ zEnd = "')";
336
+ zPrefix = "";
337
+ zSuffix = "";
338
+ zIntro = "";
339
+ }
340
+
341
+ /* Convert the list of matches into an SQL expression and text description. */
342
+ blob_zero(&expr);
343
+ blob_zero(&desc);
344
+ blob_zero(&err);
345
+ while( 1 ){
346
+ /* Skip leading delimiters. */
347
+ for( ; fossil_isspace(*zTag) || *zTag==','; ++zTag );
348
+
349
+ /* Next non-delimiter character determines quoting. */
350
+ if( !*zTag ){
351
+ /* Terminate loop at end of string. */
352
+ break;
353
+ }else if( *zTag=='\'' || *zTag=='"' ){
354
+ /* If word is quoted, prepare to stop at end quote. */
355
+ cDel = *zTag;
356
+ ++zTag;
357
+ }else{
358
+ /* If word is not quoted, prepare to stop at delimiter. */
359
+ cDel = ',';
360
+ }
361
+
362
+ /* Find the next delimiter character or end of string. */
363
+ for( i=0; zTag[i] && zTag[i]!=cDel; ++i ){
364
+ /* If delimiter is comma, also recognize spaces as delimiters. */
365
+ if( cDel==',' && fossil_isspace(zTag[i]) ){
366
+ break;
367
+ }
368
+
369
+ /* In regexp mode, ignore delimiters following backslashes. */
370
+ if( matchStyle==MS_REGEXP && zTag[i]=='\\' && zTag[i+1] ){
371
+ ++i;
372
+ }
373
+ }
374
+
375
+ /* Check for regular expression syntax errors. */
376
+ if( matchStyle==MS_REGEXP ){
377
+ ReCompiled *regexp;
378
+ char *zTagDup = fossil_strndup(zTag, i);
379
+ zFail = fossil_re_compile(&regexp, zTagDup, 0);
380
+ re_free(regexp);
381
+ fossil_free(zTagDup);
382
+ }
383
+
384
+ /* Process success and error results. */
385
+ if( !zFail ){
386
+ /* Incorporate
--- a/src/match.c
+++ b/src/match.c
@@ -0,0 +1,386 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/src/match.c
+++ b/src/match.c
@@ -0,0 +1,386 @@
1 /*
2 ** Copyright (c) 2007 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
13 ** [email protected]
14 ** http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This file contains code to implement string comparisons using a
19 ** variety of algorithm. The comparison algorithm can be any of:
20 **
21 ** MS_EXACT The string must exactly match the pattern.
22 **
23 ** MS_BRLIST The pattern is a space- and/or comma-separated
24 ** list of strings, any one of which may match
25 ** the input string.
26 **
27 ** MS_GLOB Like BRLIST, except each component of the pattern
28 ** is a GLOB expression.
29 **
30 ** MS_LIKE Like BRLIST, except each component of the pattern
31 ** is an SQL LIKE expression.
32 **
33 ** MS_REGEXP Like BRLIST, except each component of the pattern
34 ** is a regular expression.
35 **
36 */
37 #include "config.h"
38 #include <string.h>
39 #include "match.h"
40
41 #if INTERFACE
42 /*
43 ** Types of comparisons that we are able to perform:
44 */
45 typedef enum {
46 MS_EXACT=1, /* Exact string comparison */
47 MS_GLOB=2, /* Matches against a list of GLOB patterns. */
48 MS_LIKE=3, /* Matches against a list of LIKE patterns. */
49 MS_REGEXP=4, /* Matches against a list of regular expressions. */
50 MS_BRLIST=5, /* Matches any element of a list */
51 } MatchStyle;
52
53 /*
54 ** The following object represents a precompiled pattern to use for
55 ** string matching.
56 **
57 ** * Create an instance of this object using match_create().
58 ** * Do comparisons using match_text().
59 ** * Destroy using match_free() when you are done.
60 **
61 */
62 struct Matcher {
63 MatchStyle style; /* Which algorithm to use */
64 int nPattern; /* How many patterns are their */
65 char **azPattern; /* List of patterns */
66 ReCompiled **aRe; /* List of compiled regular expressions */
67 };
68
69 #endif /*INTERFACE*/
70
71 /*
72 ** Translate a "match style" text name into the MS_* enum value.
73 ** Return eDflt if no match is found.
74 */
75 MatchStyle match_style(const char *zStyle, MatchStyle eDflt){
76 if( zStyle==0 ) return eDflt;
77 if( fossil_stricmp(zStyle, "brlist")==0 ) return MS_BRLIST;
78 if( fossil_stricmp(zStyle, "list")==0 ) return MS_BRLIST;
79 if( fossil_stricmp(zStyle, "regexp")==0 ) return MS_REGEXP;
80 if( fossil_stricmp(zStyle, "re")==0 ) return MS_REGEXP;
81 if( fossil_stricmp(zStyle, "glob")==0 ) return MS_GLOB;
82 if( fossil_stricmp(zStyle, "like")==0 ) return MS_LIKE;
83 if( fossil_stricmp(zStyle, "exact")==0 ) return MS_EXACT;
84 return eDflt;
85 }
86
87
88 /*
89 ** Create a new Matcher object using the pattern provided.
90 */
91 Matcher *match_create(MatchStyle style, const char *zPat){
92 char cDel; /* Delimiter character */
93 int i; /* Loop counter */
94 Matcher *p; /* The new Matcher to be constructed */
95 char *zOne; /* One element of the pattern */
96
97 if( zPat==0 ) return 0;
98 p = fossil_malloc( sizeof(*p) );
99 memset(p, 0, sizeof(*p));
100 p->style = style;
101
102 if( style==MS_EXACT ){
103 p->nPattern = 1;
104 p->azPattern = fossil_malloc( sizeof(p->azPattern[0]) );
105 p->azPattern[0] = fossil_strdup(zPat);
106 return p;
107 }
108
109 while( 1 ){
110 /* Skip leading delimiters. */
111 for( ; fossil_isspace(*zPat) || *zPat==','; ++zPat );
112
113 /* Next non-delimiter character determines quoting. */
114 if( zPat[0]==0 ){
115 /* Terminate loop at end of string. */
116 break;
117 }else if( zPat[0]=='\'' || zPat[0]=='"' ){
118 /* If word is quoted, prepare to stop at end quote. */
119 cDel = zPat[0];
120 ++zPat;
121 }else{
122 /* If word is not quoted, prepare to stop at delimiter. */
123 cDel = ',';
124 }
125
126 /* Find the next delimiter character or end of string. */
127 for( i=0; zPat[i] && zPat[i]!=cDel; ++i ){
128 /* If delimiter is comma, also recognize spaces as delimiters. */
129 if( cDel==',' && fossil_isspace(zPat[i]) ){
130 break;
131 }
132
133 /* In regexp mode, ignore delimiters following backslashes. */
134 if( style==MS_REGEXP && zPat[i]=='\\' && zPat[i+1] ){
135 ++i;
136 }
137 }
138
139 /* zOne is a zero-terminated copy of the pattern, without delimiters */
140 zOne = fossil_strndup(zPat, i);
141 zPat += i;
142 if( zPat[0] ) zPat++;
143
144 /* Check for regular expression syntax errors. */
145 if( style==MS_REGEXP ){
146 ReCompiled *regexp;
147 const char *zFail = re_compile(&regexp, zOne, 0);
148 if( zFail ){
149 re_free(regexp);
150 continue;
151 }
152 p->nPattern++;
153 p->aRe = fossil_realloc(p->aRe, sizeof(p->aRe)*p->nPattern);
154 p->aRe[p->nPattern-1] = regexp;
155 fossil_free(zOne);
156 }else{
157 p->nPattern++;
158 p->azPattern = fossil_realloc(p->azPattern, sizeof(char*)*p->nPattern);
159 p->azPattern[p->nPattern-1] = zOne;
160 }
161 }
162 return p;
163 }
164
165 /*
166 ** Return non-zero (true) if the input string matches the pattern
167 ** described by the matcher.
168 **
169 ** The return value is really the 1-based index of the particular
170 ** pattern that matched.
171 */
172 int match_text(Matcher *p, const char *zText){
173 int i;
174 if( p==0 ){
175 return zText==0;
176 }
177 switch( p->style ){
178 case MS_BRLIST:
179 case MS_EXACT: {
180 for(i=0; i<p->nPattern; i++){
181 if( strcmp(p->azPattern[i], zText)==0 ) return i+1;
182 }
183 break;
184 }
185 case MS_GLOB: {
186 for(i=0; i<p->nPattern; i++){
187 if( sqlite3_strglob(p->azPattern[i], zText)==0 ) return i+1;
188 }
189 break;
190 }
191 case MS_LIKE: {
192 for(i=0; i<p->nPattern; i++){
193 if( sqlite3_strlike(p->azPattern[i], zText, 0)==0 ) return i+1;
194 }
195 break;
196 }
197 case MS_REGEXP: {
198 int nText = (int)strlen(zText);
199 for(i=0; i<p->nPattern; i++){
200 if( re_match(p->aRe[i], (const u8*)zText, nText) ) return i+1;
201 }
202 break;
203 }
204 }
205 return 0n 0;
206 }
207
208
209 /*
210 ** Destroy a previously allocated Matcher object.
211 */
212 void match_free(Matcher *p){
213 int i;
214 if( p==0 ) return;
215 if( p->style==MS_REGEXP ){
216 for(i=0; i<p->nPattern; i++) re_free(p->aRe[i]);
217 fossil_free(p->aRe);
218 }else{
219 for(i=0; i<p->nPattern; i++) fossil_free(p->azPattern[i]);
220 fossil_free(p->azPattern);
221 }
222 memset(p, 0, sizeof(*p));
223 fossil_free(p);
224 }
225
226
227
228 /*
229 ** Quote a tag string by surrounding it with double quotes and preceding
230 ** internal double quotes and backslashes with backslashes.
231 */
232 static const char *tagQuote(
233 int len, /* Maximum length of zTag, or negative for unlimited */
234 const char *zTag /* Tag string */
235 ){
236 Blob blob = BLOB_INITIALIZER;
237 int i, j;
238 blob_zero(&blob);
239 blob_append(&blob, "\"", 1);
240 for( i=j=0; zTag[j] && (len<0 || j<len); ++j ){
241 if( zTag[j]=='\"' || zTag[j]=='\\' ){
242 if( j>i ){
243 blob_append(&blob, zTag+i, j-i);
244 }
245 blob_append(&blob, "\\", 1);
246 i = j;
247 }
248 }
249 if( j>i ){
250 blob_append(&blob, zTag+i, j-i);
251 }
252 blob_append(&blob, "\"", 1);
253 return blob_str(&blob);
254 }
255
256 /*
257 ** Construct the SQL expression that goes into the WHERE clause of a join
258 ** that involves the TAG table and that selects a particular tag out of
259 ** that table.
260 **
261 ** This function is adapted from glob_expr() to support the MS_EXACT, MS_GLOB,
262 ** MS_LIKE, MS_REGEXP, and MS_BRLIST match styles.
263 **
264 ** For MS_EXACT, the returned expression
265 ** checks for integer match against the tag ID which is looked up directly by
266 ** this function. For the other modes, the returned SQL expression performs
267 ** string comparisons against the tag names, so it is necessary to join against
268 ** the tag table to access the "tagname" column.
269 **
270 ** Each pattern is adjusted to start with "sym-" and be anchored at end.
271 **
272 ** In MS_REGEXP mode, backslash can be used to protect delimiter characters.
273 ** The backslashes are not removed from the regular expression.
274 **
275 ** In addition to assembling and returning an SQL expression, this function
276 ** makes an English-language description of the patterns being matched, suitable
277 ** for display in the web interface.
278 **
279 ** If any errors arise during processing, *zError is set to an error message.
280 ** Otherwise it is set to NULL.
281 */
282 const char *match_tag_sqlexpr(
283 MatchStyle matchStyle, /* Match style code */
284 const char *zTag, /* Tag name, match pattern, or pattern list */
285 const char **zDesc, /* Output expression description string */
286 const char **zError /* Output error string */
287 ){
288 Blob expr = BLOB_INITIALIZER; /* SQL expression string assembly buffer */
289 Blob desc = BLOB_INITIALIZER; /* English description of match patterns */
290 Blob err = BLOB_INITIALIZER; /* Error text assembly buffer */
291 const char *zStart; /* Text at start of expression */
292 const char *zDelimiter; /* Text between expression terms */
293 const char *zEnd; /* Text at end of expression */
294 const char *zPrefix; /* Text before each match pattern */
295 const char *zSuffix; /* Text after each match pattern */
296 const char *zIntro; /* Text introducing pattern description */
297 const char *zPattern = 0; /* Previous quoted pattern */
298 const char *zFail = 0; /* Current failure message or NULL if okay */
299 const char *zOr = " or "; /* Text before final quoted pattern */
300 char cDel; /* Input delimiter character */
301 int i; /* Input match pattern length counter */
302
303 /* Optimize exact matches by looking up the ID in advance to create a simple
304 * numeric comparison. Bypass the remainder of this function. */
305 if( matchStyle==MS_EXACT ){
306 *zDesc = tagQuote(-1, zTag);
307 return mprintf("(tagid=%d)", db_int(-1,
308 "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag));
309 }
310
311 /* Decide pattern prefix and suffix strings according to match style. */
312 if( matchStyle==MS_GLOB ){
313 zStart = "(";
314 zDelimiter = " OR ";
315 zEnd = ")";
316 zPrefix = "tagname GLOB 'sym-";
317 zSuffix = "'";
318 zIntro = "glob pattern ";
319 }else irt = "(";
320 zDelimiter = " OR ";
321 zEnd = ")";
322 zPrefix = "tagname LIKE 'sym-";
323 zSuffix = "'";
324 zIntro = "SQL LIKE pattern ";
325 }else if( matchStyle==MS_REGEXP ){
326 zStart = "(tagname REGEXP '^sym-(";
327 zDelimiter = "|";
328 zEnd = ")$')";
329 zPrefix = "";
330 zSuffix = "";
331 zIntro = "regular expression ";
332 }else/* if( matchStyle==MS_BRLIST )*/{
333 zStart = "tagname IN ('sym-";
334 zDelimiter = "','sym-";
335 zEnd = "')";
336 zPrefix = "";
337 zSuffix = "";
338 zIntro = "";
339 }
340
341 /* Convert the list of matches into an SQL expression and text description. */
342 blob_zero(&expr);
343 blob_zero(&desc);
344 blob_zero(&err);
345 while( 1 ){
346 /* Skip leading delimiters. */
347 for( ; fossil_isspace(*zTag) || *zTag==','; ++zTag );
348
349 /* Next non-delimiter character determines quoting. */
350 if( !*zTag ){
351 /* Terminate loop at end of string. */
352 break;
353 }else if( *zTag=='\'' || *zTag=='"' ){
354 /* If word is quoted, prepare to stop at end quote. */
355 cDel = *zTag;
356 ++zTag;
357 }else{
358 /* If word is not quoted, prepare to stop at delimiter. */
359 cDel = ',';
360 }
361
362 /* Find the next delimiter character or end of string. */
363 for( i=0; zTag[i] && zTag[i]!=cDel; ++i ){
364 /* If delimiter is comma, also recognize spaces as delimiters. */
365 if( cDel==',' && fossil_isspace(zTag[i]) ){
366 break;
367 }
368
369 /* In regexp mode, ignore delimiters following backslashes. */
370 if( matchStyle==MS_REGEXP && zTag[i]=='\\' && zTag[i+1] ){
371 ++i;
372 }
373 }
374
375 /* Check for regular expression syntax errors. */
376 if( matchStyle==MS_REGEXP ){
377 ReCompiled *regexp;
378 char *zTagDup = fossil_strndup(zTag, i);
379 zFail = fossil_re_compile(&regexp, zTagDup, 0);
380 re_free(regexp);
381 fossil_free(zTagDup);
382 }
383
384 /* Process success and error results. */
385 if( !zFail ){
386 /* Incorporate
+477 -14
--- src/merge.c
+++ src/merge.c
@@ -20,10 +20,372 @@
2020
*/
2121
#include "config.h"
2222
#include "merge.h"
2323
#include <assert.h>
2424
25
+
26
+/*
27
+** Bring up a Tcl/Tk GUI to show details of the most recent merge.
28
+*/
29
+static void merge_info_tk(int bDark, int bAll, int nContext){
30
+ int i;
31
+ Blob script;
32
+ const char *zTempFile = 0;
33
+ char *zCmd;
34
+ const char *zTclsh;
35
+ zTclsh = find_option("tclsh",0,1);
36
+ if( zTclsh==0 ){
37
+ zTclsh = db_get("tclsh",0);
38
+ }
39
+ /* The undocumented --script FILENAME option causes the Tk script to
40
+ ** be written into the FILENAME instead of being run. This is used
41
+ ** for testing and debugging. */
42
+ zTempFile = find_option("script",0,1);
43
+ verify_all_options();
44
+
45
+ blob_zero(&script);
46
+ blob_appendf(&script, "set ncontext %d\n", nContext);
47
+ blob_appendf(&script, "set fossilcmd {| \"%/\" merge-info}\n",
48
+ g.nameOfExe);
49
+ blob_appendf(&script, "set filelist [list");
50
+ if( g.argc==2 ){
51
+ /* No files named on the command-line. Use every file mentioned
52
+ ** in the MERGESTAT table to generate the file list. */
53
+ Stmt q;
54
+ int cnt = 0;
55
+ db_prepare(&q,
56
+ "WITH priority(op,pri) AS (VALUES('CONFLICT',0),('ERROR',0),"
57
+ "('MERGE',1),('ADDED',2),('UPDATE',2))"
58
+ "SELECT coalesce(fnr,fn), op FROM mergestat JOIN priority USING(op)"
59
+ " %s ORDER BY pri, 1",
60
+ bAll ? "" : "WHERE op IN ('MERGE','CONFLICT')" /*safe-for-%s*/
61
+ );
62
+ while( db_step(&q)==SQLITE_ROW ){
63
+ blob_appendf(&script," %s ", db_column_text(&q,1));
64
+ blob_append_tcl_literal(&script, db_column_text(&q,0),
65
+ db_column_bytes(&q,0));
66
+ cnt++;
67
+ }
68
+ db_finalize(&q);
69
+ if( cnt==0 ){
70
+ fossil_print(
71
+ "No interesting changes in this merge. Use --all to see everything\n"
72
+ );
73
+ return;
74
+ }
75
+ }else{
76
+ /* Use only files named on the command-line in the file list.
77
+ ** But verify each file named is actually found in the MERGESTAT
78
+ ** table first. */
79
+ for(i=2; i<g.argc; i++){
80
+ char *zFile; /* Input filename */
81
+ char *zTreename; /* Name of the file in the tree */
82
+ Blob fname; /* Filename relative to root */
83
+ char *zOp; /* Operation on this file */
84
+ zFile = mprintf("%/", g.argv[i]);
85
+ file_tree_name(zFile, &fname, 0, 1);
86
+ fossil_free(zFile);
87
+ zTreename = blob_str(&fname);
88
+ zOp = db_text(0, "SELECT op FROM mergestat WHERE fn=%Q or fnr=%Q",
89
+ zTreename, zTreename);
90
+ blob_appendf(&script, " %s ", zOp);
91
+ fossil_free(zOp);
92
+ blob_append_tcl_literal(&script, zTreename, (int)strlen(zTreename));
93
+ blob_reset(&fname);
94
+ }
95
+ }
96
+ blob_appendf(&script, "]\n");
97
+ blob_appendf(&script, "set darkmode %d\n", bDark!=0);
98
+ blob_appendf(&script, "%s", builtin_file("merge.tcl", 0));
99
+ if( zTempFile ){
100
+ blob_write_to_file(&script, zTempFile);
101
+ fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile);
102
+ }else{
103
+#if defined(FOSSIL_ENABLE_TCL)
104
+ Th_FossilInit(TH_INIT_DEFAULT);
105
+ if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script),
106
+ blob_size(&script), 1, 1, 0)==TCL_OK ){
107
+ blob_reset(&script);
108
+ return;
109
+ }
110
+ /*
111
+ * If evaluation of the Tcl script fails, the reason may be that Tk
112
+ * could not be found by the loaded Tcl, or that Tcl cannot be loaded
113
+ * dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback
114
+ * to using the external "tclsh", if available.
115
+ */
116
+#endif
117
+ zTempFile = write_blob_to_temp_file(&script);
118
+ zCmd = mprintf("%$ %$", zTclsh, zTempFile);
119
+ fossil_system(zCmd);
120
+ file_delete(zTempFile);
121
+ fossil_free(zCmd);
122
+ }
123
+ blob_reset(&script);
124
+}
125
+
126
+/*
127
+** Generate a TCL list on standard output that can be fed into the
128
+** merge.tcl script to show the details of the most recent merge
129
+** command associated with file "zFName". zFName must be the filename
130
+** relative to the root of the check-in - in other words a "tree name".
131
+**
132
+** When this routine is called, we know that the mergestat table
133
+** exists, but we do not know if zFName is mentioned in that table.
134
+*/
135
+static void merge_info_tcl(const char *zFName, int nContext){
136
+ const char *zTreename;/* Name of the file in the tree */
137
+ Stmt q; /* To query the MERGESTAT table */
138
+ MergeBuilder mb; /* The merge builder object */
139
+ Blob pivot,v1,v2,out; /* Blobs for holding content */
140
+ const char *zFN; /* A filename */
141
+ int rid; /* RID value */
142
+ int sz; /* File size value */
143
+
144
+ zTreename = zFName;
145
+ db_prepare(&q,
146
+ /* 0 1 2 3 4 5 6 7 */
147
+ "SELECT fnp, ridp, fn, ridv, sz, fnm, ridm, fnr"
148
+ " FROM mergestat"
149
+ " WHERE fnp=%Q OR fnr=%Q",
150
+ zTreename, zTreename
151
+ );
152
+ if( db_step(&q)!=SQLITE_ROW ){
153
+ db_finalize(&q);
154
+ fossil_print("ERROR {don't know anything about file: %s}\n", zTreename);
155
+ return;
156
+ }
157
+ mergebuilder_init_tcl(&mb);
158
+ mb.nContext = nContext;
159
+
160
+ /* Set up the pivot */
161
+ zFN = db_column_text(&q, 0);
162
+ if( zFN==0 ){
163
+ /* No pivot because the file was added */
164
+ mb.zPivot = "(no baseline)";
165
+ blob_zero(&pivot);
166
+ }else{
167
+ mb.zPivot = mprintf("%s (baseline)", file_tail(zFN));
168
+ rid = db_column_int(&q, 1);
169
+ content_get(rid, &pivot);
170
+ }
171
+ mb.pPivot = &pivot;
172
+
173
+ /* Set up the merge-in as V2 */
174
+ zFN = db_column_text(&q, 5);
175
+ if( zFN==0 ){
176
+ /* File deleted in the merged-in branch */
177
+ mb.zV2 = "(deleted file)";
178
+ blob_zero(&v2);
179
+ }else{
180
+ mb.zV2 = mprintf("%s (merge-in)", file_tail(zFN));
181
+ rid = db_column_int(&q, 6);
182
+ content_get(rid, &v2);
183
+ }
184
+ mb.pV2 = &v2;
185
+
186
+ /* Set up the local content as V1 */
187
+ zFN = db_column_text(&q, 2);
188
+ if( zFN==0 ){
189
+ /* File added by merge */
190
+ mb.zV1 = "(no original)";
191
+ blob_zero(&v1);
192
+ }else{
193
+ mb.zV1 = mprintf("%s (local)", file_tail(zFN));
194
+ rid = db_column_int(&q, 3);
195
+ sz = db_column_int(&q, 4);
196
+ if( rid==0 && sz>0 ){
197
+ /* The origin file had been edited so we'll have to pull its
198
+ ** original content out of the undo buffer */
199
+ Stmt q2;
200
+ db_prepare(&q2,
201
+ "SELECT content FROM undo"
202
+ " WHERE pathname=%Q AND octet_length(content)=%d",
203
+ zFN, sz
204
+ );
205
+ blob_zero(&v1);
206
+ if( db_step(&q2)==SQLITE_ROW ){
207
+ db_column_blob(&q2, 0, &v1);
208
+ }else{
209
+ mb.zV1 = "(local content missing)";
210
+ }
211
+ db_finalize(&q2);
212
+ }else{
213
+ /* The origin file was unchanged when the merge first occurred */
214
+ content_get(rid, &v1);
215
+ }
216
+ }
217
+ mb.pV1 = &v1;
218
+
219
+ /* Set up the output */
220
+ zFN = db_column_text(&q, 7);
221
+ if( zFN==0 ){
222
+ mb.zOut = "(Merge Result)";
223
+ }else{
224
+ mb.zOut = mprintf("%s (after merge)", file_tail(zFN));
225
+ }
226
+ blob_zero(&out);
227
+ mb.pOut = &out;
228
+
229
+ merge_three_blobs(&mb);
230
+ blob_write_to_file(&out, "-");
231
+
232
+ mb.xDestroy(&mb);
233
+ blob_reset(&pivot);
234
+ blob_reset(&v1);
235
+ blob_reset(&v2);
236
+ blob_reset(&out);
237
+ db_finalize(&q);
238
+}
239
+
240
+/*
241
+** COMMAND: merge-info
242
+**
243
+** Usage: %fossil merge-info [OPTIONS]
244
+**
245
+** Display information about the most recent merge operation.
246
+**
247
+** Options:
248
+** -a|--all Show all file changes that happened because of
249
+** the merge. Normally only MERGE, CONFLICT, and ERROR
250
+** lines are shown
251
+** -c|--context N Show N lines of context around each change,
252
+** with negative N meaning show all content. Only
253
+** meaningful in combination with --tcl or --tk.
254
+** --dark Use dark mode for the Tcl/Tk-based GUI
255
+** --tcl FILE Generate (to stdout) a TCL list containing
256
+** information needed to display the changes to
257
+** FILE caused by the most recent merge. FILE must
258
+** be a pathname relative to the root of the check-out.
259
+** --tk Bring up a Tcl/Tk GUI that shows the changes
260
+** associated with the most recent merge.
261
+**
262
+*/
263
+void merge_info_cmd(void){
264
+ const char *zCnt;
265
+ const char *zTcl;
266
+ int bTk;
267
+ int bDark;
268
+ int bAll;
269
+ int nContext;
270
+ Stmt q;
271
+ const char *zWhere;
272
+ int cnt = 0;
273
+
274
+ db_must_be_within_tree();
275
+ zTcl = find_option("tcl", 0, 1);
276
+ bTk = find_option("tk", 0, 0)!=0;
277
+ zCnt = find_option("context", "c", 1);
278
+ bDark = find_option("dark", 0, 0)!=0;
279
+ bAll = find_option("all", "a", 0)!=0;
280
+ if( bTk==0 ){
281
+ verify_all_options();
282
+ if( g.argc>2 ){
283
+ usage("[OPTIONS]");
284
+ }
285
+ }
286
+ if( zCnt ){
287
+ nContext = atoi(zCnt);
288
+ if( nContext<0 ) nContext = 0xfffffff;
289
+ }else{
290
+ nContext = 6;
291
+ }
292
+ if( !db_table_exists("localdb","mergestat") ){
293
+ if( zTcl ){
294
+ fossil_print("ERROR {no merge data available}\n");
295
+ }else{
296
+ fossil_print("No merge data is available\n");
297
+ }
298
+ return;
299
+ }
300
+ if( bTk ){
301
+ merge_info_tk(bDark, bAll, nContext);
302
+ return;
303
+ }
304
+ if( zTcl ){
305
+ merge_info_tcl(zTcl, nContext);
306
+ return;
307
+ }
308
+ if( bAll ){
309
+ zWhere = "";
310
+ }else{
311
+ zWhere = "WHERE op IN ('MERGE','CONFLICT','ERROR')";
312
+ }
313
+ db_prepare(&q,
314
+ "WITH priority(op,pri) AS (VALUES('CONFLICT',0),('ERROR',0),"
315
+ "('MERGE',1),('ADDED',2),('UPDATE',2))"
316
+
317
+ /* 0 1 2 */
318
+ "SELECT op, coalesce(fnr,fn), msg"
319
+ " FROM mergestat JOIN priority USING(op)"
320
+ " %s"
321
+ " ORDER BY pri, coalesce(fnr,fn)",
322
+ zWhere /*safe-for-%s*/
323
+ );
324
+ while( db_step(&q)==SQLITE_ROW ){
325
+ const char *zOp = db_column_text(&q, 0);
326
+ const char *zName = db_column_text(&q, 1);
327
+ const char *zErr = db_column_text(&q, 2);
328
+ if( zErr && fossil_strcmp(zOp,"CONFLICT")!=0 ){
329
+ fossil_print("%-9s %s (%s)\n", zOp, zName, zErr);
330
+ }else{
331
+ fossil_print("%-9s %s\n", zOp, zName);
332
+ }
333
+ cnt++;
334
+ }
335
+ db_finalize(&q);
336
+ if( !bAll && cnt==0 ){
337
+ fossil_print(
338
+ "No interesting changes in this merge. Use --all to see everything.\n"
339
+ );
340
+ }
341
+}
342
+
343
+/*
344
+** Erase all information about prior merges. Do this, for example, after
345
+** a commit.
346
+*/
347
+void merge_info_forget(void){
348
+ db_multi_exec(
349
+ "DROP TABLE IF EXISTS localdb.mergestat;"
350
+ "DELETE FROM localdb.vvar WHERE name glob 'mergestat-*';"
351
+ );
352
+}
353
+
354
+
355
+/*
356
+** Initialize the MERGESTAT table.
357
+**
358
+** Notes about mergestat:
359
+**
360
+** * ridv is a positive integer and sz is NULL if the V file contained
361
+** no local edits prior to the merge. If the V file was modified prior
362
+** to the merge then ridv is NULL and sz is the size of the file prior
363
+** to merge.
364
+**
365
+** * fnp, ridp, fn, ridv, and sz are all NULL for a file that was
366
+** added by merge.
367
+*/
368
+void merge_info_init(void){
369
+ merge_info_forget();
370
+ db_multi_exec(
371
+ "CREATE TABLE localdb.mergestat(\n"
372
+ " op TEXT, -- 'UPDATE', 'ADDED', 'MERGE', etc...\n"
373
+ " fnp TEXT, -- Name of the pivot file (P)\n"
374
+ " ridp INT, -- RID for the pivot file\n"
375
+ " fn TEXT, -- Name of origin file (V)\n"
376
+ " ridv INT, -- RID for origin file, or NULL if previously edited\n"
377
+ " sz INT, -- Size of origin file in bytes, NULL if unedited\n"
378
+ " fnm TEXT, -- Name of the file being merged in (M)\n"
379
+ " ridm INT, -- RID for the merge-in file\n"
380
+ " fnr TEXT, -- Name of the final output file, after all renaming\n"
381
+ " nc INT DEFAULT 0, -- Number of conflicts\n"
382
+ " msg TEXT -- Error message\n"
383
+ ");"
384
+ );
385
+}
386
+
25387
/*
26388
** Print information about a particular check-in.
27389
*/
28390
void print_checkin_description(int rid, int indent, const char *zLabel){
29391
Stmt q;
@@ -295,10 +657,13 @@
295657
** Files which are renamed in the merged-in branch will be renamed in
296658
** the current check-out.
297659
**
298660
** If the VERSION argument is omitted, then Fossil attempts to find
299661
** a recent fork on the current branch to merge.
662
+**
663
+** Note that this command does not commit the merge, as that is a
664
+** separate step.
300665
**
301666
** If there are multiple VERSION arguments, then each VERSION is merged
302667
** (or cherrypicked) in the order that they appear on the command-line.
303668
**
304669
** Options:
@@ -320,12 +685,13 @@
320685
** --force-missing Force the merge even if there is missing content
321686
** --integrate Merged branch will be closed when committing
322687
** -K|--keep-merge-files On merge conflict, retain the temporary files
323688
** used for merging, named *-baseline, *-original,
324689
** and *-merge.
325
-** -n|--dry-run If given, display instead of run actions
690
+** -n|--dry-run Do not actually change files on disk
326691
** --nosync Do not auto-sync prior to merging
692
+** --noundo Do not record changes in the undo log
327693
** -v|--verbose Show additional details of the merge
328694
*/
329695
void merge_cmd(void){
330696
int vid; /* Current version "V" */
331697
int mid; /* Version we are merging from "M" */
@@ -347,10 +713,11 @@
347713
int nOverwrite = 0; /* Number of unmanaged files overwritten */
348714
char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */
349715
const char *zVersion; /* The VERSION argument */
350716
int bMultiMerge = 0; /* True if there are two or more VERSION arguments */
351717
int nMerge = 0; /* Number of prior merges processed */
718
+ int useUndo = 1; /* True to record changes in the undo log */
352719
Stmt q; /* SQL statment used for merge processing */
353720
354721
355722
/* Notation:
356723
**
@@ -395,10 +762,12 @@
395762
** * The --dry-run option is also useful in combination with --debug.
396763
*/
397764
debugFlag = find_option("debug",0,0)!=0;
398765
if( debugFlag && verboseFlag ) debugFlag = 2;
399766
showVfileFlag = find_option("show-vfile",0,0)!=0;
767
+ useUndo = find_option("noundo",0,0)==0;
768
+ if( dryRunFlag ) useUndo = 0;
400769
401770
verify_all_options();
402771
db_must_be_within_tree();
403772
if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
404773
vid = db_lget_int("checkout", 0);
@@ -561,11 +930,11 @@
561930
integrateFlag ? "integrate:" : "merge-from:");
562931
print_checkin_description(pid, 12, "baseline:");
563932
}
564933
vfile_check_signature(vid, CKSIG_ENOTFILE);
565934
if( nMerge==0 ) db_begin_transaction();
566
- if( !dryRunFlag ) undo_begin();
935
+ if( useUndo ) undo_begin();
567936
if( load_vfile_from_rid(mid) && !forceMissingFlag ){
568937
fossil_fatal("missing content, unable to merge");
569938
}
570939
if( load_vfile_from_rid(pid) && !forceMissingFlag ){
571940
fossil_fatal("missing content, unable to merge");
@@ -797,15 +1166,21 @@
7971166
7981167
/************************************************************************
7991168
** All of the information needed to do the merge is now contained in the
8001169
** FV table. Starting here, we begin to actually carry out the merge.
8011170
**
802
- ** First, find files that have changed from P->M but not P->V.
1171
+ ** Begin by constructing the localdb.mergestat table.
1172
+ */
1173
+ merge_info_init();
1174
+
1175
+ /*
1176
+ ** Find files that have changed from P->M but not P->V.
8031177
** Copy the M content over into V.
8041178
*/
8051179
db_prepare(&q,
806
- "SELECT idv, ridm, fn, islinkm FROM fv"
1180
+ /* 0 1 2 3 4 5 6 7 */
1181
+ "SELECT idv, ridm, fn, islinkm, fnp, ridp, ridv, fnm FROM fv"
8071182
" WHERE idp>0 AND idv>0 AND idm>0"
8081183
" AND ridm!=ridp AND ridv=ridp AND NOT chnged"
8091184
);
8101185
while( db_step(&q)==SQLITE_ROW ){
8111186
int idv = db_column_int(&q, 0);
@@ -812,20 +1187,31 @@
8121187
int ridm = db_column_int(&q, 1);
8131188
const char *zName = db_column_text(&q, 2);
8141189
int islinkm = db_column_int(&q, 3);
8151190
/* Copy content from idm over into idv. Overwrite idv. */
8161191
fossil_print("UPDATE %s\n", zName);
1192
+ if( useUndo ) undo_save(zName);
8171193
if( !dryRunFlag ){
818
- undo_save(zName);
8191194
db_multi_exec(
8201195
"UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d,"
8211196
" mhash=CASE WHEN rid<>%d"
8221197
" THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END"
8231198
" WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv
8241199
);
8251200
vfile_to_disk(0, idv, 0, 0);
8261201
}
1202
+ db_multi_exec(
1203
+ "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr)"
1204
+ "VALUES('UPDATE',%Q,%d,%Q,%d,%Q,%d,%Q)",
1205
+ /* fnp */ db_column_text(&q, 4),
1206
+ /* ridp */ db_column_int(&q,5),
1207
+ /* fn */ zName,
1208
+ /* ridv */ db_column_int(&q,6),
1209
+ /* fnm */ db_column_text(&q, 7),
1210
+ /* ridm */ ridm,
1211
+ /* fnr */ zName
1212
+ );
8271213
}
8281214
db_finalize(&q);
8291215
8301216
/*
8311217
** Do a three-way merge on files that have changes on both P->M and P->V.
@@ -833,11 +1219,15 @@
8331219
** Proceed even if the file doesn't exist on P, just like the common ancestor
8341220
** of M and V is an empty file. In this case, merge conflict marks will be
8351221
** added to the file and user will be forced to take a decision.
8361222
*/
8371223
db_prepare(&q,
838
- "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm FROM fv"
1224
+ /* 0 1 2 3 4 5 6 7 8 */
1225
+ "SELECT ridm, idv, ridp, ridv, %z, fn, isexe, islinkv, islinkm,"
1226
+ /* 9 10 11 */
1227
+ " fnp, fnm, chnged"
1228
+ " FROM fv"
8391229
" WHERE idv>0 AND idm>0"
8401230
" AND ridm!=ridp AND (ridv!=ridp OR chnged)",
8411231
glob_expr("fv.fn", zBinGlob)
8421232
);
8431233
while( db_step(&q)==SQLITE_ROW ){
@@ -848,12 +1238,14 @@
8481238
int isBinary = db_column_int(&q, 4);
8491239
const char *zName = db_column_text(&q, 5);
8501240
int isExe = db_column_int(&q, 6);
8511241
int islinkv = db_column_int(&q, 7);
8521242
int islinkm = db_column_int(&q, 8);
1243
+ int chnged = db_column_int(&q, 11);
8531244
int rc;
8541245
char *zFullPath;
1246
+ const char *zType = "MERGE";
8551247
Blob m, p, r;
8561248
/* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */
8571249
if( verboseFlag ){
8581250
fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n",
8591251
zName, ridp, ridm, ridv);
@@ -861,13 +1253,29 @@
8611253
fossil_print("MERGE %s\n", zName);
8621254
}
8631255
if( islinkv || islinkm ){
8641256
fossil_print("***** Cannot merge symlink %s\n", zName);
8651257
nConflict++;
1258
+ db_multi_exec(
1259
+ "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr,nc,msg)"
1260
+ "VALUES('ERROR',%Q,%d,%Q,%d,%Q,%d,%Q,1,'cannot merge symlink')",
1261
+ /* fnp */ db_column_text(&q, 9),
1262
+ /* ridp */ ridp,
1263
+ /* fn */ zName,
1264
+ /* ridv */ ridv,
1265
+ /* fnm */ db_column_text(&q, 10),
1266
+ /* ridm */ ridm,
1267
+ /* fnr */ zName
1268
+ );
8661269
}else{
867
- if( !dryRunFlag ) undo_save(zName);
1270
+ i64 sz;
1271
+ const char *zErrMsg = 0;
1272
+ int nc = 0;
1273
+
1274
+ if( useUndo ) undo_save(zName);
8681275
zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
1276
+ sz = file_size(zFullPath, ExtFILE);
8691277
content_get(ridp, &p);
8701278
content_get(ridm, &m);
8711279
if( isBinary ){
8721280
rc = -1;
8731281
blob_zero(&r);
@@ -884,15 +1292,38 @@
8841292
db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv);
8851293
if( rc>0 ){
8861294
fossil_print("***** %d merge conflict%s in %s\n",
8871295
rc, rc>1 ? "s" : "", zName);
8881296
nConflict++;
1297
+ nc = rc;
1298
+ zErrMsg = "merge conflicts";
1299
+ zType = "CONFLICT";
8891300
}
8901301
}else{
8911302
fossil_print("***** Cannot merge binary file %s\n", zName);
8921303
nConflict++;
1304
+ nc = 1;
1305
+ zErrMsg = "cannot merge binary file";
1306
+ zType = "ERROR";
8931307
}
1308
+ db_multi_exec(
1309
+ "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)"
1310
+ "VALUES(%Q,%Q,%d,%Q,iif(%d,%d,NULL),iif(%d,%lld,NULL),%Q,%d,"
1311
+ "%Q,%d,%Q)",
1312
+ /* op */ zType,
1313
+ /* fnp */ db_column_text(&q, 9),
1314
+ /* ridp */ ridp,
1315
+ /* fn */ zName,
1316
+ /* ridv */ chnged==0, ridv,
1317
+ /* sz */ chnged!=0, sz,
1318
+ /* fnm */ db_column_text(&q, 10),
1319
+ /* ridm */ ridm,
1320
+ /* fnr */ zName,
1321
+ /* nc */ nc,
1322
+ /* msg */ zErrMsg
1323
+ );
1324
+ fossil_free(zFullPath);
8941325
blob_reset(&p);
8951326
blob_reset(&m);
8961327
blob_reset(&r);
8971328
}
8981329
vmerge_insert(idv, ridm);
@@ -901,32 +1332,53 @@
9011332
9021333
/*
9031334
** Drop files that are in P and V but not in M
9041335
*/
9051336
db_prepare(&q,
906
- "SELECT idv, fn, chnged FROM fv"
1337
+ "SELECT idv, fn, chnged, ridv FROM fv"
9071338
" WHERE idp>0 AND idv>0 AND idm=0"
9081339
);
9091340
while( db_step(&q)==SQLITE_ROW ){
9101341
int idv = db_column_int(&q, 0);
9111342
const char *zName = db_column_text(&q, 1);
9121343
int chnged = db_column_int(&q, 2);
1344
+ int ridv = db_column_int(&q, 3);
1345
+ int sz = -1;
1346
+ const char *zErrMsg = 0;
1347
+ int nc = 0;
9131348
/* Delete the file idv */
9141349
fossil_print("DELETE %s\n", zName);
9151350
if( chnged ){
1351
+ char *zFullPath;
9161352
fossil_warning("WARNING: local edits lost for %s", zName);
9171353
nConflict++;
1354
+ ridv = 0;
1355
+ nc = 1;
1356
+ zErrMsg = "local edits lost";
1357
+ zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
1358
+ sz = file_size(zFullPath, ExtFILE);
1359
+ fossil_free(zFullPath);
9181360
}
919
- if( !dryRunFlag ) undo_save(zName);
1361
+ if( useUndo ) undo_save(zName);
9201362
db_multi_exec(
9211363
"UPDATE vfile SET deleted=1 WHERE id=%d", idv
9221364
);
9231365
if( !dryRunFlag ){
9241366
char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
9251367
file_delete(zFullPath);
9261368
free(zFullPath);
9271369
}
1370
+ db_multi_exec(
1371
+ "INSERT INTO localdb.mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,nc,msg)"
1372
+ "VALUES('DELETE',NULL,NULL,%Q,iif(%d,%d,NULL),iif(%d,%d,NULL),"
1373
+ "NULL,NULL,%d,%Q)",
1374
+ /* fn */ zName,
1375
+ /* ridv */ chnged==0, ridv,
1376
+ /* sz */ chnged!=0, sz,
1377
+ /* nc */ nc,
1378
+ /* msg */ zErrMsg
1379
+ );
9281380
}
9291381
db_finalize(&q);
9301382
9311383
/* For certain sets of renames (e.g. A -> B and B -> A), a file that is
9321384
** being renamed must first be moved to a temporary location to avoid
@@ -951,12 +1403,16 @@
9511403
int idv = db_column_int(&q, 0);
9521404
const char *zOldName = db_column_text(&q, 1);
9531405
const char *zNewName = db_column_text(&q, 2);
9541406
int isExe = db_column_int(&q, 3);
9551407
fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
956
- if( !dryRunFlag ) undo_save(zOldName);
957
- if( !dryRunFlag ) undo_save(zNewName);
1408
+ if( useUndo ) undo_save(zOldName);
1409
+ if( useUndo ) undo_save(zNewName);
1410
+ db_multi_exec(
1411
+ "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q",
1412
+ zOldName
1413
+ );
9581414
db_multi_exec(
9591415
"UPDATE vfile SET pathname=NULL, origname=pathname"
9601416
" WHERE vid=%d AND pathname=%Q;"
9611417
"UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)"
9621418
" WHERE id=%d;",
@@ -1006,11 +1462,11 @@
10061462
10071463
/*
10081464
** Insert into V any files that are not in V or P but are in M.
10091465
*/
10101466
db_prepare(&q,
1011
- "SELECT idm, fnm FROM fv"
1467
+ "SELECT idm, fnm, ridm FROM fv"
10121468
" WHERE idp=0 AND idv=0 AND idm>0"
10131469
);
10141470
while( db_step(&q)==SQLITE_ROW ){
10151471
int idm = db_column_int(&q, 0);
10161472
const char *zName;
@@ -1039,12 +1495,19 @@
10391495
nOverwrite++;
10401496
}else{
10411497
fossil_print("ADDED %s\n", zName);
10421498
}
10431499
fossil_free(zFullName);
1500
+ db_multi_exec(
1501
+ "INSERT INTO mergestat(op,fnm,ridm,fnr)"
1502
+ "VALUES('ADDED',%Q,%d,%Q)",
1503
+ /* fnm */ zName,
1504
+ /* ridm */ db_column_int(&q,2),
1505
+ /* fnr */ zName
1506
+ );
1507
+ if( useUndo ) undo_save(zName);
10441508
if( !dryRunFlag ){
1045
- undo_save(zName);
10461509
vfile_to_disk(0, idm, 0, 0);
10471510
}
10481511
}
10491512
db_finalize(&q);
10501513
@@ -1099,9 +1562,9 @@
10991562
}
11001563
if( bMultiMerge && nConflict==0 ){
11011564
nMerge++;
11021565
goto merge_next_child;
11031566
}
1104
- if( !dryRunFlag ) undo_finish();
1567
+ if( useUndo ) undo_finish();
11051568
11061569
db_end_transaction(dryRunFlag);
11071570
}
11081571
11091572
ADDED src/merge.tcl
--- src/merge.c
+++ src/merge.c
@@ -20,10 +20,372 @@
20 */
21 #include "config.h"
22 #include "merge.h"
23 #include <assert.h>
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25 /*
26 ** Print information about a particular check-in.
27 */
28 void print_checkin_description(int rid, int indent, const char *zLabel){
29 Stmt q;
@@ -295,10 +657,13 @@
295 ** Files which are renamed in the merged-in branch will be renamed in
296 ** the current check-out.
297 **
298 ** If the VERSION argument is omitted, then Fossil attempts to find
299 ** a recent fork on the current branch to merge.
 
 
 
300 **
301 ** If there are multiple VERSION arguments, then each VERSION is merged
302 ** (or cherrypicked) in the order that they appear on the command-line.
303 **
304 ** Options:
@@ -320,12 +685,13 @@
320 ** --force-missing Force the merge even if there is missing content
321 ** --integrate Merged branch will be closed when committing
322 ** -K|--keep-merge-files On merge conflict, retain the temporary files
323 ** used for merging, named *-baseline, *-original,
324 ** and *-merge.
325 ** -n|--dry-run If given, display instead of run actions
326 ** --nosync Do not auto-sync prior to merging
 
327 ** -v|--verbose Show additional details of the merge
328 */
329 void merge_cmd(void){
330 int vid; /* Current version "V" */
331 int mid; /* Version we are merging from "M" */
@@ -347,10 +713,11 @@
347 int nOverwrite = 0; /* Number of unmanaged files overwritten */
348 char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */
349 const char *zVersion; /* The VERSION argument */
350 int bMultiMerge = 0; /* True if there are two or more VERSION arguments */
351 int nMerge = 0; /* Number of prior merges processed */
 
352 Stmt q; /* SQL statment used for merge processing */
353
354
355 /* Notation:
356 **
@@ -395,10 +762,12 @@
395 ** * The --dry-run option is also useful in combination with --debug.
396 */
397 debugFlag = find_option("debug",0,0)!=0;
398 if( debugFlag && verboseFlag ) debugFlag = 2;
399 showVfileFlag = find_option("show-vfile",0,0)!=0;
 
 
400
401 verify_all_options();
402 db_must_be_within_tree();
403 if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
404 vid = db_lget_int("checkout", 0);
@@ -561,11 +930,11 @@
561 integrateFlag ? "integrate:" : "merge-from:");
562 print_checkin_description(pid, 12, "baseline:");
563 }
564 vfile_check_signature(vid, CKSIG_ENOTFILE);
565 if( nMerge==0 ) db_begin_transaction();
566 if( !dryRunFlag ) undo_begin();
567 if( load_vfile_from_rid(mid) && !forceMissingFlag ){
568 fossil_fatal("missing content, unable to merge");
569 }
570 if( load_vfile_from_rid(pid) && !forceMissingFlag ){
571 fossil_fatal("missing content, unable to merge");
@@ -797,15 +1166,21 @@
797
798 /************************************************************************
799 ** All of the information needed to do the merge is now contained in the
800 ** FV table. Starting here, we begin to actually carry out the merge.
801 **
802 ** First, find files that have changed from P->M but not P->V.
 
 
 
 
 
803 ** Copy the M content over into V.
804 */
805 db_prepare(&q,
806 "SELECT idv, ridm, fn, islinkm FROM fv"
 
807 " WHERE idp>0 AND idv>0 AND idm>0"
808 " AND ridm!=ridp AND ridv=ridp AND NOT chnged"
809 );
810 while( db_step(&q)==SQLITE_ROW ){
811 int idv = db_column_int(&q, 0);
@@ -812,20 +1187,31 @@
812 int ridm = db_column_int(&q, 1);
813 const char *zName = db_column_text(&q, 2);
814 int islinkm = db_column_int(&q, 3);
815 /* Copy content from idm over into idv. Overwrite idv. */
816 fossil_print("UPDATE %s\n", zName);
 
817 if( !dryRunFlag ){
818 undo_save(zName);
819 db_multi_exec(
820 "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d,"
821 " mhash=CASE WHEN rid<>%d"
822 " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END"
823 " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv
824 );
825 vfile_to_disk(0, idv, 0, 0);
826 }
 
 
 
 
 
 
 
 
 
 
 
827 }
828 db_finalize(&q);
829
830 /*
831 ** Do a three-way merge on files that have changes on both P->M and P->V.
@@ -833,11 +1219,15 @@
833 ** Proceed even if the file doesn't exist on P, just like the common ancestor
834 ** of M and V is an empty file. In this case, merge conflict marks will be
835 ** added to the file and user will be forced to take a decision.
836 */
837 db_prepare(&q,
838 "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm FROM fv"
 
 
 
 
839 " WHERE idv>0 AND idm>0"
840 " AND ridm!=ridp AND (ridv!=ridp OR chnged)",
841 glob_expr("fv.fn", zBinGlob)
842 );
843 while( db_step(&q)==SQLITE_ROW ){
@@ -848,12 +1238,14 @@
848 int isBinary = db_column_int(&q, 4);
849 const char *zName = db_column_text(&q, 5);
850 int isExe = db_column_int(&q, 6);
851 int islinkv = db_column_int(&q, 7);
852 int islinkm = db_column_int(&q, 8);
 
853 int rc;
854 char *zFullPath;
 
855 Blob m, p, r;
856 /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */
857 if( verboseFlag ){
858 fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n",
859 zName, ridp, ridm, ridv);
@@ -861,13 +1253,29 @@
861 fossil_print("MERGE %s\n", zName);
862 }
863 if( islinkv || islinkm ){
864 fossil_print("***** Cannot merge symlink %s\n", zName);
865 nConflict++;
 
 
 
 
 
 
 
 
 
 
 
866 }else{
867 if( !dryRunFlag ) undo_save(zName);
 
 
 
 
868 zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
 
869 content_get(ridp, &p);
870 content_get(ridm, &m);
871 if( isBinary ){
872 rc = -1;
873 blob_zero(&r);
@@ -884,15 +1292,38 @@
884 db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv);
885 if( rc>0 ){
886 fossil_print("***** %d merge conflict%s in %s\n",
887 rc, rc>1 ? "s" : "", zName);
888 nConflict++;
 
 
 
889 }
890 }else{
891 fossil_print("***** Cannot merge binary file %s\n", zName);
892 nConflict++;
 
 
 
893 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
894 blob_reset(&p);
895 blob_reset(&m);
896 blob_reset(&r);
897 }
898 vmerge_insert(idv, ridm);
@@ -901,32 +1332,53 @@
901
902 /*
903 ** Drop files that are in P and V but not in M
904 */
905 db_prepare(&q,
906 "SELECT idv, fn, chnged FROM fv"
907 " WHERE idp>0 AND idv>0 AND idm=0"
908 );
909 while( db_step(&q)==SQLITE_ROW ){
910 int idv = db_column_int(&q, 0);
911 const char *zName = db_column_text(&q, 1);
912 int chnged = db_column_int(&q, 2);
 
 
 
 
913 /* Delete the file idv */
914 fossil_print("DELETE %s\n", zName);
915 if( chnged ){
 
916 fossil_warning("WARNING: local edits lost for %s", zName);
917 nConflict++;
 
 
 
 
 
 
918 }
919 if( !dryRunFlag ) undo_save(zName);
920 db_multi_exec(
921 "UPDATE vfile SET deleted=1 WHERE id=%d", idv
922 );
923 if( !dryRunFlag ){
924 char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
925 file_delete(zFullPath);
926 free(zFullPath);
927 }
 
 
 
 
 
 
 
 
 
 
928 }
929 db_finalize(&q);
930
931 /* For certain sets of renames (e.g. A -> B and B -> A), a file that is
932 ** being renamed must first be moved to a temporary location to avoid
@@ -951,12 +1403,16 @@
951 int idv = db_column_int(&q, 0);
952 const char *zOldName = db_column_text(&q, 1);
953 const char *zNewName = db_column_text(&q, 2);
954 int isExe = db_column_int(&q, 3);
955 fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
956 if( !dryRunFlag ) undo_save(zOldName);
957 if( !dryRunFlag ) undo_save(zNewName);
 
 
 
 
958 db_multi_exec(
959 "UPDATE vfile SET pathname=NULL, origname=pathname"
960 " WHERE vid=%d AND pathname=%Q;"
961 "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)"
962 " WHERE id=%d;",
@@ -1006,11 +1462,11 @@
1006
1007 /*
1008 ** Insert into V any files that are not in V or P but are in M.
1009 */
1010 db_prepare(&q,
1011 "SELECT idm, fnm FROM fv"
1012 " WHERE idp=0 AND idv=0 AND idm>0"
1013 );
1014 while( db_step(&q)==SQLITE_ROW ){
1015 int idm = db_column_int(&q, 0);
1016 const char *zName;
@@ -1039,12 +1495,19 @@
1039 nOverwrite++;
1040 }else{
1041 fossil_print("ADDED %s\n", zName);
1042 }
1043 fossil_free(zFullName);
 
 
 
 
 
 
 
 
1044 if( !dryRunFlag ){
1045 undo_save(zName);
1046 vfile_to_disk(0, idm, 0, 0);
1047 }
1048 }
1049 db_finalize(&q);
1050
@@ -1099,9 +1562,9 @@
1099 }
1100 if( bMultiMerge && nConflict==0 ){
1101 nMerge++;
1102 goto merge_next_child;
1103 }
1104 if( !dryRunFlag ) undo_finish();
1105
1106 db_end_transaction(dryRunFlag);
1107 }
1108
1109 DDED src/merge.tcl
--- src/merge.c
+++ src/merge.c
@@ -20,10 +20,372 @@
20 */
21 #include "config.h"
22 #include "merge.h"
23 #include <assert.h>
24
25
26 /*
27 ** Bring up a Tcl/Tk GUI to show details of the most recent merge.
28 */
29 static void merge_info_tk(int bDark, int bAll, int nContext){
30 int i;
31 Blob script;
32 const char *zTempFile = 0;
33 char *zCmd;
34 const char *zTclsh;
35 zTclsh = find_option("tclsh",0,1);
36 if( zTclsh==0 ){
37 zTclsh = db_get("tclsh",0);
38 }
39 /* The undocumented --script FILENAME option causes the Tk script to
40 ** be written into the FILENAME instead of being run. This is used
41 ** for testing and debugging. */
42 zTempFile = find_option("script",0,1);
43 verify_all_options();
44
45 blob_zero(&script);
46 blob_appendf(&script, "set ncontext %d\n", nContext);
47 blob_appendf(&script, "set fossilcmd {| \"%/\" merge-info}\n",
48 g.nameOfExe);
49 blob_appendf(&script, "set filelist [list");
50 if( g.argc==2 ){
51 /* No files named on the command-line. Use every file mentioned
52 ** in the MERGESTAT table to generate the file list. */
53 Stmt q;
54 int cnt = 0;
55 db_prepare(&q,
56 "WITH priority(op,pri) AS (VALUES('CONFLICT',0),('ERROR',0),"
57 "('MERGE',1),('ADDED',2),('UPDATE',2))"
58 "SELECT coalesce(fnr,fn), op FROM mergestat JOIN priority USING(op)"
59 " %s ORDER BY pri, 1",
60 bAll ? "" : "WHERE op IN ('MERGE','CONFLICT')" /*safe-for-%s*/
61 );
62 while( db_step(&q)==SQLITE_ROW ){
63 blob_appendf(&script," %s ", db_column_text(&q,1));
64 blob_append_tcl_literal(&script, db_column_text(&q,0),
65 db_column_bytes(&q,0));
66 cnt++;
67 }
68 db_finalize(&q);
69 if( cnt==0 ){
70 fossil_print(
71 "No interesting changes in this merge. Use --all to see everything\n"
72 );
73 return;
74 }
75 }else{
76 /* Use only files named on the command-line in the file list.
77 ** But verify each file named is actually found in the MERGESTAT
78 ** table first. */
79 for(i=2; i<g.argc; i++){
80 char *zFile; /* Input filename */
81 char *zTreename; /* Name of the file in the tree */
82 Blob fname; /* Filename relative to root */
83 char *zOp; /* Operation on this file */
84 zFile = mprintf("%/", g.argv[i]);
85 file_tree_name(zFile, &fname, 0, 1);
86 fossil_free(zFile);
87 zTreename = blob_str(&fname);
88 zOp = db_text(0, "SELECT op FROM mergestat WHERE fn=%Q or fnr=%Q",
89 zTreename, zTreename);
90 blob_appendf(&script, " %s ", zOp);
91 fossil_free(zOp);
92 blob_append_tcl_literal(&script, zTreename, (int)strlen(zTreename));
93 blob_reset(&fname);
94 }
95 }
96 blob_appendf(&script, "]\n");
97 blob_appendf(&script, "set darkmode %d\n", bDark!=0);
98 blob_appendf(&script, "%s", builtin_file("merge.tcl", 0));
99 if( zTempFile ){
100 blob_write_to_file(&script, zTempFile);
101 fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile);
102 }else{
103 #if defined(FOSSIL_ENABLE_TCL)
104 Th_FossilInit(TH_INIT_DEFAULT);
105 if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script),
106 blob_size(&script), 1, 1, 0)==TCL_OK ){
107 blob_reset(&script);
108 return;
109 }
110 /*
111 * If evaluation of the Tcl script fails, the reason may be that Tk
112 * could not be found by the loaded Tcl, or that Tcl cannot be loaded
113 * dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback
114 * to using the external "tclsh", if available.
115 */
116 #endif
117 zTempFile = write_blob_to_temp_file(&script);
118 zCmd = mprintf("%$ %$", zTclsh, zTempFile);
119 fossil_system(zCmd);
120 file_delete(zTempFile);
121 fossil_free(zCmd);
122 }
123 blob_reset(&script);
124 }
125
126 /*
127 ** Generate a TCL list on standard output that can be fed into the
128 ** merge.tcl script to show the details of the most recent merge
129 ** command associated with file "zFName". zFName must be the filename
130 ** relative to the root of the check-in - in other words a "tree name".
131 **
132 ** When this routine is called, we know that the mergestat table
133 ** exists, but we do not know if zFName is mentioned in that table.
134 */
135 static void merge_info_tcl(const char *zFName, int nContext){
136 const char *zTreename;/* Name of the file in the tree */
137 Stmt q; /* To query the MERGESTAT table */
138 MergeBuilder mb; /* The merge builder object */
139 Blob pivot,v1,v2,out; /* Blobs for holding content */
140 const char *zFN; /* A filename */
141 int rid; /* RID value */
142 int sz; /* File size value */
143
144 zTreename = zFName;
145 db_prepare(&q,
146 /* 0 1 2 3 4 5 6 7 */
147 "SELECT fnp, ridp, fn, ridv, sz, fnm, ridm, fnr"
148 " FROM mergestat"
149 " WHERE fnp=%Q OR fnr=%Q",
150 zTreename, zTreename
151 );
152 if( db_step(&q)!=SQLITE_ROW ){
153 db_finalize(&q);
154 fossil_print("ERROR {don't know anything about file: %s}\n", zTreename);
155 return;
156 }
157 mergebuilder_init_tcl(&mb);
158 mb.nContext = nContext;
159
160 /* Set up the pivot */
161 zFN = db_column_text(&q, 0);
162 if( zFN==0 ){
163 /* No pivot because the file was added */
164 mb.zPivot = "(no baseline)";
165 blob_zero(&pivot);
166 }else{
167 mb.zPivot = mprintf("%s (baseline)", file_tail(zFN));
168 rid = db_column_int(&q, 1);
169 content_get(rid, &pivot);
170 }
171 mb.pPivot = &pivot;
172
173 /* Set up the merge-in as V2 */
174 zFN = db_column_text(&q, 5);
175 if( zFN==0 ){
176 /* File deleted in the merged-in branch */
177 mb.zV2 = "(deleted file)";
178 blob_zero(&v2);
179 }else{
180 mb.zV2 = mprintf("%s (merge-in)", file_tail(zFN));
181 rid = db_column_int(&q, 6);
182 content_get(rid, &v2);
183 }
184 mb.pV2 = &v2;
185
186 /* Set up the local content as V1 */
187 zFN = db_column_text(&q, 2);
188 if( zFN==0 ){
189 /* File added by merge */
190 mb.zV1 = "(no original)";
191 blob_zero(&v1);
192 }else{
193 mb.zV1 = mprintf("%s (local)", file_tail(zFN));
194 rid = db_column_int(&q, 3);
195 sz = db_column_int(&q, 4);
196 if( rid==0 && sz>0 ){
197 /* The origin file had been edited so we'll have to pull its
198 ** original content out of the undo buffer */
199 Stmt q2;
200 db_prepare(&q2,
201 "SELECT content FROM undo"
202 " WHERE pathname=%Q AND octet_length(content)=%d",
203 zFN, sz
204 );
205 blob_zero(&v1);
206 if( db_step(&q2)==SQLITE_ROW ){
207 db_column_blob(&q2, 0, &v1);
208 }else{
209 mb.zV1 = "(local content missing)";
210 }
211 db_finalize(&q2);
212 }else{
213 /* The origin file was unchanged when the merge first occurred */
214 content_get(rid, &v1);
215 }
216 }
217 mb.pV1 = &v1;
218
219 /* Set up the output */
220 zFN = db_column_text(&q, 7);
221 if( zFN==0 ){
222 mb.zOut = "(Merge Result)";
223 }else{
224 mb.zOut = mprintf("%s (after merge)", file_tail(zFN));
225 }
226 blob_zero(&out);
227 mb.pOut = &out;
228
229 merge_three_blobs(&mb);
230 blob_write_to_file(&out, "-");
231
232 mb.xDestroy(&mb);
233 blob_reset(&pivot);
234 blob_reset(&v1);
235 blob_reset(&v2);
236 blob_reset(&out);
237 db_finalize(&q);
238 }
239
240 /*
241 ** COMMAND: merge-info
242 **
243 ** Usage: %fossil merge-info [OPTIONS]
244 **
245 ** Display information about the most recent merge operation.
246 **
247 ** Options:
248 ** -a|--all Show all file changes that happened because of
249 ** the merge. Normally only MERGE, CONFLICT, and ERROR
250 ** lines are shown
251 ** -c|--context N Show N lines of context around each change,
252 ** with negative N meaning show all content. Only
253 ** meaningful in combination with --tcl or --tk.
254 ** --dark Use dark mode for the Tcl/Tk-based GUI
255 ** --tcl FILE Generate (to stdout) a TCL list containing
256 ** information needed to display the changes to
257 ** FILE caused by the most recent merge. FILE must
258 ** be a pathname relative to the root of the check-out.
259 ** --tk Bring up a Tcl/Tk GUI that shows the changes
260 ** associated with the most recent merge.
261 **
262 */
263 void merge_info_cmd(void){
264 const char *zCnt;
265 const char *zTcl;
266 int bTk;
267 int bDark;
268 int bAll;
269 int nContext;
270 Stmt q;
271 const char *zWhere;
272 int cnt = 0;
273
274 db_must_be_within_tree();
275 zTcl = find_option("tcl", 0, 1);
276 bTk = find_option("tk", 0, 0)!=0;
277 zCnt = find_option("context", "c", 1);
278 bDark = find_option("dark", 0, 0)!=0;
279 bAll = find_option("all", "a", 0)!=0;
280 if( bTk==0 ){
281 verify_all_options();
282 if( g.argc>2 ){
283 usage("[OPTIONS]");
284 }
285 }
286 if( zCnt ){
287 nContext = atoi(zCnt);
288 if( nContext<0 ) nContext = 0xfffffff;
289 }else{
290 nContext = 6;
291 }
292 if( !db_table_exists("localdb","mergestat") ){
293 if( zTcl ){
294 fossil_print("ERROR {no merge data available}\n");
295 }else{
296 fossil_print("No merge data is available\n");
297 }
298 return;
299 }
300 if( bTk ){
301 merge_info_tk(bDark, bAll, nContext);
302 return;
303 }
304 if( zTcl ){
305 merge_info_tcl(zTcl, nContext);
306 return;
307 }
308 if( bAll ){
309 zWhere = "";
310 }else{
311 zWhere = "WHERE op IN ('MERGE','CONFLICT','ERROR')";
312 }
313 db_prepare(&q,
314 "WITH priority(op,pri) AS (VALUES('CONFLICT',0),('ERROR',0),"
315 "('MERGE',1),('ADDED',2),('UPDATE',2))"
316
317 /* 0 1 2 */
318 "SELECT op, coalesce(fnr,fn), msg"
319 " FROM mergestat JOIN priority USING(op)"
320 " %s"
321 " ORDER BY pri, coalesce(fnr,fn)",
322 zWhere /*safe-for-%s*/
323 );
324 while( db_step(&q)==SQLITE_ROW ){
325 const char *zOp = db_column_text(&q, 0);
326 const char *zName = db_column_text(&q, 1);
327 const char *zErr = db_column_text(&q, 2);
328 if( zErr && fossil_strcmp(zOp,"CONFLICT")!=0 ){
329 fossil_print("%-9s %s (%s)\n", zOp, zName, zErr);
330 }else{
331 fossil_print("%-9s %s\n", zOp, zName);
332 }
333 cnt++;
334 }
335 db_finalize(&q);
336 if( !bAll && cnt==0 ){
337 fossil_print(
338 "No interesting changes in this merge. Use --all to see everything.\n"
339 );
340 }
341 }
342
343 /*
344 ** Erase all information about prior merges. Do this, for example, after
345 ** a commit.
346 */
347 void merge_info_forget(void){
348 db_multi_exec(
349 "DROP TABLE IF EXISTS localdb.mergestat;"
350 "DELETE FROM localdb.vvar WHERE name glob 'mergestat-*';"
351 );
352 }
353
354
355 /*
356 ** Initialize the MERGESTAT table.
357 **
358 ** Notes about mergestat:
359 **
360 ** * ridv is a positive integer and sz is NULL if the V file contained
361 ** no local edits prior to the merge. If the V file was modified prior
362 ** to the merge then ridv is NULL and sz is the size of the file prior
363 ** to merge.
364 **
365 ** * fnp, ridp, fn, ridv, and sz are all NULL for a file that was
366 ** added by merge.
367 */
368 void merge_info_init(void){
369 merge_info_forget();
370 db_multi_exec(
371 "CREATE TABLE localdb.mergestat(\n"
372 " op TEXT, -- 'UPDATE', 'ADDED', 'MERGE', etc...\n"
373 " fnp TEXT, -- Name of the pivot file (P)\n"
374 " ridp INT, -- RID for the pivot file\n"
375 " fn TEXT, -- Name of origin file (V)\n"
376 " ridv INT, -- RID for origin file, or NULL if previously edited\n"
377 " sz INT, -- Size of origin file in bytes, NULL if unedited\n"
378 " fnm TEXT, -- Name of the file being merged in (M)\n"
379 " ridm INT, -- RID for the merge-in file\n"
380 " fnr TEXT, -- Name of the final output file, after all renaming\n"
381 " nc INT DEFAULT 0, -- Number of conflicts\n"
382 " msg TEXT -- Error message\n"
383 ");"
384 );
385 }
386
387 /*
388 ** Print information about a particular check-in.
389 */
390 void print_checkin_description(int rid, int indent, const char *zLabel){
391 Stmt q;
@@ -295,10 +657,13 @@
657 ** Files which are renamed in the merged-in branch will be renamed in
658 ** the current check-out.
659 **
660 ** If the VERSION argument is omitted, then Fossil attempts to find
661 ** a recent fork on the current branch to merge.
662 **
663 ** Note that this command does not commit the merge, as that is a
664 ** separate step.
665 **
666 ** If there are multiple VERSION arguments, then each VERSION is merged
667 ** (or cherrypicked) in the order that they appear on the command-line.
668 **
669 ** Options:
@@ -320,12 +685,13 @@
685 ** --force-missing Force the merge even if there is missing content
686 ** --integrate Merged branch will be closed when committing
687 ** -K|--keep-merge-files On merge conflict, retain the temporary files
688 ** used for merging, named *-baseline, *-original,
689 ** and *-merge.
690 ** -n|--dry-run Do not actually change files on disk
691 ** --nosync Do not auto-sync prior to merging
692 ** --noundo Do not record changes in the undo log
693 ** -v|--verbose Show additional details of the merge
694 */
695 void merge_cmd(void){
696 int vid; /* Current version "V" */
697 int mid; /* Version we are merging from "M" */
@@ -347,10 +713,11 @@
713 int nOverwrite = 0; /* Number of unmanaged files overwritten */
714 char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */
715 const char *zVersion; /* The VERSION argument */
716 int bMultiMerge = 0; /* True if there are two or more VERSION arguments */
717 int nMerge = 0; /* Number of prior merges processed */
718 int useUndo = 1; /* True to record changes in the undo log */
719 Stmt q; /* SQL statment used for merge processing */
720
721
722 /* Notation:
723 **
@@ -395,10 +762,12 @@
762 ** * The --dry-run option is also useful in combination with --debug.
763 */
764 debugFlag = find_option("debug",0,0)!=0;
765 if( debugFlag && verboseFlag ) debugFlag = 2;
766 showVfileFlag = find_option("show-vfile",0,0)!=0;
767 useUndo = find_option("noundo",0,0)==0;
768 if( dryRunFlag ) useUndo = 0;
769
770 verify_all_options();
771 db_must_be_within_tree();
772 if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
773 vid = db_lget_int("checkout", 0);
@@ -561,11 +930,11 @@
930 integrateFlag ? "integrate:" : "merge-from:");
931 print_checkin_description(pid, 12, "baseline:");
932 }
933 vfile_check_signature(vid, CKSIG_ENOTFILE);
934 if( nMerge==0 ) db_begin_transaction();
935 if( useUndo ) undo_begin();
936 if( load_vfile_from_rid(mid) && !forceMissingFlag ){
937 fossil_fatal("missing content, unable to merge");
938 }
939 if( load_vfile_from_rid(pid) && !forceMissingFlag ){
940 fossil_fatal("missing content, unable to merge");
@@ -797,15 +1166,21 @@
1166
1167 /************************************************************************
1168 ** All of the information needed to do the merge is now contained in the
1169 ** FV table. Starting here, we begin to actually carry out the merge.
1170 **
1171 ** Begin by constructing the localdb.mergestat table.
1172 */
1173 merge_info_init();
1174
1175 /*
1176 ** Find files that have changed from P->M but not P->V.
1177 ** Copy the M content over into V.
1178 */
1179 db_prepare(&q,
1180 /* 0 1 2 3 4 5 6 7 */
1181 "SELECT idv, ridm, fn, islinkm, fnp, ridp, ridv, fnm FROM fv"
1182 " WHERE idp>0 AND idv>0 AND idm>0"
1183 " AND ridm!=ridp AND ridv=ridp AND NOT chnged"
1184 );
1185 while( db_step(&q)==SQLITE_ROW ){
1186 int idv = db_column_int(&q, 0);
@@ -812,20 +1187,31 @@
1187 int ridm = db_column_int(&q, 1);
1188 const char *zName = db_column_text(&q, 2);
1189 int islinkm = db_column_int(&q, 3);
1190 /* Copy content from idm over into idv. Overwrite idv. */
1191 fossil_print("UPDATE %s\n", zName);
1192 if( useUndo ) undo_save(zName);
1193 if( !dryRunFlag ){
 
1194 db_multi_exec(
1195 "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d,"
1196 " mhash=CASE WHEN rid<>%d"
1197 " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END"
1198 " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv
1199 );
1200 vfile_to_disk(0, idv, 0, 0);
1201 }
1202 db_multi_exec(
1203 "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr)"
1204 "VALUES('UPDATE',%Q,%d,%Q,%d,%Q,%d,%Q)",
1205 /* fnp */ db_column_text(&q, 4),
1206 /* ridp */ db_column_int(&q,5),
1207 /* fn */ zName,
1208 /* ridv */ db_column_int(&q,6),
1209 /* fnm */ db_column_text(&q, 7),
1210 /* ridm */ ridm,
1211 /* fnr */ zName
1212 );
1213 }
1214 db_finalize(&q);
1215
1216 /*
1217 ** Do a three-way merge on files that have changes on both P->M and P->V.
@@ -833,11 +1219,15 @@
1219 ** Proceed even if the file doesn't exist on P, just like the common ancestor
1220 ** of M and V is an empty file. In this case, merge conflict marks will be
1221 ** added to the file and user will be forced to take a decision.
1222 */
1223 db_prepare(&q,
1224 /* 0 1 2 3 4 5 6 7 8 */
1225 "SELECT ridm, idv, ridp, ridv, %z, fn, isexe, islinkv, islinkm,"
1226 /* 9 10 11 */
1227 " fnp, fnm, chnged"
1228 " FROM fv"
1229 " WHERE idv>0 AND idm>0"
1230 " AND ridm!=ridp AND (ridv!=ridp OR chnged)",
1231 glob_expr("fv.fn", zBinGlob)
1232 );
1233 while( db_step(&q)==SQLITE_ROW ){
@@ -848,12 +1238,14 @@
1238 int isBinary = db_column_int(&q, 4);
1239 const char *zName = db_column_text(&q, 5);
1240 int isExe = db_column_int(&q, 6);
1241 int islinkv = db_column_int(&q, 7);
1242 int islinkm = db_column_int(&q, 8);
1243 int chnged = db_column_int(&q, 11);
1244 int rc;
1245 char *zFullPath;
1246 const char *zType = "MERGE";
1247 Blob m, p, r;
1248 /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */
1249 if( verboseFlag ){
1250 fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n",
1251 zName, ridp, ridm, ridv);
@@ -861,13 +1253,29 @@
1253 fossil_print("MERGE %s\n", zName);
1254 }
1255 if( islinkv || islinkm ){
1256 fossil_print("***** Cannot merge symlink %s\n", zName);
1257 nConflict++;
1258 db_multi_exec(
1259 "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr,nc,msg)"
1260 "VALUES('ERROR',%Q,%d,%Q,%d,%Q,%d,%Q,1,'cannot merge symlink')",
1261 /* fnp */ db_column_text(&q, 9),
1262 /* ridp */ ridp,
1263 /* fn */ zName,
1264 /* ridv */ ridv,
1265 /* fnm */ db_column_text(&q, 10),
1266 /* ridm */ ridm,
1267 /* fnr */ zName
1268 );
1269 }else{
1270 i64 sz;
1271 const char *zErrMsg = 0;
1272 int nc = 0;
1273
1274 if( useUndo ) undo_save(zName);
1275 zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
1276 sz = file_size(zFullPath, ExtFILE);
1277 content_get(ridp, &p);
1278 content_get(ridm, &m);
1279 if( isBinary ){
1280 rc = -1;
1281 blob_zero(&r);
@@ -884,15 +1292,38 @@
1292 db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv);
1293 if( rc>0 ){
1294 fossil_print("***** %d merge conflict%s in %s\n",
1295 rc, rc>1 ? "s" : "", zName);
1296 nConflict++;
1297 nc = rc;
1298 zErrMsg = "merge conflicts";
1299 zType = "CONFLICT";
1300 }
1301 }else{
1302 fossil_print("***** Cannot merge binary file %s\n", zName);
1303 nConflict++;
1304 nc = 1;
1305 zErrMsg = "cannot merge binary file";
1306 zType = "ERROR";
1307 }
1308 db_multi_exec(
1309 "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)"
1310 "VALUES(%Q,%Q,%d,%Q,iif(%d,%d,NULL),iif(%d,%lld,NULL),%Q,%d,"
1311 "%Q,%d,%Q)",
1312 /* op */ zType,
1313 /* fnp */ db_column_text(&q, 9),
1314 /* ridp */ ridp,
1315 /* fn */ zName,
1316 /* ridv */ chnged==0, ridv,
1317 /* sz */ chnged!=0, sz,
1318 /* fnm */ db_column_text(&q, 10),
1319 /* ridm */ ridm,
1320 /* fnr */ zName,
1321 /* nc */ nc,
1322 /* msg */ zErrMsg
1323 );
1324 fossil_free(zFullPath);
1325 blob_reset(&p);
1326 blob_reset(&m);
1327 blob_reset(&r);
1328 }
1329 vmerge_insert(idv, ridm);
@@ -901,32 +1332,53 @@
1332
1333 /*
1334 ** Drop files that are in P and V but not in M
1335 */
1336 db_prepare(&q,
1337 "SELECT idv, fn, chnged, ridv FROM fv"
1338 " WHERE idp>0 AND idv>0 AND idm=0"
1339 );
1340 while( db_step(&q)==SQLITE_ROW ){
1341 int idv = db_column_int(&q, 0);
1342 const char *zName = db_column_text(&q, 1);
1343 int chnged = db_column_int(&q, 2);
1344 int ridv = db_column_int(&q, 3);
1345 int sz = -1;
1346 const char *zErrMsg = 0;
1347 int nc = 0;
1348 /* Delete the file idv */
1349 fossil_print("DELETE %s\n", zName);
1350 if( chnged ){
1351 char *zFullPath;
1352 fossil_warning("WARNING: local edits lost for %s", zName);
1353 nConflict++;
1354 ridv = 0;
1355 nc = 1;
1356 zErrMsg = "local edits lost";
1357 zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
1358 sz = file_size(zFullPath, ExtFILE);
1359 fossil_free(zFullPath);
1360 }
1361 if( useUndo ) undo_save(zName);
1362 db_multi_exec(
1363 "UPDATE vfile SET deleted=1 WHERE id=%d", idv
1364 );
1365 if( !dryRunFlag ){
1366 char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
1367 file_delete(zFullPath);
1368 free(zFullPath);
1369 }
1370 db_multi_exec(
1371 "INSERT INTO localdb.mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,nc,msg)"
1372 "VALUES('DELETE',NULL,NULL,%Q,iif(%d,%d,NULL),iif(%d,%d,NULL),"
1373 "NULL,NULL,%d,%Q)",
1374 /* fn */ zName,
1375 /* ridv */ chnged==0, ridv,
1376 /* sz */ chnged!=0, sz,
1377 /* nc */ nc,
1378 /* msg */ zErrMsg
1379 );
1380 }
1381 db_finalize(&q);
1382
1383 /* For certain sets of renames (e.g. A -> B and B -> A), a file that is
1384 ** being renamed must first be moved to a temporary location to avoid
@@ -951,12 +1403,16 @@
1403 int idv = db_column_int(&q, 0);
1404 const char *zOldName = db_column_text(&q, 1);
1405 const char *zNewName = db_column_text(&q, 2);
1406 int isExe = db_column_int(&q, 3);
1407 fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
1408 if( useUndo ) undo_save(zOldName);
1409 if( useUndo ) undo_save(zNewName);
1410 db_multi_exec(
1411 "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q",
1412 zOldName
1413 );
1414 db_multi_exec(
1415 "UPDATE vfile SET pathname=NULL, origname=pathname"
1416 " WHERE vid=%d AND pathname=%Q;"
1417 "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)"
1418 " WHERE id=%d;",
@@ -1006,11 +1462,11 @@
1462
1463 /*
1464 ** Insert into V any files that are not in V or P but are in M.
1465 */
1466 db_prepare(&q,
1467 "SELECT idm, fnm, ridm FROM fv"
1468 " WHERE idp=0 AND idv=0 AND idm>0"
1469 );
1470 while( db_step(&q)==SQLITE_ROW ){
1471 int idm = db_column_int(&q, 0);
1472 const char *zName;
@@ -1039,12 +1495,19 @@
1495 nOverwrite++;
1496 }else{
1497 fossil_print("ADDED %s\n", zName);
1498 }
1499 fossil_free(zFullName);
1500 db_multi_exec(
1501 "INSERT INTO mergestat(op,fnm,ridm,fnr)"
1502 "VALUES('ADDED',%Q,%d,%Q)",
1503 /* fnm */ zName,
1504 /* ridm */ db_column_int(&q,2),
1505 /* fnr */ zName
1506 );
1507 if( useUndo ) undo_save(zName);
1508 if( !dryRunFlag ){
 
1509 vfile_to_disk(0, idm, 0, 0);
1510 }
1511 }
1512 db_finalize(&q);
1513
@@ -1099,9 +1562,9 @@
1562 }
1563 if( bMultiMerge && nConflict==0 ){
1564 nMerge++;
1565 goto merge_next_child;
1566 }
1567 if( useUndo ) undo_finish();
1568
1569 db_end_transaction(dryRunFlag);
1570 }
1571
1572 DDED src/merge.tcl
+581
--- a/src/merge.tcl
+++ b/src/merge.tcl
@@ -0,0 +1,581 @@
1
+# Show details of a 3-way merge operation. The left-most column is the
2
+# common ancestor. The next two columns are edits of that common ancestor.
3
+# The right-most column is the result of the merge.
4
+#
5
+# There is always a "fossilcmd" variable which tells the script how to
6
+# invoke Fossil to get the information it needs. This script will
7
+# automatically append "-c N" to tell Fossil how much context it wants. True for debugging output
8
+#
9
+# If the "filelist" global variable is defined, then it is a list of
10
+# alternating "merge-type names" (ex: UPDATE, MERGE, CONFLICT, ERROR) and
11
+# filenames. In that case, the initial display shows the changes for
12
+# the first pair on the list and there is a optionmenu that allows the
13
+# useelect otheere should also be a global variable named "ncontext" which is the
14
+# number of lines of context to display. The value of this variable
15
+# controls the "-c N" argument that is appended to fossilcmdht {
16
+ TITLE {Fossil Merge}
17
+ LN_COL_BG #dddddd
18
+ LN_COL_FG #444444
19
+ TXT_COL_BG #ffffff
20
+ TXT_COL_FG #000000
21
+ MKR_COL_BG #444444
22
+ MKR_COL_FG #dddddd
23
+ CHNG_BG #d0d070
24
+ ADD_BG #c0ffc0
25
+ RM_BG #ffc0c0
26
+ HR_FG #444444
27
+ HR_PAD_TOP 4
28
+ HR_PAD_BTM 8
29
+ FN_BG #444444
30
+ FN_FG #ffffff
31
+ FN_PAD 5
32
+ ERR_FG #ee0000
33
+ PADX 5
34
+ WIDTH 80
35
+ HEIGHT 45
36
+ LB_HEIGHT 25
37
+}
38
+
39
+array set CFG_dark {
40
+ TITLE {Fossil Merge}
41
+ LN_COL_BG #dddddd
42
+ LN_COL_FG #444444
43
+ TXT_COL_BG #3f3f3f
44
+ TXT_COL_FG #dcdccc
45
+ MKR_COL_BG #444444
46
+ MKR_COL_FG #dddddd
47
+ CHNG_BG #6a6a00
48
+ ADD_BG #57934c
49
+ RM_BG #ef6767
50
+ HR_FG #444444
51
+ HR_PAD_TOP 4
52
+ HR_PAD_BTM 8
53
+ FN_BG #5e5e5e
54
+ FN_FG #ffffff
55
+ FN_PAD 5
56
+ ERR_FG #ee0000
57
+ PADX 5
58
+ WIDTH 80
59
+ HEIGHT 45
60
+ LB_HEIGHT 25
61
+}
62
+
63
+array set CFG_arr {
64
+ 0 CFG_light
65
+ 1 CFG_dark
66
+}
67
+
68
+array set CFG [array get $CFG_arr($darkmode)]
69
+
70
+if {![namespace exists ttk]} {
71
+ interp alias {} ::ttk::scrollbar {} ::scrollbar
72
+ interp alias {} ::ttk::menubutton {} ::menubutton
73
+}
74
+
75
+proc dehtml {x} {
76
+ set x [regsub -all {<[^>]*>} $x {}]
77
+ return [string map {&amp; & &lt; < &gt; > &#39; ' &quot; \"} $x]
78
+}
79
+
80
+proc cols {} {
81
+ return [list .lnA .txtA .lnB .txtB .lnC .txtC .lnD .txtD]
82
+}
83
+
84
+proc colType {c} {
85
+ regexp {[a-z]+} $c type
86
+ return $type
87
+}
88
+
89
+proc readMercmdes currentails of a 3-way merge operation# Show det for
90
+# the first pair on the list and there is a optionmenu that allows the
91
+# user to select other fiels on the list.
92
+#
93
+# This header comment is stripped off by the "mkbuiltin.c" program.
94
+#
95
+package require Tk
96
+
97
+array set CFG_light {
98
+ TITLE {Fossil Merge}
99
+ LN_COL_BG #dddddd
100
+ LN_COL_FG #444444
101
+ TXT_COL_BG #ffffff
102
+ TXT_COL_FG #000000
103
+ MKR_COL_BG #444444
104
+ MKR_COL_FG #dddddd
105
+ CHNG_BG #d0d070
106
+ ADD_BG #c0ffc0
107
+ RM_BG #ffc0c0
108
+ HR_FG #444444
109
+ HR_PAD_TOP 4
110
+ HR_PAD_BTM 8
111
+ FN_BG #444444
112
+ FN_FG #ffffff
113
+ FN_PAD 5
114
+ ERR_FG #ee0000
115
+ PADX 5
116
+ WIDTH 80
117
+ HEIGHT 45
118
+ LB_HEIGHT 25
119
+}
120
+
121
+array set CFG_dark {
122
+ TITLE {Fossil Merge}
123
+ LN_COL_BG #dddddd
124
+ LN_COL_FG #444444
125
+ TXT_COL_BG #3f3f3f
126
+ TXT_COL_FG #dcdccc
127
+ MKR_COL_BG #444444
128
+ MKR_COL_FG #dddddd
129
+ CHNG_BG #6a6a00
130
+ ADD_BG #57934c
131
+ RM_BG #ef6767
132
+ HR_FG #444444
133
+ HR_PAD_TOP 4
134
+ HR_PAD_BTM 8
135
+ FN_BG #5e5e5e
136
+ FN_FG #ffffff
137
+ FN_PAD 5
138
+ ERR_FG #ee0000
139
+ PADX 5
140
+ WIDTH 80
141
+ HEIGHT 45
142
+ LB_HEIGHT 25
143
+}
144
+
145
+array set CFG_arr {
146
+ 0 CFG_light
147
+ 1 CFG_dark
148
+}
149
+
150
+array set CFG [array get $CFG_arr($darkmode)]
151
+
152
+if {![namespace exists ttk]} {
153
+ interp alias {} ::ttk::scrollbar {} ::scrollbar
154
+ interp alias {} ::ttk::menubutton {} ::menubutton
155
+}
156
+
157
+proc dehtml {x} {
158
+ set x [regsub -all {<[^>]*>} $x {}]
159
+ return [string map {&amp; & &lt; < &gt; > &#39; ' &quot; \"} $x]
160
+}
161
+
162
+proc cols {} {
163
+ return [list .lnA .txtA .lnB .txtB .lnC .txtC .lnD .txtD]
164
+}
165
+
166
+proc colType {c} {
167
+ regexp {[a-z]+} $c type
168
+ return $type
169
+}
170
+
171
+proc readMerge {args} {
172
+ global fossilexe ncontext current_file debug
173
+ if {$ncontext=="All"} {
174
+ set cmd "| $fossilexe merge-info -c -1"
175
+ } else {
176
+ set cmd "| $fossilexe merge-info -c $ncontext"
177
+ }
178
+ if {[info exists current_file]} {
179
+ regsub {^[A-Z]+ } $current_file {} fn
180
+ lappend cmd -tcl $fn
181
+ }
182
+ if {$debug} {
183
+ regsub {^\| +} $cmd {} cmd2
184
+ puts $cmd2
185
+ flush stdout
186
+ }
187
+ if {[catch {
188
+ set in [open $cmd r]
189
+ fconfigure $in -encoding utf-8
190
+ set mergetxt [read $in]
191
+ close $in
192
+ } msg]} {
193
+ tk_messageBox -message "Unable to run command: \"$cmd\""
194
+ set mergetxt {}
195
+ }
196
+ foreach c [cols] {
197
+ $c config -state normal
198
+ $c delete 1.0 end
199
+ }
200
+ set lnA 1
201
+ set lnB 1
202
+ set lnC 1
203
+ set lnD 1
204
+ foreach {A B C D} $mergetxt {
205
+ set key1 [string index $A 0]
206
+ if {$key1=="S"} {
207
+ scan [string range $A 1 end] "%d %d %d %d" nA nB nC nD
208
+ foreach x {A B C D} {
209
+ set N [set n$x]
210
+ incr ln$x $N
211
+ if {$N>0} {
212
+ .ln$x insert end ...\n hrln
213
+ .txt$x insert end [string repeat . 30]\n hrtxt
214
+ } else {
215
+ .ln$x insert end \n hrln
216
+ .txt$x insert end \n hrtxt
217
+ }
218
+ }
219
+ continue
220
+ }
221
+ set key2 [string index $B 0]
222
+ set key3 [string index $C 0]
223
+ set key4 [string index $D 0]
224
+ if {$key1=="."} {
225
+ .lnA insert end \n -
226
+ .txtA insert end \n -
227
+ } elseif {$key1=="N"} {
228
+ .nameA config -text [string range $A 1 end]
229
+ } else {
230
+ .lnA insert end $lnA\n -
231
+ incr lnA
232
+ if {$key1=="X"} {
233
+ .txtA insert end [string range $A 1 end]\n rm
234
+ } else {
235
+ .txtA insert end [string range $A 1 end]\n -
236
+ }
237
+ }
238
+ if {$key2=="."} {
239
+ .lnB insert end \n -
240
+ .txtB insert end \n -
241
+ } elseif {$key2=="N"} {
242
+ .nameB config -text [string range $B 1 end]
243
+ } else {
244
+ .lnB insert end $lnB\n -
245
+ incr lnB
246
+ if {$key4=="2"} {set tag chng} {set tag -}
247
+ if {$key2=="1"} {
248
+ .txtB insert end [string range $A 1 end]\n $tag
249
+ } elseif {$key2=="X"} {
250
+ .txtB insert end [string range $B 1 end]\n rm
251
+ } else {
252
+ .txtB insert end [string range $B 1 end]\n $tag
253
+ }
254
+ }
255
+ if {$key3=="."} {
256
+ .lnC insert end \n -
257
+ .txtC insert end \n -
258
+ } elseif {$key3=="N"} {
259
+ .nameC config -text [string range $C 1 end]
260
+ } else {
261
+ .lnC insert end $lnC\n -
262
+ incr lnC
263
+ if {$key4=="3"} {set tag add} {set tag -}
264
+ if {$key3=="1"} {
265
+ .txtC insert end [string range $A 1 end]\n $tag
266
+ } elseif {$key3=="2"} {
267
+ .txtC insert end [string range $B 1 end]\n chng
268
+ } elseif {$key3=="X"} {
269
+ .txtC insert end [string range $C 1 end]\n rm
270
+ } else {
271
+ .txtC insert end [string range $C 1 end]\n $tag
272
+ }
273
+ }
274
+ if {$key4=="."} {
275
+ .lnD insert end \n -
276
+ .txtD insert end \n -
277
+ } elseif {$key4=="N"} {
278
+ .nameD config -text [string range $D 1 end]
279
+ } else {
280
+ .lnD insert end $lnD\n -
281
+ incr lnD
282
+ if {$key4=="1"} {
283
+ .txtD insert end [string range $A 1 end]\n -
284
+ } elseif {$key4=="2"} {
285
+ .txtD insert end [string range $B 1 end]\n chng
286
+ } elseif {$key4=="3"} {
287
+ .txtD insert end [string range $C 1 end]\n add
288
+ } elseif {$key4=="X"} {
289
+ .txtD insert end [string range $D 1 end]\n rm
290
+ } else {
291
+ .txtD insert end [string range $D 1 end]\n -
292
+ }
293
+ }
294
+ }
295
+ foreach c [cols] {
296
+ set type [colType $c]
297
+ if {$type ne "txt"} {
298
+ $c config -width 6; # $widths($type)
299
+ }
300
+ $c config -state disabled
301
+ }
302
+ set mx $lnA
303
+ if {$lnB>$mx} {set mx $lnB}
304
+ if {$lnC>$mx} {set mx $lnC}
305
+ if {$lnD>$mx} {set mx $lnD}
306
+ global lnWidth
307
+ set lnWidth [string length [format +%d $mx]]
308
+ .lnA config -width $lnWidth
309
+ .lnB config -width $lnWidth
310
+ .lnC config -width $lnWidth
311
+ .lnD config -width $lnWidth
312
+ grid columnconfig . {0 2 4 6} -minsize $lnWidth
313
+}
314
+
315
+proc viewDiff {idx} {
316
+ .txtA yview $idx
317
+ .txtA xview moveto 0
318
+}
319
+
320
+proc cycleDiffs {{reverse 0}} {
321
+ if {$reverse} {
322
+ set range [.txtA tag prevrange fn @0,0 1.0]
323
+ if {$range eq ""} {
324
+ viewDiff {fn.last -1c}
325
+ } else {
326
+ viewDiff [lindex $range 0]
327
+ }
328
+ } else {
329
+ set range [.txtA tag nextrange fn {@0,0 +1c} end]
330
+ if {$range eq "" || [lindex [.txtA yview] 1] == 1} {
331
+ viewDiff fn.first
332
+ } else {
333
+ viewDiff [lindex $range 0]
334
+ }
335
+ }
336
+}
337
+
338
+proc xvis {col} {
339
+ set view [$col xview]
340
+ return [expr {[lindex $view 1]-[lindex $view 0]}]
341
+}
342
+
343
+proc scroll-x {args} {
344
+ set c .txt[expr {[xvis .txtA] < [xvis .txtB] ? "A" : "B"}]
345
+ eval $c xview $args
346
+}
347
+
348
+interp alias {} scroll-y {} .txtA yview
349
+
350
+proc noop {args} {}
351
+
352
+proc enableSync {axis} {
353
+ update idletasks
354
+ interp alias {} sync-$axis {}
355
+ rename _sync-$axis sync-$axis
356
+}
357
+
358
+proc disableSync {axis} {
359
+ rename sync-$axis _sync-$axis
360
+ interp alias {} sync-$axis {} noop
361
+}
362
+
363
+proc sync-y {first last} {
364
+ disableSync y
365
+ foreach c [cols] {
366
+ $c yview moveto $first
367
+ }
368
+ if {$first > 0 || $last < 1} {
369
+ grid .sby
370
+ .sby set $first $last
371
+ } else {
372
+ grid remove .sby
373
+ }
374
+ enableSync y
375
+}
376
+
377
+wm withdraw .
378
+wm title . $CFG(TITLE)
379
+wm iconname . $CFG(TITLE)
380
+# Keystroke bindings for on the top-level window for navigation and
381
+# control also fire when those same keystrokes are pressed in the
382
+# Search entry box. Disable them, to prevent the diff screen from
383
+# disappearing abruptly and unexpectedly when searching for "q".
384
+#
385
+bind . <Control-q> exit
386
+bind . <Control-p> {catch searchPrev; break}
387
+bind . <Control-n> {catch searchNext; break}
388
+bind . <Escape><Escape> exit
389
+bind . <Destroy> {after 0 exit}
390
+bind . <Tab> {cycleDiffs; break}
391
+bind . <<PrevWindow>> {cycleDiffs 1; break}
392
+bind . <Control-f> {searchOnOff; break}
393
+bind . <Control-g> {catch searchNext; break}
394
+bind . <Return> {
395
+ event generate .bb.files <1>
396
+ event generate .bb.files <ButtonRelease-1>
397
+ break
398
+}
399
+foreach {key axis args} {
400
+ Up y {scroll -5 units}
401
+ k y {scroll -5 units}
402
+ Down y {scroll 5 units}
403
+ j y {scroll 5 units}
404
+ Left x {scroll -5 units}
405
+ h x {scroll -5 units}
406
+ Right x {scroll 5 units}
407
+ l x {scroll 5 units}
408
+ Prior y {scroll -1 page}
409
+ b y {scroll -1 page}
410
+ Next y {scroll 1 page}
411
+ space y {scroll 1 page}
412
+ Home y {moveto 0}
413
+ g y {moveto 0}
414
+ End y {moveto 1}
415
+} {
416
+ bind . <$key> "scroll-$axis $args; break"
417
+ bind . <Shift-$key> continue
418
+}
419
+
420
+frame .bb
421
+::ttk::menubutton .bb.diff2 -text {2-way diffs
422
+.bb.diff2.m add command -label {baseline vs. local} -command {two-way 12}
423
+.bb.diff2.m add command -label {baseline vs. merge-in} -command {two-way 13}
424
+.bb.diff2.m add command -label {local vs. merge-in} -command {two-way 23}
425
+
426
+# Bring up a separate two-way diff between a pair of columns
427
+# the argument is one of:
428
+# 12 Baseline versus Local
429
+# 13 Baseline versus Merge-in
430
+# 23 Local versus Merge-in
431
+#
432
+proc two-way {mode} {
433
+ global current_file fossilexe debug darkmode ncontext
434
+ regsub {^[A-Z]+ } $current_file {} fn
435
+ set cmd $fossilexe
436
+ lappend cmd merge-info --diff$mode $fn -c $ncontext
437
+ if {$darkmode} {
438
+ lappend cmd --dark
439
+ }
440
+ if {$debug} {
441
+ lappend cc {*}$cmd &
442
+}
443
+
444
+set useOptionMenu 1
445
+if {[info exists filelist]} {
446
+ set current_file "[lindex $filelist 0] [lindex $filelist 1]"
447
+ if {[llength $filelist]>2} {
448
+ trace add variable current_file write readMerge
449
+
450
+ if {$tcl_platform(os)=="Darwin" || [llength $filelist]<30} {
451
+ set fnlist {}
452
+ foreach {op fn} $filelist {lappend fnlist "$op $fn"}
453
+ tk_optionMenu .bb.files current_file {*}$fnlist
454
+ } else {
455
+ set useOptionMenu 0
456
+ ::ttk::menubutton .bb.files -text $current_file
457
+ if {[tk windowingsystem] eq "win32"} {
458
+ ::ttk::style theme use winnative
459
+ .bb.files configure -padding {20 1 10 2}
460
+ }
461
+ toplevel .wfiles
462
+ wm withdraw .wfiles
463
+ update idletasks
464
+ wm transient .wfiles .
465
+ wm overrideredirect .wfiles 1
466
+ set ht [expr {[llength $filelist]/2}]
467
+ if {$ht>$CFG(LB_HEIGHT)} {set ht $CFG(LB_HEIGHT)}
468
+ listbox .wfiles.lb -width 0 -height $ht -activestyle none \
469
+ -yscroll {.wfiles.sb set}
470
+ set mx 1
471
+ foreach {op fn} $filelist {
472
+ set n [string length $fn]
473
+ if {$n>$mx} {set mx $n}
474
+ .wfiles.lb insert end "$op $fn"
475
+ }
476
+ .bb.files config -width $mx
477
+ ::ttk::scrollbar .wfiles.sb -command {.wfiles.lb yview}
478
+ grid .wfiles.lb .wfiles.sb -sticky ns
479
+ bind .bb.files <1> {
480
+ set x [winfo rootx %W]
481
+ set y [expr {[winfo rooty %W]+[winfo height %W]}]
482
+ wm geometry .wfiles +$x+$y
483
+ wm deiconify .wfiles
484
+ focus .wfiles.lb
485
+ }
486
+ bind .wfiles <FocusOut> {wm withdraw .wfiles}
487
+ bind .wfiles <Escape> {focus .}
488
+ foreach evt {1 Return} {
489
+ bind .wfiles.lb <$evt> {
490
+ set ii [%W curselection]
491
+ set ::current_file [%W get $ii]
492
+ .bb.files config -text $::current_file
493
+ focus .
494
+ break
495
+ }
496
+ }
497
+ bind .wfiles.lb <Motion> {
498
+ %W selection clear 0 end
499
+ %W selection set @%x,%y
500
+ }
501
+ }
502
+ }
503
+}
504
+
505
+label .bb.ctxtag -text "Context:"
506
+set context_choices {3 6 12 25 50 100 All}
507
+if {$ncontext<0} {set ncontext All}
508
+trace add variable ncontext write readMerge
509
+if {$tcl_platform(os)=="Darwin" || $useOptionMenu} {
510
+ tk_optionMenu .bb.ctx ncontext {*}$context_choices
511
+} else {
512
+ ::ttk::menubutton .bb.ctx -text $ncontext
513
+ if {[tk windowingsystem] eq "win32"} {
514
+ ::ttk::style theme use winnative
515
+ .bb.ctx configure -padding {20 1 10 2}
516
+ }
517
+ toplevel .wctx
518
+ wm withdraw .wctx
519
+ update idletasks
520
+ wm transient .wctx .
521
+ wm overrideredirect .wctx 1
522
+ listbox .wctx.lb -width 0 -height 7 -activestyle none
523
+ .wctx.lb insert end {*}$context_choices
524
+ pack .wctx.lb
525
+ bind .bb.ctx <1> {
526
+ set x [winfo rootx %W]
527
+ set y [expr {[winfo rooty %W]+[winfo height %W]}]
528
+ wm geometry .wctx +$x+$y
529
+ wm deiconify .wctx
530
+ focus .wctx.lb
531
+ }
532
+ bind .wctx <FocusOut> {wm withdraw .wctx}
533
+ bind .wctx <Escape> {focus .}
534
+ foreach evt {1 Return} {
535
+ bind .wctx.lb <$evt> {
536
+ set ::ncontext [lindex $::context_choices [%W curselection]]
537
+ .bb.ctx config -text $::ncontext
538
+ focus .
539
+ break
540
+ }
541
+ }
542
+ bind .wctx.lb <Motion> {
543
+ %W selection clear 0 end
544
+ %W selection set @%x,%y
545
+ }
546
+}
547
+
548
+foreach {side syncCol} {A .txtA B .txtB C .txtC D .txtD} {
549
+ set ln .ln$side
550
+ text $ln -width 6
551
+ $ln tag config - -justify right
552
+
553
+ set txt .txt$side
554
+ text $txt -width $CFG(WIDTH) -height $CFG(HEIGHT) -wrap none \
555
+ -xscroll ".sbx$side set"
556
+ catch {$txt config -tabstyle wordprocessor} ;# Required for Tk>=8.5
557
+ foreach tag {add rm chng} {
558
+ $txt tag config $tag -background $CFG([string toupper $tag]_BG)
559
+ $txt tag lower $tag
560
+ }
561
+ $txt tag config fn -background $CFG(FN_BG) -foreground $CFG(FN_FG) \
562
+ -justify center
563
+ $txt tag config err -foreground $CFG(ERR_FG)
564
+}
565
+text .mkr
566
+
567
+set mxwidth [lindex [wm maxsize .] 0]
568
+while {$CFG(WIDTH)>=40} {
569
+ set wanted [expr {([winfo reqwidth .lnA]+[winfo reqwidth .txtA])*4+30}]
570
+ if {$wanted<=$mxwidth} break
571
+ incr CFG(WIDTH) -10
572
+ .txtA config -width $CFG(WIDTH)
573
+ .txtB config -width $CFG(WIDTH)
574
+ .txtC config -width $CFG(WIDTH)
575
+ .txtD config -width $CFG(WIDTH)
576
+}
577
+
578
+foreach c [cols] {
579
+ set keyPrefix [string toupper [colType $c]]_COL_
580
+ if {[tk windowingsystem] eq "win32"} {$c config -font {courier 9}}
581
+ $c co
--- a/src/merge.tcl
+++ b/src/merge.tcl
@@ -0,0 +1,581 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/src/merge.tcl
+++ b/src/merge.tcl
@@ -0,0 +1,581 @@
1 # Show details of a 3-way merge operation. The left-most column is the
2 # common ancestor. The next two columns are edits of that common ancestor.
3 # The right-most column is the result of the merge.
4 #
5 # There is always a "fossilcmd" variable which tells the script how to
6 # invoke Fossil to get the information it needs. This script will
7 # automatically append "-c N" to tell Fossil how much context it wants. True for debugging output
8 #
9 # If the "filelist" global variable is defined, then it is a list of
10 # alternating "merge-type names" (ex: UPDATE, MERGE, CONFLICT, ERROR) and
11 # filenames. In that case, the initial display shows the changes for
12 # the first pair on the list and there is a optionmenu that allows the
13 # useelect otheere should also be a global variable named "ncontext" which is the
14 # number of lines of context to display. The value of this variable
15 # controls the "-c N" argument that is appended to fossilcmdht {
16 TITLE {Fossil Merge}
17 LN_COL_BG #dddddd
18 LN_COL_FG #444444
19 TXT_COL_BG #ffffff
20 TXT_COL_FG #000000
21 MKR_COL_BG #444444
22 MKR_COL_FG #dddddd
23 CHNG_BG #d0d070
24 ADD_BG #c0ffc0
25 RM_BG #ffc0c0
26 HR_FG #444444
27 HR_PAD_TOP 4
28 HR_PAD_BTM 8
29 FN_BG #444444
30 FN_FG #ffffff
31 FN_PAD 5
32 ERR_FG #ee0000
33 PADX 5
34 WIDTH 80
35 HEIGHT 45
36 LB_HEIGHT 25
37 }
38
39 array set CFG_dark {
40 TITLE {Fossil Merge}
41 LN_COL_BG #dddddd
42 LN_COL_FG #444444
43 TXT_COL_BG #3f3f3f
44 TXT_COL_FG #dcdccc
45 MKR_COL_BG #444444
46 MKR_COL_FG #dddddd
47 CHNG_BG #6a6a00
48 ADD_BG #57934c
49 RM_BG #ef6767
50 HR_FG #444444
51 HR_PAD_TOP 4
52 HR_PAD_BTM 8
53 FN_BG #5e5e5e
54 FN_FG #ffffff
55 FN_PAD 5
56 ERR_FG #ee0000
57 PADX 5
58 WIDTH 80
59 HEIGHT 45
60 LB_HEIGHT 25
61 }
62
63 array set CFG_arr {
64 0 CFG_light
65 1 CFG_dark
66 }
67
68 array set CFG [array get $CFG_arr($darkmode)]
69
70 if {![namespace exists ttk]} {
71 interp alias {} ::ttk::scrollbar {} ::scrollbar
72 interp alias {} ::ttk::menubutton {} ::menubutton
73 }
74
75 proc dehtml {x} {
76 set x [regsub -all {<[^>]*>} $x {}]
77 return [string map {&amp; & &lt; < &gt; > &#39; ' &quot; \"} $x]
78 }
79
80 proc cols {} {
81 return [list .lnA .txtA .lnB .txtB .lnC .txtC .lnD .txtD]
82 }
83
84 proc colType {c} {
85 regexp {[a-z]+} $c type
86 return $type
87 }
88
89 proc readMercmdes currentails of a 3-way merge operation# Show det for
90 # the first pair on the list and there is a optionmenu that allows the
91 # user to select other fiels on the list.
92 #
93 # This header comment is stripped off by the "mkbuiltin.c" program.
94 #
95 package require Tk
96
97 array set CFG_light {
98 TITLE {Fossil Merge}
99 LN_COL_BG #dddddd
100 LN_COL_FG #444444
101 TXT_COL_BG #ffffff
102 TXT_COL_FG #000000
103 MKR_COL_BG #444444
104 MKR_COL_FG #dddddd
105 CHNG_BG #d0d070
106 ADD_BG #c0ffc0
107 RM_BG #ffc0c0
108 HR_FG #444444
109 HR_PAD_TOP 4
110 HR_PAD_BTM 8
111 FN_BG #444444
112 FN_FG #ffffff
113 FN_PAD 5
114 ERR_FG #ee0000
115 PADX 5
116 WIDTH 80
117 HEIGHT 45
118 LB_HEIGHT 25
119 }
120
121 array set CFG_dark {
122 TITLE {Fossil Merge}
123 LN_COL_BG #dddddd
124 LN_COL_FG #444444
125 TXT_COL_BG #3f3f3f
126 TXT_COL_FG #dcdccc
127 MKR_COL_BG #444444
128 MKR_COL_FG #dddddd
129 CHNG_BG #6a6a00
130 ADD_BG #57934c
131 RM_BG #ef6767
132 HR_FG #444444
133 HR_PAD_TOP 4
134 HR_PAD_BTM 8
135 FN_BG #5e5e5e
136 FN_FG #ffffff
137 FN_PAD 5
138 ERR_FG #ee0000
139 PADX 5
140 WIDTH 80
141 HEIGHT 45
142 LB_HEIGHT 25
143 }
144
145 array set CFG_arr {
146 0 CFG_light
147 1 CFG_dark
148 }
149
150 array set CFG [array get $CFG_arr($darkmode)]
151
152 if {![namespace exists ttk]} {
153 interp alias {} ::ttk::scrollbar {} ::scrollbar
154 interp alias {} ::ttk::menubutton {} ::menubutton
155 }
156
157 proc dehtml {x} {
158 set x [regsub -all {<[^>]*>} $x {}]
159 return [string map {&amp; & &lt; < &gt; > &#39; ' &quot; \"} $x]
160 }
161
162 proc cols {} {
163 return [list .lnA .txtA .lnB .txtB .lnC .txtC .lnD .txtD]
164 }
165
166 proc colType {c} {
167 regexp {[a-z]+} $c type
168 return $type
169 }
170
171 proc readMerge {args} {
172 global fossilexe ncontext current_file debug
173 if {$ncontext=="All"} {
174 set cmd "| $fossilexe merge-info -c -1"
175 } else {
176 set cmd "| $fossilexe merge-info -c $ncontext"
177 }
178 if {[info exists current_file]} {
179 regsub {^[A-Z]+ } $current_file {} fn
180 lappend cmd -tcl $fn
181 }
182 if {$debug} {
183 regsub {^\| +} $cmd {} cmd2
184 puts $cmd2
185 flush stdout
186 }
187 if {[catch {
188 set in [open $cmd r]
189 fconfigure $in -encoding utf-8
190 set mergetxt [read $in]
191 close $in
192 } msg]} {
193 tk_messageBox -message "Unable to run command: \"$cmd\""
194 set mergetxt {}
195 }
196 foreach c [cols] {
197 $c config -state normal
198 $c delete 1.0 end
199 }
200 set lnA 1
201 set lnB 1
202 set lnC 1
203 set lnD 1
204 foreach {A B C D} $mergetxt {
205 set key1 [string index $A 0]
206 if {$key1=="S"} {
207 scan [string range $A 1 end] "%d %d %d %d" nA nB nC nD
208 foreach x {A B C D} {
209 set N [set n$x]
210 incr ln$x $N
211 if {$N>0} {
212 .ln$x insert end ...\n hrln
213 .txt$x insert end [string repeat . 30]\n hrtxt
214 } else {
215 .ln$x insert end \n hrln
216 .txt$x insert end \n hrtxt
217 }
218 }
219 continue
220 }
221 set key2 [string index $B 0]
222 set key3 [string index $C 0]
223 set key4 [string index $D 0]
224 if {$key1=="."} {
225 .lnA insert end \n -
226 .txtA insert end \n -
227 } elseif {$key1=="N"} {
228 .nameA config -text [string range $A 1 end]
229 } else {
230 .lnA insert end $lnA\n -
231 incr lnA
232 if {$key1=="X"} {
233 .txtA insert end [string range $A 1 end]\n rm
234 } else {
235 .txtA insert end [string range $A 1 end]\n -
236 }
237 }
238 if {$key2=="."} {
239 .lnB insert end \n -
240 .txtB insert end \n -
241 } elseif {$key2=="N"} {
242 .nameB config -text [string range $B 1 end]
243 } else {
244 .lnB insert end $lnB\n -
245 incr lnB
246 if {$key4=="2"} {set tag chng} {set tag -}
247 if {$key2=="1"} {
248 .txtB insert end [string range $A 1 end]\n $tag
249 } elseif {$key2=="X"} {
250 .txtB insert end [string range $B 1 end]\n rm
251 } else {
252 .txtB insert end [string range $B 1 end]\n $tag
253 }
254 }
255 if {$key3=="."} {
256 .lnC insert end \n -
257 .txtC insert end \n -
258 } elseif {$key3=="N"} {
259 .nameC config -text [string range $C 1 end]
260 } else {
261 .lnC insert end $lnC\n -
262 incr lnC
263 if {$key4=="3"} {set tag add} {set tag -}
264 if {$key3=="1"} {
265 .txtC insert end [string range $A 1 end]\n $tag
266 } elseif {$key3=="2"} {
267 .txtC insert end [string range $B 1 end]\n chng
268 } elseif {$key3=="X"} {
269 .txtC insert end [string range $C 1 end]\n rm
270 } else {
271 .txtC insert end [string range $C 1 end]\n $tag
272 }
273 }
274 if {$key4=="."} {
275 .lnD insert end \n -
276 .txtD insert end \n -
277 } elseif {$key4=="N"} {
278 .nameD config -text [string range $D 1 end]
279 } else {
280 .lnD insert end $lnD\n -
281 incr lnD
282 if {$key4=="1"} {
283 .txtD insert end [string range $A 1 end]\n -
284 } elseif {$key4=="2"} {
285 .txtD insert end [string range $B 1 end]\n chng
286 } elseif {$key4=="3"} {
287 .txtD insert end [string range $C 1 end]\n add
288 } elseif {$key4=="X"} {
289 .txtD insert end [string range $D 1 end]\n rm
290 } else {
291 .txtD insert end [string range $D 1 end]\n -
292 }
293 }
294 }
295 foreach c [cols] {
296 set type [colType $c]
297 if {$type ne "txt"} {
298 $c config -width 6; # $widths($type)
299 }
300 $c config -state disabled
301 }
302 set mx $lnA
303 if {$lnB>$mx} {set mx $lnB}
304 if {$lnC>$mx} {set mx $lnC}
305 if {$lnD>$mx} {set mx $lnD}
306 global lnWidth
307 set lnWidth [string length [format +%d $mx]]
308 .lnA config -width $lnWidth
309 .lnB config -width $lnWidth
310 .lnC config -width $lnWidth
311 .lnD config -width $lnWidth
312 grid columnconfig . {0 2 4 6} -minsize $lnWidth
313 }
314
315 proc viewDiff {idx} {
316 .txtA yview $idx
317 .txtA xview moveto 0
318 }
319
320 proc cycleDiffs {{reverse 0}} {
321 if {$reverse} {
322 set range [.txtA tag prevrange fn @0,0 1.0]
323 if {$range eq ""} {
324 viewDiff {fn.last -1c}
325 } else {
326 viewDiff [lindex $range 0]
327 }
328 } else {
329 set range [.txtA tag nextrange fn {@0,0 +1c} end]
330 if {$range eq "" || [lindex [.txtA yview] 1] == 1} {
331 viewDiff fn.first
332 } else {
333 viewDiff [lindex $range 0]
334 }
335 }
336 }
337
338 proc xvis {col} {
339 set view [$col xview]
340 return [expr {[lindex $view 1]-[lindex $view 0]}]
341 }
342
343 proc scroll-x {args} {
344 set c .txt[expr {[xvis .txtA] < [xvis .txtB] ? "A" : "B"}]
345 eval $c xview $args
346 }
347
348 interp alias {} scroll-y {} .txtA yview
349
350 proc noop {args} {}
351
352 proc enableSync {axis} {
353 update idletasks
354 interp alias {} sync-$axis {}
355 rename _sync-$axis sync-$axis
356 }
357
358 proc disableSync {axis} {
359 rename sync-$axis _sync-$axis
360 interp alias {} sync-$axis {} noop
361 }
362
363 proc sync-y {first last} {
364 disableSync y
365 foreach c [cols] {
366 $c yview moveto $first
367 }
368 if {$first > 0 || $last < 1} {
369 grid .sby
370 .sby set $first $last
371 } else {
372 grid remove .sby
373 }
374 enableSync y
375 }
376
377 wm withdraw .
378 wm title . $CFG(TITLE)
379 wm iconname . $CFG(TITLE)
380 # Keystroke bindings for on the top-level window for navigation and
381 # control also fire when those same keystrokes are pressed in the
382 # Search entry box. Disable them, to prevent the diff screen from
383 # disappearing abruptly and unexpectedly when searching for "q".
384 #
385 bind . <Control-q> exit
386 bind . <Control-p> {catch searchPrev; break}
387 bind . <Control-n> {catch searchNext; break}
388 bind . <Escape><Escape> exit
389 bind . <Destroy> {after 0 exit}
390 bind . <Tab> {cycleDiffs; break}
391 bind . <<PrevWindow>> {cycleDiffs 1; break}
392 bind . <Control-f> {searchOnOff; break}
393 bind . <Control-g> {catch searchNext; break}
394 bind . <Return> {
395 event generate .bb.files <1>
396 event generate .bb.files <ButtonRelease-1>
397 break
398 }
399 foreach {key axis args} {
400 Up y {scroll -5 units}
401 k y {scroll -5 units}
402 Down y {scroll 5 units}
403 j y {scroll 5 units}
404 Left x {scroll -5 units}
405 h x {scroll -5 units}
406 Right x {scroll 5 units}
407 l x {scroll 5 units}
408 Prior y {scroll -1 page}
409 b y {scroll -1 page}
410 Next y {scroll 1 page}
411 space y {scroll 1 page}
412 Home y {moveto 0}
413 g y {moveto 0}
414 End y {moveto 1}
415 } {
416 bind . <$key> "scroll-$axis $args; break"
417 bind . <Shift-$key> continue
418 }
419
420 frame .bb
421 ::ttk::menubutton .bb.diff2 -text {2-way diffs
422 .bb.diff2.m add command -label {baseline vs. local} -command {two-way 12}
423 .bb.diff2.m add command -label {baseline vs. merge-in} -command {two-way 13}
424 .bb.diff2.m add command -label {local vs. merge-in} -command {two-way 23}
425
426 # Bring up a separate two-way diff between a pair of columns
427 # the argument is one of:
428 # 12 Baseline versus Local
429 # 13 Baseline versus Merge-in
430 # 23 Local versus Merge-in
431 #
432 proc two-way {mode} {
433 global current_file fossilexe debug darkmode ncontext
434 regsub {^[A-Z]+ } $current_file {} fn
435 set cmd $fossilexe
436 lappend cmd merge-info --diff$mode $fn -c $ncontext
437 if {$darkmode} {
438 lappend cmd --dark
439 }
440 if {$debug} {
441 lappend cc {*}$cmd &
442 }
443
444 set useOptionMenu 1
445 if {[info exists filelist]} {
446 set current_file "[lindex $filelist 0] [lindex $filelist 1]"
447 if {[llength $filelist]>2} {
448 trace add variable current_file write readMerge
449
450 if {$tcl_platform(os)=="Darwin" || [llength $filelist]<30} {
451 set fnlist {}
452 foreach {op fn} $filelist {lappend fnlist "$op $fn"}
453 tk_optionMenu .bb.files current_file {*}$fnlist
454 } else {
455 set useOptionMenu 0
456 ::ttk::menubutton .bb.files -text $current_file
457 if {[tk windowingsystem] eq "win32"} {
458 ::ttk::style theme use winnative
459 .bb.files configure -padding {20 1 10 2}
460 }
461 toplevel .wfiles
462 wm withdraw .wfiles
463 update idletasks
464 wm transient .wfiles .
465 wm overrideredirect .wfiles 1
466 set ht [expr {[llength $filelist]/2}]
467 if {$ht>$CFG(LB_HEIGHT)} {set ht $CFG(LB_HEIGHT)}
468 listbox .wfiles.lb -width 0 -height $ht -activestyle none \
469 -yscroll {.wfiles.sb set}
470 set mx 1
471 foreach {op fn} $filelist {
472 set n [string length $fn]
473 if {$n>$mx} {set mx $n}
474 .wfiles.lb insert end "$op $fn"
475 }
476 .bb.files config -width $mx
477 ::ttk::scrollbar .wfiles.sb -command {.wfiles.lb yview}
478 grid .wfiles.lb .wfiles.sb -sticky ns
479 bind .bb.files <1> {
480 set x [winfo rootx %W]
481 set y [expr {[winfo rooty %W]+[winfo height %W]}]
482 wm geometry .wfiles +$x+$y
483 wm deiconify .wfiles
484 focus .wfiles.lb
485 }
486 bind .wfiles <FocusOut> {wm withdraw .wfiles}
487 bind .wfiles <Escape> {focus .}
488 foreach evt {1 Return} {
489 bind .wfiles.lb <$evt> {
490 set ii [%W curselection]
491 set ::current_file [%W get $ii]
492 .bb.files config -text $::current_file
493 focus .
494 break
495 }
496 }
497 bind .wfiles.lb <Motion> {
498 %W selection clear 0 end
499 %W selection set @%x,%y
500 }
501 }
502 }
503 }
504
505 label .bb.ctxtag -text "Context:"
506 set context_choices {3 6 12 25 50 100 All}
507 if {$ncontext<0} {set ncontext All}
508 trace add variable ncontext write readMerge
509 if {$tcl_platform(os)=="Darwin" || $useOptionMenu} {
510 tk_optionMenu .bb.ctx ncontext {*}$context_choices
511 } else {
512 ::ttk::menubutton .bb.ctx -text $ncontext
513 if {[tk windowingsystem] eq "win32"} {
514 ::ttk::style theme use winnative
515 .bb.ctx configure -padding {20 1 10 2}
516 }
517 toplevel .wctx
518 wm withdraw .wctx
519 update idletasks
520 wm transient .wctx .
521 wm overrideredirect .wctx 1
522 listbox .wctx.lb -width 0 -height 7 -activestyle none
523 .wctx.lb insert end {*}$context_choices
524 pack .wctx.lb
525 bind .bb.ctx <1> {
526 set x [winfo rootx %W]
527 set y [expr {[winfo rooty %W]+[winfo height %W]}]
528 wm geometry .wctx +$x+$y
529 wm deiconify .wctx
530 focus .wctx.lb
531 }
532 bind .wctx <FocusOut> {wm withdraw .wctx}
533 bind .wctx <Escape> {focus .}
534 foreach evt {1 Return} {
535 bind .wctx.lb <$evt> {
536 set ::ncontext [lindex $::context_choices [%W curselection]]
537 .bb.ctx config -text $::ncontext
538 focus .
539 break
540 }
541 }
542 bind .wctx.lb <Motion> {
543 %W selection clear 0 end
544 %W selection set @%x,%y
545 }
546 }
547
548 foreach {side syncCol} {A .txtA B .txtB C .txtC D .txtD} {
549 set ln .ln$side
550 text $ln -width 6
551 $ln tag config - -justify right
552
553 set txt .txt$side
554 text $txt -width $CFG(WIDTH) -height $CFG(HEIGHT) -wrap none \
555 -xscroll ".sbx$side set"
556 catch {$txt config -tabstyle wordprocessor} ;# Required for Tk>=8.5
557 foreach tag {add rm chng} {
558 $txt tag config $tag -background $CFG([string toupper $tag]_BG)
559 $txt tag lower $tag
560 }
561 $txt tag config fn -background $CFG(FN_BG) -foreground $CFG(FN_FG) \
562 -justify center
563 $txt tag config err -foreground $CFG(ERR_FG)
564 }
565 text .mkr
566
567 set mxwidth [lindex [wm maxsize .] 0]
568 while {$CFG(WIDTH)>=40} {
569 set wanted [expr {([winfo reqwidth .lnA]+[winfo reqwidth .txtA])*4+30}]
570 if {$wanted<=$mxwidth} break
571 incr CFG(WIDTH) -10
572 .txtA config -width $CFG(WIDTH)
573 .txtB config -width $CFG(WIDTH)
574 .txtC config -width $CFG(WIDTH)
575 .txtD config -width $CFG(WIDTH)
576 }
577
578 foreach c [cols] {
579 set keyPrefix [string toupper [colType $c]]_COL_
580 if {[tk windowingsystem] eq "win32"} {$c config -font {courier 9}}
581 $c co
+804 -168
--- src/merge3.c
+++ src/merge3.c
@@ -75,75 +75,17 @@
7575
if( aC1[2]!=aC2[2] ) return 0;
7676
if( sameLines(pV1, pV2, aC1[2]) ) return 1;
7777
return 0;
7878
}
7979
80
-/*
81
-** The aC[] array contains triples of integers. Within each triple, the
82
-** elements are:
83
-**
84
-** (0) The number of lines to copy
85
-** (1) The number of lines to delete
86
-** (2) The number of liens to insert
87
-**
88
-** Suppose we want to advance over sz lines of the original file. This routine
89
-** returns true if that advance would land us on a copy operation. It
90
-** returns false if the advance would end on a delete.
91
-*/
92
-static int ends_at_CPY(int *aC, int sz){
93
- while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){
94
- if( aC[0]>=sz ) return 1;
95
- sz -= aC[0];
96
- if( aC[1]>sz ) return 0;
97
- sz -= aC[1];
98
- aC += 3;
99
- }
100
- return 1;
101
-}
102
-
103
-/*
104
-** pSrc contains an edited file where aC[] describes the edit. Part of
105
-** pSrc has already been output. This routine outputs additional lines
106
-** of pSrc - lines that correspond to the next sz lines of the original
107
-** unedited file.
108
-**
109
-** Note that sz counts the number of lines of text in the original file.
110
-** But text is output from the edited file. So the number of lines transfer
111
-** to pOut might be different from sz. Fewer lines appear in pOut if there
112
-** are deletes. More lines appear if there are inserts.
113
-**
114
-** The aC[] array is updated and the new index into aC[] is returned.
115
-*/
116
-static int output_one_side(
117
- Blob *pOut, /* Write to this blob */
118
- Blob *pSrc, /* The edited file that is to be copied to pOut */
119
- int *aC, /* Array of integer triples describing the edit */
120
- int i, /* Index in aC[] of current location in pSrc */
121
- int sz, /* Number of lines in unedited source to output */
122
- int *pLn /* Line number counter */
123
-){
124
- while( sz>0 ){
125
- if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break;
126
- if( aC[i]>=sz ){
127
- blob_copy_lines(pOut, pSrc, sz); *pLn += sz;
128
- aC[i] -= sz;
129
- break;
130
- }
131
- blob_copy_lines(pOut, pSrc, aC[i]); *pLn += aC[i];
132
- blob_copy_lines(pOut, pSrc, aC[i+2]); *pLn += aC[i+2];
133
- sz -= aC[i] + aC[i+1];
134
- i += 3;
135
- }
136
- return i;
137
-}
138
-
13980
/*
14081
** Text of boundary markers for merge conflicts.
14182
*/
14283
static const char *const mergeMarker[] = {
14384
/*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/
14485
"<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<",
86
+ "####### SUGGESTED CONFLICT RESOLUTION follows ###################",
14587
"||||||| COMMON ANCESTOR content follows |||||||||||||||||||||||||",
14688
"======= MERGED IN content follows ===============================",
14789
">>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
14890
};
14991
@@ -186,10 +128,615 @@
186128
ensure_line_end(pOut, useCrLf);
187129
blob_append(pOut, mergeMarker[iMark], -1);
188130
if( ln>0 ) blob_appendf(pOut, " (line %d)", ln);
189131
ensure_line_end(pOut, useCrLf);
190132
}
133
+
134
+#if INTERFACE
135
+/*
136
+** This is an abstract class for constructing a merge.
137
+** Subclasses of this object format the merge output in different ways.
138
+**
139
+** To subclass, create an instance of the MergeBuilder object and fill
140
+** in appropriate method implementations.
141
+*/
142
+struct MergeBuilder {
143
+ void (*xStart)(MergeBuilder*);
144
+ void (*xSame)(MergeBuilder*, unsigned int);
145
+ void (*xChngV1)(MergeBuilder*, unsigned int, unsigned int);
146
+ void (*xChngV2)(MergeBuilder*, unsigned int, unsigned int);
147
+ void (*xChngBoth)(MergeBuilder*, unsigned int, unsigned int);
148
+ void (*xConflict)(MergeBuilder*, unsigned int, unsigned int, unsigned int);
149
+ void (*xEnd)(MergeBuilder*);
150
+ void (*xDestroy)(MergeBuilder*);
151
+ const char *zPivot; /* Label or name for the pivot */
152
+ const char *zV1; /* Label or name for the V1 file */
153
+ const char *zV2; /* Label or name for the V2 file */
154
+ const char *zOut; /* Label or name for the output */
155
+ Blob *pPivot; /* The common ancestor */
156
+ Blob *pV1; /* First variant (local copy) */
157
+ Blob *pV2; /* Second variant (merged in) */
158
+ Blob *pOut; /* Write merge results here */
159
+ int useCrLf; /* Use CRLF line endings */
160
+ int nContext; /* Size of unchanged line boundaries */
161
+ unsigned int mxPivot; /* Number of lines in the pivot */
162
+ unsigned int mxV1; /* Number of lines in V1 */
163
+ unsigned int mxV2; /* Number of lines in V2 */
164
+ unsigned int lnPivot; /* Lines read from pivot */
165
+ unsigned int lnV1; /* Lines read from v1 */
166
+ unsigned int lnV2; /* Lines read from v2 */
167
+ unsigned int lnOut; /* Lines written to out */
168
+ unsigned int nConflict; /* Number of conflicts seen */
169
+ u64 diffFlags; /* Flags for difference engine */
170
+};
171
+#endif /* INTERFACE */
172
+
173
+
174
+/************************* Generic MergeBuilder ******************************/
175
+/* These are generic methods for MergeBuilder. They just output debugging
176
+** information. But some of them are useful as base methods for other useful
177
+** implementations of MergeBuilder.
178
+*/
179
+
180
+/* xStart() and xEnd() are called to generate header and fotter information
181
+** in the output. This is a no-op in the generic implementation.
182
+*/
183
+static void dbgStartEnd(MergeBuilder *p){ (void)p; }
184
+
185
+/* The next N lines of PIVOT are unchanged in both V1 and V2
186
+*/
187
+static void dbgSame(MergeBuilder *p, unsigned int N){
188
+ blob_appendf(p->pOut,
189
+ "COPY %u from BASELINE(%u..%u) or V1(%u..%u) or V2(%u..%u)\n",
190
+ N, p->lnPivot+1, p->lnPivot+N, p->lnV1+1, p->lnV1+N,
191
+ p->lnV2+1, p->lnV2+N);
192
+ p->lnPivot += N;
193
+ p->lnV1 += N;
194
+ p->lnV2 += N;
195
+}
196
+
197
+/* The next nPivot lines of the PIVOT are changed into nV1 lines by V1
198
+*/
199
+static void dbgChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
200
+ blob_appendf(p->pOut, "COPY %u from V1(%u..%u)\n",
201
+ nV1, p->lnV1+1, p->lnV1+nV1);
202
+ p->lnPivot += nPivot;
203
+ p->lnV2 += nPivot;
204
+ p->lnV1 += nV1;
205
+}
206
+
207
+/* The next nPivot lines of the PIVOT are changed into nV2 lines by V2
208
+*/
209
+static void dbgChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
210
+ blob_appendf(p->pOut, "COPY %u lines FROM V2(%u..%u)\n",
211
+ nV2, p->lnV2+1, p->lnV2+nV2);
212
+ p->lnPivot += nPivot;
213
+ p->lnV1 += nPivot;
214
+ p->lnV2 += nV2;
215
+}
216
+
217
+/* The next nPivot lines of the PIVOT are changed into nV lines from V1 and
218
+** V2, which should be the same. In other words, the same change is found
219
+** in both V1 and V2.
220
+*/
221
+static void dbgChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
222
+ blob_appendf(p->pOut, "COPY %u lines from V1(%u..%u) or V2(%u..%u)\n",
223
+ nV, p->lnV1+1, p->lnV1+nV, p->lnV2+1, p->lnV2+nV);
224
+ p->lnPivot += nPivot;
225
+ p->lnV1 += nV;
226
+ p->lnV2 += nV;
227
+}
228
+
229
+/* V1 and V2 have different and overlapping changes. The next nPivot lines
230
+** of the PIVOT are converted into nV1 lines of V1 and nV2 lines of V2.
231
+*/
232
+static void dbgConflict(
233
+ MergeBuilder *p,
234
+ unsigned int nPivot,
235
+ unsigned int nV1,
236
+ unsigned int nV2
237
+){
238
+ blob_appendf(p->pOut,
239
+ "CONFLICT %u,%u,%u BASELINE(%u..%u) versus V1(%u..%u) versus V2(%u..%u)\n",
240
+ nPivot, nV1, nV2,
241
+ p->lnPivot+1, p->lnPivot+nPivot,
242
+ p->lnV1+1, p->lnV1+nV1,
243
+ p->lnV2+1, p->lnV2+nV2);
244
+ p->lnV1 += nV1;
245
+ p->lnPivot += nPivot;
246
+ p->lnV2 += nV2;
247
+}
248
+
249
+/* Generic destructor for the MergeBuilder object
250
+*/
251
+static void dbgDestroy(MergeBuilder *p){
252
+ memset(p, 0, sizeof(*p));
253
+}
254
+
255
+/* Generic initializer for a MergeBuilder object
256
+*/
257
+static void mergebuilder_init(MergeBuilder *p){
258
+ memset(p, 0, sizeof(*p));
259
+ p->xStart = dbgStartEnd;
260
+ p->xSame = dbgSame;
261
+ p->xChngV1 = dbgChngV1;
262
+ p->xChngV2 = dbgChngV2;
263
+ p->xChngBoth = dbgChngBoth;
264
+ p->xConflict = dbgConflict;
265
+ p->xEnd = dbgStartEnd;
266
+ p->xDestroy = dbgDestroy;
267
+}
268
+
269
+/************************* MergeBuilderToken ********************************/
270
+/* This version of MergeBuilder actually performs a merge on file that
271
+** are broken up into tokens instead of lines, and puts the result in pOut.
272
+*/
273
+static void tokenSame(MergeBuilder *p, unsigned int N){
274
+ blob_append(p->pOut, p->pPivot->aData+p->pPivot->iCursor, N);
275
+ p->pPivot->iCursor += N;
276
+ p->pV1->iCursor += N;
277
+ p->pV2->iCursor += N;
278
+}
279
+static void tokenChngV1(MergeBuilder *p, unsigned int nPivot, unsigned nV1){
280
+ blob_append(p->pOut, p->pV1->aData+p->pV1->iCursor, nV1);
281
+ p->pPivot->iCursor += nPivot;
282
+ p->pV1->iCursor += nV1;
283
+ p->pV2->iCursor += nPivot;
284
+}
285
+static void tokenChngV2(MergeBuilder *p, unsigned int nPivot, unsigned nV2){
286
+ blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV2);
287
+ p->pPivot->iCursor += nPivot;
288
+ p->pV1->iCursor += nPivot;
289
+ p->pV2->iCursor += nV2;
290
+}
291
+static void tokenChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned nV){
292
+ blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV);
293
+ p->pPivot->iCursor += nPivot;
294
+ p->pV1->iCursor += nV;
295
+ p->pV2->iCursor += nV;
296
+}
297
+static void tokenConflict(
298
+ MergeBuilder *p,
299
+ unsigned int nPivot,
300
+ unsigned int nV1,
301
+ unsigned int nV2
302
+){
303
+ /* For a token-merge conflict, use the text from the merge-in */
304
+ blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV2);
305
+ p->pPivot->iCursor += nPivot;
306
+ p->pV1->iCursor += nV1;
307
+ p->pV2->iCursor += nV2;
308
+}
309
+static void mergebuilder_init_token(MergeBuilder *p){
310
+ mergebuilder_init(p);
311
+ p->xSame = tokenSame;
312
+ p->xChngV1 = tokenChngV1;
313
+ p->xChngV2 = tokenChngV2;
314
+ p->xChngBoth = tokenChngBoth;
315
+ p->xConflict = tokenConflict;
316
+ p->diffFlags = DIFF_BY_TOKEN;
317
+}
318
+
319
+/*
320
+** Attempt to do a low-level merge on a conflict. The conflict is
321
+** described by the first four parameters, which are the same as the
322
+** arguments to the xConflict method of the MergeBuilder object.
323
+** This routine attempts to resolve the conflict by looking at
324
+** elements of the conflict region that are finer grain than complete
325
+** lines of text.
326
+**
327
+** The result is written into Blob pOut. pOut is initialized by this
328
+** routine.
329
+*/
330
+int merge_try_to_resolve_conflict(
331
+ MergeBuilder *pMB, /* MergeBuilder that encounter conflict */
332
+ unsigned int nPivot, /* Lines of conflict in the pivot */
333
+ unsigned int nV1, /* Lines of conflict in V1 */
334
+ unsigned int nV2, /* Lines of conflict in V2 */
335
+ Blob *pOut /* Write resolution text here */
336
+){
337
+ int nConflict;
338
+ MergeBuilder mb;
339
+ Blob pv, v1, v2;
340
+ mergebuilder_init_token(&mb);
341
+ blob_extract_lines(pMB->pPivot, nPivot, &pv);
342
+ blob_extract_lines(pMB->pV1, nV1, &v1);
343
+ blob_extract_lines(pMB->pV2, nV2, &v2);
344
+ blob_zero(pOut);
345
+ blob_materialize(&pv);
346
+ blob_materialize(&v1);
347
+ blob_materialize(&v2);
348
+ mb.pPivot = &pv;
349
+ mb.pV1 = &v1;
350
+ mb.pV2 = &v2;
351
+ mb.pOut = pOut;
352
+ nConflict = merge_three_blobs(&mb);
353
+ blob_reset(&pv);
354
+ blob_reset(&v1);
355
+ blob_reset(&v2);
356
+ /* mb has not allocated any resources, so we do not need to invoke
357
+ ** the xDestroy method. */
358
+ blob_add_final_newline(pOut);
359
+ return nConflict;
360
+}
361
+
362
+
363
+/************************* MergeBuilderText **********************************/
364
+/* This version of MergeBuilder actually performs a merge on file and puts
365
+** the result in pOut
366
+*/
367
+static void txtStart(MergeBuilder *p){
368
+ /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM),
369
+ ** keep it in the output. This should be secure enough not to cause
370
+ ** unintended changes to the merged file and consistent with what
371
+ ** users are using in their source files.
372
+ */
373
+ if( starts_with_utf8_bom(p->pV1, 0) && starts_with_utf8_bom(p->pV2, 0) ){
374
+ blob_append(p->pOut, (char*)get_utf8_bom(0), -1);
375
+ }
376
+ if( contains_crlf(p->pV1) && contains_crlf(p->pV2) ){
377
+ p->useCrLf = 1;
378
+ }
379
+}
380
+static void txtSame(MergeBuilder *p, unsigned int N){
381
+ blob_copy_lines(p->pOut, p->pPivot, N); p->lnPivot += N;
382
+ blob_copy_lines(0, p->pV1, N); p->lnV1 += N;
383
+ blob_copy_lines(0, p->pV2, N); p->lnV2 += N;
384
+}
385
+static void txtChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
386
+ blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
387
+ blob_copy_lines(0, p->pV2, nPivot); p->lnV2 += nPivot;
388
+ blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1;
389
+}
390
+static void txtChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
391
+ blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
392
+ blob_copy_lines(0, p->pV1, nPivot); p->lnV1 += nPivot;
393
+ blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2;
394
+}
395
+static void txtChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
396
+ blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
397
+ blob_copy_lines(0, p->pV1, nV); p->lnV1 += nV;
398
+ blob_copy_lines(p->pOut, p->pV2, nV); p->lnV2 += nV;
399
+}
400
+static void txtConflict(
401
+ MergeBuilder *p,
402
+ unsigned int nPivot,
403
+ unsigned int nV1,
404
+ unsigned int nV2
405
+){
406
+ int nRes; /* Lines in the computed conflict resolution */
407
+ Blob res; /* Text of the conflict resolution */
408
+
409
+ merge_try_to_resolve_conflict(p, nPivot, nV1, nV2, &res);
410
+ nRes = blob_linecount(&res);
411
+
412
+ append_merge_mark(p->pOut, 0, p->lnV1+1, p->useCrLf);
413
+ blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1;
414
+
415
+ if( nRes>0 ){
416
+ append_merge_mark(p->pOut, 1, 0, p->useCrLf);
417
+ blob_copy_lines(p->pOut, &res, nRes);
418
+ }
419
+ blob_reset(&res);
420
+
421
+ append_merge_mark(p->pOut, 2, p->lnPivot+1, p->useCrLf);
422
+ blob_copy_lines(p->pOut, p->pPivot, nPivot); p->lnPivot += nPivot;
423
+
424
+ append_merge_mark(p->pOut, 3, p->lnV2+1, p->useCrLf);
425
+ blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2;
426
+
427
+ append_merge_mark(p->pOut, 4, -1, p->useCrLf);
428
+}
429
+static void mergebuilder_init_text(MergeBuilder *p){
430
+ mergebuilder_init(p);
431
+ p->xStart = txtStart;
432
+ p->xSame = txtSame;
433
+ p->xChngV1 = txtChngV1;
434
+ p->xChngV2 = txtChngV2;
435
+ p->xChngBoth = txtChngBoth;
436
+ p->xConflict = txtConflict;
437
+}
438
+
439
+/************************* MergeBuilderTcl **********************************/
440
+/* Generate merge output formatted for reading by a TCL script.
441
+**
442
+** The output consists of lines of text, each with 4 tokens. The tokens
443
+** represent the content for one line from baseline, v1, v2, and output
444
+** respectively. The first character of each token provides auxiliary
445
+** information:
446
+**
447
+** . This line is omitted.
448
+** N Name of the file.
449
+** T Literal text follows that should have a \n terminator.
450
+** R Literal text follows that needs a \r\n terminator.
451
+** X Merge conflict.
452
+** Z Literal text without a line terminator.
453
+** S Skipped lines. Followed by number of lines to skip.
454
+** 1 Text is a copy of token 1
455
+** 2 Use data from data-token 2
456
+** 3 Use data from data-token 3
457
+*/
458
+
459
+/* Write text that goes into the interior of a double-quoted string in TCL */
460
+static void tclWriteQuotedText(Blob *pOut, const char *zIn, int nIn){
461
+ int j;
462
+ for(j=0; j<nIn; j++){
463
+ char c = zIn[j];
464
+ if( c=='\\' ){
465
+ blob_append(pOut, "\\\\", 2);
466
+ }else if( c=='"' ){
467
+ blob_append(pOut, "\\\"", 2);
468
+ }else if( c<' ' || c>0x7e ){
469
+ char z[5];
470
+ z[0] = '\\';
471
+ z[1] = "01234567"[(c>>6)&0x3];
472
+ z[2] = "01234567"[(c>>3)&0x7];
473
+ z[3] = "01234567"[c&0x7];
474
+ z[4] = 0;
475
+ blob_append(pOut, z, 4);
476
+ }else{
477
+ blob_append_char(pOut, c);
478
+ }
479
+ }
480
+}
481
+
482
+/* Copy one line of text from pIn and append to pOut, encoded as TCL */
483
+static void tclLineOfText(Blob *pOut, Blob *pIn, char cType){
484
+ int i, k;
485
+ for(i=pIn->iCursor; i<pIn->nUsed && pIn->aData[i]!='\n'; i++){}
486
+ if( i==pIn->nUsed ){
487
+ k = i;
488
+ }else if( i>pIn->iCursor && pIn->aData[i-1]=='\r' ){
489
+ k = i-1;
490
+ i++;
491
+ }else{
492
+ k = i;
493
+ i++;
494
+ }
495
+ blob_append_char(pOut, '"');
496
+ blob_append_char(pOut, cType);
497
+ tclWriteQuotedText(pOut, pIn->aData+pIn->iCursor, k-pIn->iCursor);
498
+ pIn->iCursor = i;
499
+ blob_append_char(pOut, '"');
500
+}
501
+static void tclStart(MergeBuilder *p){
502
+ Blob *pOut = p->pOut;
503
+ blob_append(pOut, "\"N", 2);
504
+ tclWriteQuotedText(pOut, p->zPivot, (int)strlen(p->zPivot));
505
+ blob_append(pOut, "\" \"N", 4);
506
+ tclWriteQuotedText(pOut, p->zV1, (int)strlen(p->zV1));
507
+ blob_append(pOut, "\" \"N", 4);
508
+ tclWriteQuotedText(pOut, p->zV2, (int)strlen(p->zV2));
509
+ blob_append(pOut, "\" \"N", 4);
510
+ if( p->zOut ){
511
+ tclWriteQuotedText(pOut, p->zOut, (int)strlen(p->zOut));
512
+ }else{
513
+ blob_append(pOut, "(Merge Result)", -1);
514
+ }
515
+ blob_append(pOut, "\"\n", 2);
516
+}
517
+static void tclSame(MergeBuilder *p, unsigned int N){
518
+ int i = 0;
519
+ int nSkip;
520
+
521
+ if( p->lnPivot>=2 || p->lnV1>2 || p->lnV2>2 ){
522
+ while( i<N && i<p->nContext ){
523
+ tclLineOfText(p->pOut, p->pPivot, 'T');
524
+ blob_append(p->pOut, " 1 1 1\n", 7);
525
+ i++;
526
+ }
527
+ nSkip = N - p->nContext*2;
528
+ }else{
529
+ nSkip = N - p->nContext;
530
+ }
531
+ if( nSkip>0 ){
532
+ blob_appendf(p->pOut, "\"S%d %d %d %d\" . . .\n",
533
+ nSkip, nSkip, nSkip, nSkip);
534
+ blob_copy_lines(0, p->pPivot, nSkip);
535
+ i += nSkip;
536
+ }
537
+
538
+ p->lnPivot += N;
539
+ p->lnV1 += N;
540
+ p->lnV2 += N;
541
+
542
+ if( p->lnPivot<p->mxPivot || p->lnV1<p->mxV1 || p->lnV2<p->mxV2 ){
543
+ while( i<N ){
544
+ tclLineOfText(p->pOut, p->pPivot, 'T');
545
+ blob_append(p->pOut, " 1 1 1\n", 7);
546
+ i++;
547
+ }
548
+ }
549
+
550
+ blob_copy_lines(0, p->pV1, N);
551
+ blob_copy_lines(0, p->pV2, N);
552
+}
553
+static void tclChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
554
+ int i;
555
+ for(i=0; i<nPivot && i<nV1; i++){
556
+ tclLineOfText(p->pOut, p->pPivot, 'T');
557
+ blob_append_char(p->pOut, ' ');
558
+ tclLineOfText(p->pOut, p->pV1, 'T');
559
+ blob_append(p->pOut, " 1 2\n", 5);
560
+ }
561
+ while( i<nPivot ){
562
+ tclLineOfText(p->pOut, p->pPivot, 'T');
563
+ blob_append(p->pOut, " . 1 .\n", 7);
564
+ i++;
565
+ }
566
+ while( i<nV1 ){
567
+ blob_append(p->pOut, ". ", 2);
568
+ tclLineOfText(p->pOut, p->pV1, 'T');
569
+ blob_append(p->pOut, " . 2\n", 5);
570
+ i++;
571
+ }
572
+ p->lnPivot += nPivot;
573
+ p->lnV1 += nV1;
574
+ p->lnV2 += nPivot;
575
+ blob_copy_lines(0, p->pV2, nPivot);
576
+}
577
+static void tclChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
578
+ int i;
579
+ for(i=0; i<nPivot && i<nV2; i++){
580
+ tclLineOfText(p->pOut, p->pPivot, 'T');
581
+ blob_append(p->pOut, " 1 ", 3);
582
+ tclLineOfText(p->pOut, p->pV2, 'T');
583
+ blob_append(p->pOut, " 3\n", 3);
584
+ }
585
+ while( i<nPivot ){
586
+ tclLineOfText(p->pOut, p->pPivot, 'T');
587
+ blob_append(p->pOut, " 1 . .\n", 7);
588
+ i++;
589
+ }
590
+ while( i<nV2 ){
591
+ blob_append(p->pOut, ". . ", 4);
592
+ tclLineOfText(p->pOut, p->pV2, 'T');
593
+ blob_append(p->pOut, " 3\n", 3);
594
+ i++;
595
+ }
596
+ p->lnPivot += nPivot;
597
+ p->lnV1 += nPivot;
598
+ p->lnV2 += nV2;
599
+ blob_copy_lines(0, p->pV1, nPivot);
600
+}
601
+static void tclChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
602
+ int i;
603
+ for(i=0; i<nPivot && i<nV; i++){
604
+ tclLineOfText(p->pOut, p->pPivot, 'T');
605
+ blob_append_char(p->pOut, ' ');
606
+ tclLineOfText(p->pOut, p->pV1, 'T');
607
+ blob_append(p->pOut, " 2 2\n", 5);
608
+ }
609
+ while( i<nPivot ){
610
+ tclLineOfText(p->pOut, p->pPivot, 'T');
611
+ blob_append(p->pOut, " . . .\n", 7);
612
+ i++;
613
+ }
614
+ while( i<nV ){
615
+ blob_append(p->pOut, ". ", 2);
616
+ tclLineOfText(p->pOut, p->pV1, 'T');
617
+ blob_append(p->pOut, " 2 2\n", 5);
618
+ i++;
619
+ }
620
+ p->lnPivot += nPivot;
621
+ p->lnV1 += nV;
622
+ p->lnV2 += nV;
623
+ blob_copy_lines(0, p->pV2, nV);
624
+}
625
+static void tclConflict(
626
+ MergeBuilder *p,
627
+ unsigned int nPivot,
628
+ unsigned int nV1,
629
+ unsigned int nV2
630
+){
631
+ int mx = nPivot;
632
+ int i;
633
+ int nRes;
634
+ Blob res;
635
+
636
+ merge_try_to_resolve_conflict(p, nPivot, nV1, nV2, &res);
637
+ nRes = blob_linecount(&res);
638
+ if( nV1>mx ) mx = nV1;
639
+ if( nV2>mx ) mx = nV2;
640
+ if( nRes>mx ) mx = nRes;
641
+ if( nRes>0 ){
642
+ blob_appendf(p->pOut, "\"S0 0 0 %d\" . . .\n", nV2+2);
643
+ }
644
+ for(i=0; i<mx; i++){
645
+ if( i<nPivot ){
646
+ tclLineOfText(p->pOut, p->pPivot, 'X');
647
+ }else{
648
+ blob_append_char(p->pOut, '.');
649
+ }
650
+ blob_append_char(p->pOut, ' ');
651
+ if( i<nV1 ){
652
+ tclLineOfText(p->pOut, p->pV1, 'X');
653
+ }else{
654
+ blob_append_char(p->pOut, '.');
655
+ }
656
+ blob_append_char(p->pOut, ' ');
657
+ if( i<nV2 ){
658
+ tclLineOfText(p->pOut, p->pV2, 'X');
659
+ }else{
660
+ blob_append_char(p->pOut, '.');
661
+ }
662
+ if( i<nRes ){
663
+ blob_append_char(p->pOut, ' ');
664
+ tclLineOfText(p->pOut, &res, 'X');
665
+ blob_append_char(p->pOut, '\n');
666
+ }else{
667
+ blob_append(p->pOut, " .\n", 3);
668
+ }
669
+ if( i==mx-1 ){
670
+ blob_appendf(p->pOut, "\"S0 0 0 %d\" . . .\n", nPivot+nV1+3);
671
+ }
672
+ }
673
+ blob_reset(&res);
674
+ p->lnPivot += nPivot;
675
+ p->lnV1 += nV1;
676
+ p->lnV2 += nV2;
677
+}
678
+void mergebuilder_init_tcl(MergeBuilder *p){
679
+ mergebuilder_init(p);
680
+ p->xStart = tclStart;
681
+ p->xSame = tclSame;
682
+ p->xChngV1 = tclChngV1;
683
+ p->xChngV2 = tclChngV2;
684
+ p->xChngBoth = tclChngBoth;
685
+ p->xConflict = tclConflict;
686
+}
687
+/*****************************************************************************/
688
+
689
+/*
690
+** The aC[] array contains triples of integers. Within each triple, the
691
+** elements are:
692
+**
693
+** (0) The number of lines to copy
694
+** (1) The number of lines to delete
695
+** (2) The number of liens to insert
696
+**
697
+** Suppose we want to advance over sz lines of the original file. This routine
698
+** returns true if that advance would land us on a copy operation. It
699
+** returns false if the advance would end on a delete.
700
+*/
701
+static int ends_with_copy(int *aC, int sz){
702
+ while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){
703
+ if( aC[0]>=sz ) return 1;
704
+ sz -= aC[0];
705
+ if( aC[1]>sz ) return 0;
706
+ sz -= aC[1];
707
+ aC += 3;
708
+ }
709
+ return 1;
710
+}
711
+
712
+/*
713
+** aC[] is an "edit triple" for changes from A to B. Advance through
714
+** this triple to determine the number of lines to bypass on B in order
715
+** to match an advance of sz lines on A.
716
+*/
717
+static int skip_conflict(
718
+ int *aC, /* Array of integer triples describing the edit */
719
+ int i, /* Index in aC[] of current location */
720
+ int sz, /* Lines of A that have been skipped */
721
+ unsigned int *pLn /* OUT: Lines of B to skip to keep aligment with A */
722
+){
723
+ *pLn = 0;
724
+ while( sz>0 ){
725
+ if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break;
726
+ if( aC[i]>=sz ){
727
+ aC[i] -= sz;
728
+ *pLn += sz;
729
+ break;
730
+ }
731
+ *pLn += aC[i];
732
+ *pLn += aC[i+2];
733
+ sz -= aC[i] + aC[i+1];
734
+ i += 3;
735
+ }
736
+ return i;
737
+}
191738
192739
/*
193740
** Do a three-way merge. Initialize pOut to contain the result.
194741
**
195742
** The merge is an edit against pV2. Both pV1 and pV2 have a
@@ -199,156 +746,113 @@
199746
** The return is 0 upon complete success. If any input file is binary,
200747
** -1 is returned and pOut is unmodified. If there are merge
201748
** conflicts, the merge proceeds as best as it can and the number
202749
** of conflicts is returns
203750
*/
204
-static int blob_merge(Blob *pPivot, Blob *pV1, Blob *pV2, Blob *pOut){
751
+int merge_three_blobs(MergeBuilder *p){
205752
int *aC1; /* Changes from pPivot to pV1 */
206753
int *aC2; /* Changes from pPivot to pV2 */
207754
int i1, i2; /* Index into aC1[] and aC2[] */
208755
int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */
209756
int limit1, limit2; /* Sizes of aC1[] and aC2[] */
210757
int nConflict = 0; /* Number of merge conflicts seen so far */
211
- int useCrLf = 0;
212
- int ln1, ln2, lnPivot; /* Line numbers for all files */
213758
DiffConfig DCfg;
214759
215
- blob_zero(pOut); /* Merge results stored in pOut */
216
-
217
- /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM),
218
- ** keep it in the output. This should be secure enough not to cause
219
- ** unintended changes to the merged file and consistent with what
220
- ** users are using in their source files.
221
- */
222
- if( starts_with_utf8_bom(pV1, 0) && starts_with_utf8_bom(pV2, 0) ){
223
- blob_append(pOut, (char*)get_utf8_bom(0), -1);
224
- }
225
-
226
- /* Check once to see if both pV1 and pV2 contains CR/LF endings.
227
- ** If true, CR/LF pair will be used later to append the
228
- ** boundary markers for merge conflicts.
229
- */
230
- if( contains_crlf(pV1) && contains_crlf(pV2) ){
231
- useCrLf = 1;
232
- }
233
-
234760
/* Compute the edits that occur from pPivot => pV1 (into aC1)
235761
** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is
236762
** an array of integer triples. Within each triple, the first integer
237763
** is the number of lines of text to copy directly from the pivot,
238764
** the second integer is the number of lines of text to omit from the
239765
** pivot, and the third integer is the number of lines of text that are
240766
** inserted. The edit array ends with a triple of 0,0,0.
241767
*/
242768
diff_config_init(&DCfg, 0);
243
- aC1 = text_diff(pPivot, pV1, 0, &DCfg);
244
- aC2 = text_diff(pPivot, pV2, 0, &DCfg);
769
+ DCfg.diffFlags = p->diffFlags;
770
+ aC1 = text_diff(p->pPivot, p->pV1, 0, &DCfg);
771
+ aC2 = text_diff(p->pPivot, p->pV2, 0, &DCfg);
245772
if( aC1==0 || aC2==0 ){
246773
free(aC1);
247774
free(aC2);
248775
return -1;
249776
}
250777
251
- blob_rewind(pV1); /* Rewind inputs: Needed to reconstruct output */
252
- blob_rewind(pV2);
253
- blob_rewind(pPivot);
778
+ blob_rewind(p->pV1); /* Rewind inputs: Needed to reconstruct output */
779
+ blob_rewind(p->pV2);
780
+ blob_rewind(p->pPivot);
254781
255782
/* Determine the length of the aC1[] and aC2[] change vectors */
256
- for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){}
783
+ p->mxPivot = 0;
784
+ p->mxV1 = 0;
785
+ for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){
786
+ p->mxPivot += aC1[i1] + aC1[i1+1];
787
+ p->mxV1 += aC1[i1] + aC1[i1+2];
788
+ }
257789
limit1 = i1;
258
- for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){}
790
+ p->mxV2 = 0;
791
+ for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){
792
+ p->mxV2 += aC2[i2] + aC2[i2+2];
793
+ }
259794
limit2 = i2;
260795
261
- DEBUG(
262
- for(i1=0; i1<limit1; i1+=3){
263
- printf("c1: %4d %4d %4d\n", aC1[i1], aC1[i1+1], aC1[i1+2]);
264
- }
265
- for(i2=0; i2<limit2; i2+=3){
266
- printf("c2: %4d %4d %4d\n", aC2[i2], aC2[i2+1], aC2[i2+2]);
267
- }
268
- )
796
+ /* Output header text and do any other required initialization */
797
+ p->xStart(p);
269798
270799
/* Loop over the two edit vectors and use them to compute merged text
271800
** which is written into pOut. i1 and i2 are multiples of 3 which are
272801
** indices into aC1[] and aC2[] to the edit triple currently being
273802
** processed
274803
*/
275804
i1 = i2 = 0;
276
- ln1 = ln2 = lnPivot = 1;
277805
while( i1<limit1 && i2<limit2 ){
278
- DEBUG( printf("%d: %2d %2d %2d %d: %2d %2d %2d\n",
279
- i1/3, aC1[i1], aC1[i1+1], aC1[i1+2],
280
- i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); )
281
-
282806
if( aC1[i1]>0 && aC2[i2]>0 ){
283807
/* Output text that is unchanged in both V1 and V2 */
284808
nCpy = min(aC1[i1], aC2[i2]);
285
- DEBUG( printf("COPY %d\n", nCpy); )
286
- blob_copy_lines(pOut, pPivot, nCpy); lnPivot += nCpy;
287
- blob_copy_lines(0, pV1, nCpy); ln1 += nCpy;
288
- blob_copy_lines(0, pV2, nCpy); ln2 += nCpy;
809
+ p->xSame(p, nCpy);
289810
aC1[i1] -= nCpy;
290811
aC2[i2] -= nCpy;
291812
}else
292813
if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){
293814
/* Output edits to V2 that occurs within unchanged regions of V1 */
294815
nDel = aC2[i2+1];
295816
nIns = aC2[i2+2];
296
- DEBUG( printf("EDIT -%d+%d left\n", nDel, nIns); )
297
- blob_copy_lines(0, pPivot, nDel); lnPivot += nDel;
298
- blob_copy_lines(0, pV1, nDel); ln1 += nDel;
299
- blob_copy_lines(pOut, pV2, nIns); ln2 += nIns;
817
+ p->xChngV2(p, nDel, nIns);
300818
aC1[i1] -= nDel;
301819
i2 += 3;
302820
}else
303821
if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){
304822
/* Output edits to V1 that occur within unchanged regions of V2 */
305823
nDel = aC1[i1+1];
306824
nIns = aC1[i1+2];
307
- DEBUG( printf("EDIT -%d+%d right\n", nDel, nIns); )
308
- blob_copy_lines(0, pPivot, nDel); lnPivot += nDel;
309
- blob_copy_lines(0, pV2, nDel); ln2 += nDel;
310
- blob_copy_lines(pOut, pV1, nIns); ln1 += nIns;
825
+ p->xChngV1(p, nDel, nIns);
311826
aC2[i2] -= nDel;
312827
i1 += 3;
313828
}else
314
- if( sameEdit(&aC1[i1], &aC2[i2], pV1, pV2) ){
829
+ if( sameEdit(&aC1[i1], &aC2[i2], p->pV1, p->pV2) ){
315830
/* Output edits that are identical in both V1 and V2. */
316831
assert( aC1[i1]==0 );
317832
nDel = aC1[i1+1];
318833
nIns = aC1[i1+2];
319
- DEBUG( printf("EDIT -%d+%d both\n", nDel, nIns); )
320
- blob_copy_lines(0, pPivot, nDel); lnPivot += nDel;
321
- blob_copy_lines(pOut, pV1, nIns); ln1 += nIns;
322
- blob_copy_lines(0, pV2, nIns); ln2 += nIns;
834
+ p->xChngBoth(p, nDel, nIns);
323835
i1 += 3;
324836
i2 += 3;
325837
}else
326838
{
327839
/* We have found a region where different edits to V1 and V2 overlap.
328840
** This is a merge conflict. Find the size of the conflict, then
329841
** output both possible edits separated by distinctive marks.
330842
*/
331
- int sz = 1; /* Size of the conflict in lines */
843
+ unsigned int sz = 1; /* Size of the conflict in the pivot, in lines */
844
+ unsigned int nV1, nV2; /* Size of conflict in V1 and V2, in lines */
332845
nConflict++;
333
- while( !ends_at_CPY(&aC1[i1], sz) || !ends_at_CPY(&aC2[i2], sz) ){
846
+ while( !ends_with_copy(&aC1[i1], sz) || !ends_with_copy(&aC2[i2], sz) ){
334847
sz++;
335848
}
336
- DEBUG( printf("CONFLICT %d\n", sz); )
337
-
338
- append_merge_mark(pOut, 0, ln1, useCrLf);
339
- i1 = output_one_side(pOut, pV1, aC1, i1, sz, &ln1);
340
-
341
- append_merge_mark(pOut, 1, lnPivot, useCrLf);
342
- blob_copy_lines(pOut, pPivot, sz); lnPivot += sz;
343
-
344
- append_merge_mark(pOut, 2, ln2, useCrLf);
345
- i2 = output_one_side(pOut, pV2, aC2, i2, sz, &ln2);
346
-
347
- append_merge_mark(pOut, 3, -1, useCrLf);
348
- }
349
-
849
+ i1 = skip_conflict(aC1, i1, sz, &nV1);
850
+ i2 = skip_conflict(aC2, i2, sz, &nV2);
851
+ p->xConflict(p, sz, nV1, nV2);
852
+ }
853
+
350854
/* If we are finished with an edit triple, advance to the next
351855
** triple.
352856
*/
353857
if( i1<limit1 && aC1[i1]==0 && aC1[i1+1]==0 && aC1[i1+2]==0 ) i1+=3;
354858
if( i2<limit2 && aC2[i2]==0 && aC2[i2+1]==0 && aC2[i2+2]==0 ) i2+=3;
@@ -356,20 +860,18 @@
356860
357861
/* When one of the two edit vectors reaches its end, there might still
358862
** be an insert in the other edit vector. Output this remaining
359863
** insert.
360864
*/
361
- DEBUG( printf("%d: %2d %2d %2d %d: %2d %2d %2d\n",
362
- i1/3, aC1[i1], aC1[i1+1], aC1[i1+2],
363
- i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); )
364865
if( i1<limit1 && aC1[i1+2]>0 ){
365
- DEBUG( printf("INSERT +%d left\n", aC1[i1+2]); )
366
- blob_copy_lines(pOut, pV1, aC1[i1+2]);
866
+ p->xChngV1(p, 0, aC1[i1+2]);
367867
}else if( i2<limit2 && aC2[i2+2]>0 ){
368
- DEBUG( printf("INSERT +%d right\n", aC2[i2+2]); )
369
- blob_copy_lines(pOut, pV2, aC2[i2+2]);
868
+ p->xChngV2(p, 0, aC2[i2+2]);
370869
}
870
+
871
+ /* Output footer text */
872
+ p->xEnd(p);
371873
372874
free(aC1);
373875
free(aC2);
374876
return nConflict;
375877
}
@@ -384,11 +886,12 @@
384886
const char *z = blob_buffer(p);
385887
int n = blob_size(p) - len + 1;
386888
assert( len==(int)strlen(mergeMarker[1]) );
387889
assert( len==(int)strlen(mergeMarker[2]) );
388890
assert( len==(int)strlen(mergeMarker[3]) );
389
- assert( count(mergeMarker)==4 );
891
+ assert( len==(int)strlen(mergeMarker[4]) );
892
+ assert( count(mergeMarker)==5 );
390893
for(i=0; i<n; ){
391894
for(j=0; j<4; j++){
392895
if( (memcmp(&z[i], mergeMarker[j], len)==0) ){
393896
return 1;
394897
}
@@ -408,18 +911,106 @@
408911
blob_read_from_file(&file, zFullpath, ExtFILE);
409912
rc = contains_merge_marker(&file);
410913
blob_reset(&file);
411914
return rc;
412915
}
916
+
917
+/*
918
+** Show merge output in a Tcl/Tk window, in response to the --tk option
919
+** to the "merge" or "3-way-merge" command.
920
+**
921
+** If fossil has direct access to a Tcl interpreter (either loaded
922
+** dynamically through stubs or linked in statically), we can use it
923
+** directly. Otherwise:
924
+** (1) Write the Tcl/Tk script used for rendering into a temp file.
925
+** (2) Invoke "tclsh" on the temp file using fossil_system().
926
+** (3) Delete the temp file.
927
+*/
928
+void merge_tk(const char *zSubCmd, int firstArg){
929
+ int i;
930
+ Blob script;
931
+ const char *zTempFile = 0;
932
+ char *zCmd;
933
+ const char *zTclsh;
934
+ const char *zCnt;
935
+ int bDarkMode = find_option("dark",0,0)!=0;
936
+ int nContext;
937
+ zCnt = find_option("context", "c", 1);
938
+ if( zCnt==0 ){
939
+ nContext = 6;
940
+ }else{
941
+ nContext = atoi(zCnt);
942
+ if( nContext<0 ) nContext = 0xfffffff;
943
+ }
944
+ blob_zero(&script);
945
+ blob_appendf(&script, "set ncontext %d\n", nContext);
946
+ blob_appendf(&script, "set fossilcmd {| \"%/\" %s -tcl",
947
+ g.nameOfExe, zSubCmd);
948
+ find_option("tcl",0,0);
949
+ find_option("debug",0,0);
950
+ zTclsh = find_option("tclsh",0,1);
951
+ if( zTclsh==0 ){
952
+ zTclsh = db_get("tclsh",0);
953
+ }
954
+ /* The undocumented --script FILENAME option causes the Tk script to
955
+ ** be written into the FILENAME instead of being run. This is used
956
+ ** for testing and debugging. */
957
+ zTempFile = find_option("script",0,1);
958
+ verify_all_options();
959
+
960
+ if( (g.argc - firstArg)!=3 ){
961
+ fossil_fatal("Requires 3 filename arguments");
962
+ }
963
+
964
+ for(i=firstArg; i<g.argc; i++){
965
+ const char *z = g.argv[i];
966
+ if( sqlite3_strglob("*}*",z) ){
967
+ blob_appendf(&script, " {%/}", z);
968
+ }else{
969
+ int j;
970
+ blob_append(&script, " ", 1);
971
+ for(j=0; z[j]; j++) blob_appendf(&script, "\\%03o", (unsigned char)z[j]);
972
+ }
973
+ }
974
+ blob_appendf(&script, "}\nset darkmode %d\n", bDarkMode);
975
+ blob_appendf(&script, "%s", builtin_file("merge.tcl", 0));
976
+ if( zTempFile ){
977
+ blob_write_to_file(&script, zTempFile);
978
+ fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile);
979
+ }else{
980
+#if defined(FOSSIL_ENABLE_TCL)
981
+ Th_FossilInit(TH_INIT_DEFAULT);
982
+ if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script),
983
+ blob_size(&script), 1, 1, 0)==TCL_OK ){
984
+ blob_reset(&script);
985
+ return;
986
+ }
987
+ /*
988
+ * If evaluation of the Tcl script fails, the reason may be that Tk
989
+ * could not be found by the loaded Tcl, or that Tcl cannot be loaded
990
+ * dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback
991
+ * to using the external "tclsh", if available.
992
+ */
993
+#endif
994
+ zTempFile = write_blob_to_temp_file(&script);
995
+ zCmd = mprintf("%$ %$", zTclsh, zTempFile);
996
+ fossil_system(zCmd);
997
+ file_delete(zTempFile);
998
+ fossil_free(zCmd);
999
+ }
1000
+ blob_reset(&script);
1001
+}
1002
+
4131003
4141004
/*
4151005
** COMMAND: 3-way-merge*
4161006
**
417
-** Usage: %fossil 3-way-merge BASELINE V1 V2 MERGED
1007
+** Usage: %fossil 3-way-merge BASELINE V1 V2 [MERGED]
4181008
**
4191009
** Inputs are files BASELINE, V1, and V2. The file MERGED is generated
420
-** as output.
1010
+** as output. If no MERGED file is specified, output is sent to
1011
+** stdout.
4211012
**
4221013
** BASELINE is a common ancestor of two files V1 and V2 that have diverging
4231014
** edits. The generated output file MERGED is the combination of all
4241015
** changes in both V1 and V2.
4251016
**
@@ -436,38 +1027,75 @@
4361027
** cp Xup.c Xbase.c
4371028
** # Verify that everything still works
4381029
** fossil commit
4391030
**
4401031
*/
441
-void delta_3waymerge_cmd(void){
442
- Blob pivot, v1, v2, merged;
1032
+void merge_3way_cmd(void){
1033
+ MergeBuilder s;
4431034
int nConflict;
1035
+ Blob pivot, v1, v2, out;
1036
+ int noWarn = 0;
1037
+ const char *zCnt;
1038
+
1039
+ if( find_option("tk", 0, 0)!=0 ){
1040
+ merge_tk("3-way-merge", 2);
1041
+ return;
1042
+ }
1043
+ mergebuilder_init_text(&s);
1044
+ if( find_option("debug", 0, 0) ){
1045
+ mergebuilder_init(&s);
1046
+ }
1047
+ if( find_option("tcl", 0, 0) ){
1048
+ mergebuilder_init_tcl(&s);
1049
+ noWarn = 1;
1050
+ }
1051
+ zCnt = find_option("context", "c", 1);
1052
+ if( zCnt ){
1053
+ s.nContext = atoi(zCnt);
1054
+ if( s.nContext<0 ) s.nContext = 0xfffffff;
1055
+ }else{
1056
+ s.nContext = 6;
1057
+ }
1058
+ blob_zero(&pivot); s.pPivot = &pivot;
1059
+ blob_zero(&v1); s.pV1 = &v1;
1060
+ blob_zero(&v2); s.pV2 = &v2;
1061
+ blob_zero(&out); s.pOut = &out;
4441062
4451063
/* We should be done with options.. */
4461064
verify_all_options();
4471065
448
- if( g.argc!=6 ){
449
- usage("PIVOT V1 V2 MERGED");
1066
+ if( g.argc!=6 && g.argc!=5 ){
1067
+ usage("[OPTIONS] PIVOT V1 V2 [MERGED]");
4501068
}
451
- if( blob_read_from_file(&pivot, g.argv[2], ExtFILE)<0 ){
1069
+ s.zPivot = file_tail(g.argv[2]);
1070
+ s.zV1 = file_tail(g.argv[3]);
1071
+ s.zV2 = file_tail(g.argv[4]);
1072
+ if( blob_read_from_file(s.pPivot, g.argv[2], ExtFILE)<0 ){
4521073
fossil_fatal("cannot read %s", g.argv[2]);
4531074
}
454
- if( blob_read_from_file(&v1, g.argv[3], ExtFILE)<0 ){
1075
+ if( blob_read_from_file(s.pV1, g.argv[3], ExtFILE)<0 ){
4551076
fossil_fatal("cannot read %s", g.argv[3]);
4561077
}
457
- if( blob_read_from_file(&v2, g.argv[4], ExtFILE)<0 ){
1078
+ if( blob_read_from_file(s.pV2, g.argv[4], ExtFILE)<0 ){
4581079
fossil_fatal("cannot read %s", g.argv[4]);
4591080
}
460
- nConflict = blob_merge(&pivot, &v1, &v2, &merged);
461
- if( blob_write_to_file(&merged, g.argv[5])<(int)blob_size(&merged) ){
462
- fossil_fatal("cannot write %s", g.argv[4]);
1081
+ nConflict = merge_three_blobs(&s);
1082
+ if( g.argc==6 ){
1083
+ s.zOut = file_tail(g.argv[5]);
1084
+ blob_write_to_file(s.pOut, g.argv[5]);
1085
+ }else{
1086
+ s.zOut = "(Merge Result)";
1087
+ blob_write_to_file(s.pOut, "-");
4631088
}
1089
+ s.xDestroy(&s);
4641090
blob_reset(&pivot);
4651091
blob_reset(&v1);
4661092
blob_reset(&v2);
467
- blob_reset(&merged);
468
- if( nConflict>0 ) fossil_warning("WARNING: %d merge conflicts", nConflict);
1093
+ blob_reset(&out);
1094
+ if( nConflict>0 && !noWarn ){
1095
+ fossil_warning("WARNING: %d merge conflicts", nConflict);
1096
+ }
4691097
}
4701098
4711099
/*
4721100
** aSubst is an array of string pairs. The first element of each pair is
4731101
** a string that begins with %. The second element is a replacement for that
@@ -505,32 +1133,32 @@
5051133
5061134
#if INTERFACE
5071135
/*
5081136
** Flags to the 3-way merger
5091137
*/
510
-#define MERGE_DRYRUN 0x0001
1138
+#define MERGE_DRYRUN 0x0001
5111139
/*
5121140
** The MERGE_KEEP_FILES flag specifies that merge_3way() should retain
5131141
** its temporary files on error. By default they are removed after the
5141142
** merge, regardless of success or failure.
5151143
*/
516
-#define MERGE_KEEP_FILES 0x0002
1144
+#define MERGE_KEEP_FILES 0x0002
5171145
#endif
5181146
5191147
5201148
/*
521
-** This routine is a wrapper around blob_merge() with the following
1149
+** This routine is a wrapper around merge_three_blobs() with the following
5221150
** enhancements:
5231151
**
5241152
** (1) If the merge-command is defined, then use the external merging
5251153
** program specified instead of the built-in blob-merge to do the
5261154
** merging. Panic if the external merger fails.
5271155
** ** Not currently implemented **
5281156
**
5291157
** (2) If gmerge-command is defined and there are merge conflicts in
530
-** blob_merge() then invoke the external graphical merger to resolve
531
-** the conflicts.
1158
+** merge_three_blobs() then invoke the external graphical merger
1159
+** to resolve the conflicts.
5321160
**
5331161
** (3) If a merge conflict occurs and gmerge-command is not defined,
5341162
** then write the pivot, original, and merge-in files to the
5351163
** filesystem.
5361164
*/
@@ -539,30 +1167,37 @@
5391167
const char *zV1, /* Name of file for version merging into (mine) */
5401168
Blob *pV2, /* Version merging from (yours) */
5411169
Blob *pOut, /* Output written here */
5421170
unsigned mergeFlags /* Flags that control operation */
5431171
){
544
- Blob v1; /* Content of zV1 */
545
- int rc; /* Return code of subroutines and this routine */
1172
+ Blob v1; /* Content of zV1 */
1173
+ int rc; /* Return code of subroutines and this routine */
5461174
const char *zGMerge; /* Name of the gmerge command */
1175
+ MergeBuilder s; /* The merge state */
5471176
548
- blob_read_from_file(&v1, zV1, ExtFILE);
549
- rc = blob_merge(pPivot, &v1, pV2, pOut);
1177
+ mergebuilder_init_text(&s);
1178
+ s.pPivot = pPivot;
1179
+ s.pV1 = &v1;
1180
+ s.pV2 = pV2;
1181
+ blob_zero(pOut);
1182
+ s.pOut = pOut;
1183
+ blob_read_from_file(s.pV1, zV1, ExtFILE);
1184
+ rc = merge_three_blobs(&s);
5501185
zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0);
5511186
if( (mergeFlags & MERGE_DRYRUN)==0
5521187
&& ((zGMerge!=0 && zGMerge[0]!=0)
5531188
|| (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){
5541189
char *zPivot; /* Name of the pivot file */
5551190
char *zOrig; /* Name of the original content file */
5561191
char *zOther; /* Name of the merge file */
5571192
5581193
zPivot = file_newname(zV1, "baseline", 1);
559
- blob_write_to_file(pPivot, zPivot);
1194
+ blob_write_to_file(s.pPivot, zPivot);
5601195
zOrig = file_newname(zV1, "original", 1);
561
- blob_write_to_file(&v1, zOrig);
1196
+ blob_write_to_file(s.pV1, zOrig);
5621197
zOther = file_newname(zV1, "merge", 1);
563
- blob_write_to_file(pV2, zOther);
1198
+ blob_write_to_file(s.pV2, zOther);
5641199
if( rc>0 ){
5651200
if( zGMerge && zGMerge[0] ){
5661201
char *zOut; /* Temporary output file */
5671202
char *zCmd; /* Command to invoke */
5681203
const char *azSubst[8]; /* Strings to be substituted */
@@ -590,7 +1225,8 @@
5901225
fossil_free(zPivot);
5911226
fossil_free(zOrig);
5921227
fossil_free(zOther);
5931228
}
5941229
blob_reset(&v1);
1230
+ s.xDestroy(&s);
5951231
return rc;
5961232
}
5971233
--- src/merge3.c
+++ src/merge3.c
@@ -75,75 +75,17 @@
75 if( aC1[2]!=aC2[2] ) return 0;
76 if( sameLines(pV1, pV2, aC1[2]) ) return 1;
77 return 0;
78 }
79
80 /*
81 ** The aC[] array contains triples of integers. Within each triple, the
82 ** elements are:
83 **
84 ** (0) The number of lines to copy
85 ** (1) The number of lines to delete
86 ** (2) The number of liens to insert
87 **
88 ** Suppose we want to advance over sz lines of the original file. This routine
89 ** returns true if that advance would land us on a copy operation. It
90 ** returns false if the advance would end on a delete.
91 */
92 static int ends_at_CPY(int *aC, int sz){
93 while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){
94 if( aC[0]>=sz ) return 1;
95 sz -= aC[0];
96 if( aC[1]>sz ) return 0;
97 sz -= aC[1];
98 aC += 3;
99 }
100 return 1;
101 }
102
103 /*
104 ** pSrc contains an edited file where aC[] describes the edit. Part of
105 ** pSrc has already been output. This routine outputs additional lines
106 ** of pSrc - lines that correspond to the next sz lines of the original
107 ** unedited file.
108 **
109 ** Note that sz counts the number of lines of text in the original file.
110 ** But text is output from the edited file. So the number of lines transfer
111 ** to pOut might be different from sz. Fewer lines appear in pOut if there
112 ** are deletes. More lines appear if there are inserts.
113 **
114 ** The aC[] array is updated and the new index into aC[] is returned.
115 */
116 static int output_one_side(
117 Blob *pOut, /* Write to this blob */
118 Blob *pSrc, /* The edited file that is to be copied to pOut */
119 int *aC, /* Array of integer triples describing the edit */
120 int i, /* Index in aC[] of current location in pSrc */
121 int sz, /* Number of lines in unedited source to output */
122 int *pLn /* Line number counter */
123 ){
124 while( sz>0 ){
125 if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break;
126 if( aC[i]>=sz ){
127 blob_copy_lines(pOut, pSrc, sz); *pLn += sz;
128 aC[i] -= sz;
129 break;
130 }
131 blob_copy_lines(pOut, pSrc, aC[i]); *pLn += aC[i];
132 blob_copy_lines(pOut, pSrc, aC[i+2]); *pLn += aC[i+2];
133 sz -= aC[i] + aC[i+1];
134 i += 3;
135 }
136 return i;
137 }
138
139 /*
140 ** Text of boundary markers for merge conflicts.
141 */
142 static const char *const mergeMarker[] = {
143 /*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/
144 "<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<",
 
145 "||||||| COMMON ANCESTOR content follows |||||||||||||||||||||||||",
146 "======= MERGED IN content follows ===============================",
147 ">>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
148 };
149
@@ -186,10 +128,615 @@
186 ensure_line_end(pOut, useCrLf);
187 blob_append(pOut, mergeMarker[iMark], -1);
188 if( ln>0 ) blob_appendf(pOut, " (line %d)", ln);
189 ensure_line_end(pOut, useCrLf);
190 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
192 /*
193 ** Do a three-way merge. Initialize pOut to contain the result.
194 **
195 ** The merge is an edit against pV2. Both pV1 and pV2 have a
@@ -199,156 +746,113 @@
199 ** The return is 0 upon complete success. If any input file is binary,
200 ** -1 is returned and pOut is unmodified. If there are merge
201 ** conflicts, the merge proceeds as best as it can and the number
202 ** of conflicts is returns
203 */
204 static int blob_merge(Blob *pPivot, Blob *pV1, Blob *pV2, Blob *pOut){
205 int *aC1; /* Changes from pPivot to pV1 */
206 int *aC2; /* Changes from pPivot to pV2 */
207 int i1, i2; /* Index into aC1[] and aC2[] */
208 int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */
209 int limit1, limit2; /* Sizes of aC1[] and aC2[] */
210 int nConflict = 0; /* Number of merge conflicts seen so far */
211 int useCrLf = 0;
212 int ln1, ln2, lnPivot; /* Line numbers for all files */
213 DiffConfig DCfg;
214
215 blob_zero(pOut); /* Merge results stored in pOut */
216
217 /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM),
218 ** keep it in the output. This should be secure enough not to cause
219 ** unintended changes to the merged file and consistent with what
220 ** users are using in their source files.
221 */
222 if( starts_with_utf8_bom(pV1, 0) && starts_with_utf8_bom(pV2, 0) ){
223 blob_append(pOut, (char*)get_utf8_bom(0), -1);
224 }
225
226 /* Check once to see if both pV1 and pV2 contains CR/LF endings.
227 ** If true, CR/LF pair will be used later to append the
228 ** boundary markers for merge conflicts.
229 */
230 if( contains_crlf(pV1) && contains_crlf(pV2) ){
231 useCrLf = 1;
232 }
233
234 /* Compute the edits that occur from pPivot => pV1 (into aC1)
235 ** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is
236 ** an array of integer triples. Within each triple, the first integer
237 ** is the number of lines of text to copy directly from the pivot,
238 ** the second integer is the number of lines of text to omit from the
239 ** pivot, and the third integer is the number of lines of text that are
240 ** inserted. The edit array ends with a triple of 0,0,0.
241 */
242 diff_config_init(&DCfg, 0);
243 aC1 = text_diff(pPivot, pV1, 0, &DCfg);
244 aC2 = text_diff(pPivot, pV2, 0, &DCfg);
 
245 if( aC1==0 || aC2==0 ){
246 free(aC1);
247 free(aC2);
248 return -1;
249 }
250
251 blob_rewind(pV1); /* Rewind inputs: Needed to reconstruct output */
252 blob_rewind(pV2);
253 blob_rewind(pPivot);
254
255 /* Determine the length of the aC1[] and aC2[] change vectors */
256 for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){}
 
 
 
 
 
257 limit1 = i1;
258 for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){}
 
 
 
259 limit2 = i2;
260
261 DEBUG(
262 for(i1=0; i1<limit1; i1+=3){
263 printf("c1: %4d %4d %4d\n", aC1[i1], aC1[i1+1], aC1[i1+2]);
264 }
265 for(i2=0; i2<limit2; i2+=3){
266 printf("c2: %4d %4d %4d\n", aC2[i2], aC2[i2+1], aC2[i2+2]);
267 }
268 )
269
270 /* Loop over the two edit vectors and use them to compute merged text
271 ** which is written into pOut. i1 and i2 are multiples of 3 which are
272 ** indices into aC1[] and aC2[] to the edit triple currently being
273 ** processed
274 */
275 i1 = i2 = 0;
276 ln1 = ln2 = lnPivot = 1;
277 while( i1<limit1 && i2<limit2 ){
278 DEBUG( printf("%d: %2d %2d %2d %d: %2d %2d %2d\n",
279 i1/3, aC1[i1], aC1[i1+1], aC1[i1+2],
280 i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); )
281
282 if( aC1[i1]>0 && aC2[i2]>0 ){
283 /* Output text that is unchanged in both V1 and V2 */
284 nCpy = min(aC1[i1], aC2[i2]);
285 DEBUG( printf("COPY %d\n", nCpy); )
286 blob_copy_lines(pOut, pPivot, nCpy); lnPivot += nCpy;
287 blob_copy_lines(0, pV1, nCpy); ln1 += nCpy;
288 blob_copy_lines(0, pV2, nCpy); ln2 += nCpy;
289 aC1[i1] -= nCpy;
290 aC2[i2] -= nCpy;
291 }else
292 if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){
293 /* Output edits to V2 that occurs within unchanged regions of V1 */
294 nDel = aC2[i2+1];
295 nIns = aC2[i2+2];
296 DEBUG( printf("EDIT -%d+%d left\n", nDel, nIns); )
297 blob_copy_lines(0, pPivot, nDel); lnPivot += nDel;
298 blob_copy_lines(0, pV1, nDel); ln1 += nDel;
299 blob_copy_lines(pOut, pV2, nIns); ln2 += nIns;
300 aC1[i1] -= nDel;
301 i2 += 3;
302 }else
303 if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){
304 /* Output edits to V1 that occur within unchanged regions of V2 */
305 nDel = aC1[i1+1];
306 nIns = aC1[i1+2];
307 DEBUG( printf("EDIT -%d+%d right\n", nDel, nIns); )
308 blob_copy_lines(0, pPivot, nDel); lnPivot += nDel;
309 blob_copy_lines(0, pV2, nDel); ln2 += nDel;
310 blob_copy_lines(pOut, pV1, nIns); ln1 += nIns;
311 aC2[i2] -= nDel;
312 i1 += 3;
313 }else
314 if( sameEdit(&aC1[i1], &aC2[i2], pV1, pV2) ){
315 /* Output edits that are identical in both V1 and V2. */
316 assert( aC1[i1]==0 );
317 nDel = aC1[i1+1];
318 nIns = aC1[i1+2];
319 DEBUG( printf("EDIT -%d+%d both\n", nDel, nIns); )
320 blob_copy_lines(0, pPivot, nDel); lnPivot += nDel;
321 blob_copy_lines(pOut, pV1, nIns); ln1 += nIns;
322 blob_copy_lines(0, pV2, nIns); ln2 += nIns;
323 i1 += 3;
324 i2 += 3;
325 }else
326 {
327 /* We have found a region where different edits to V1 and V2 overlap.
328 ** This is a merge conflict. Find the size of the conflict, then
329 ** output both possible edits separated by distinctive marks.
330 */
331 int sz = 1; /* Size of the conflict in lines */
 
332 nConflict++;
333 while( !ends_at_CPY(&aC1[i1], sz) || !ends_at_CPY(&aC2[i2], sz) ){
334 sz++;
335 }
336 DEBUG( printf("CONFLICT %d\n", sz); )
337
338 append_merge_mark(pOut, 0, ln1, useCrLf);
339 i1 = output_one_side(pOut, pV1, aC1, i1, sz, &ln1);
340
341 append_merge_mark(pOut, 1, lnPivot, useCrLf);
342 blob_copy_lines(pOut, pPivot, sz); lnPivot += sz;
343
344 append_merge_mark(pOut, 2, ln2, useCrLf);
345 i2 = output_one_side(pOut, pV2, aC2, i2, sz, &ln2);
346
347 append_merge_mark(pOut, 3, -1, useCrLf);
348 }
349
350 /* If we are finished with an edit triple, advance to the next
351 ** triple.
352 */
353 if( i1<limit1 && aC1[i1]==0 && aC1[i1+1]==0 && aC1[i1+2]==0 ) i1+=3;
354 if( i2<limit2 && aC2[i2]==0 && aC2[i2+1]==0 && aC2[i2+2]==0 ) i2+=3;
@@ -356,20 +860,18 @@
356
357 /* When one of the two edit vectors reaches its end, there might still
358 ** be an insert in the other edit vector. Output this remaining
359 ** insert.
360 */
361 DEBUG( printf("%d: %2d %2d %2d %d: %2d %2d %2d\n",
362 i1/3, aC1[i1], aC1[i1+1], aC1[i1+2],
363 i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); )
364 if( i1<limit1 && aC1[i1+2]>0 ){
365 DEBUG( printf("INSERT +%d left\n", aC1[i1+2]); )
366 blob_copy_lines(pOut, pV1, aC1[i1+2]);
367 }else if( i2<limit2 && aC2[i2+2]>0 ){
368 DEBUG( printf("INSERT +%d right\n", aC2[i2+2]); )
369 blob_copy_lines(pOut, pV2, aC2[i2+2]);
370 }
 
 
 
371
372 free(aC1);
373 free(aC2);
374 return nConflict;
375 }
@@ -384,11 +886,12 @@
384 const char *z = blob_buffer(p);
385 int n = blob_size(p) - len + 1;
386 assert( len==(int)strlen(mergeMarker[1]) );
387 assert( len==(int)strlen(mergeMarker[2]) );
388 assert( len==(int)strlen(mergeMarker[3]) );
389 assert( count(mergeMarker)==4 );
 
390 for(i=0; i<n; ){
391 for(j=0; j<4; j++){
392 if( (memcmp(&z[i], mergeMarker[j], len)==0) ){
393 return 1;
394 }
@@ -408,18 +911,106 @@
408 blob_read_from_file(&file, zFullpath, ExtFILE);
409 rc = contains_merge_marker(&file);
410 blob_reset(&file);
411 return rc;
412 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
414 /*
415 ** COMMAND: 3-way-merge*
416 **
417 ** Usage: %fossil 3-way-merge BASELINE V1 V2 MERGED
418 **
419 ** Inputs are files BASELINE, V1, and V2. The file MERGED is generated
420 ** as output.
 
421 **
422 ** BASELINE is a common ancestor of two files V1 and V2 that have diverging
423 ** edits. The generated output file MERGED is the combination of all
424 ** changes in both V1 and V2.
425 **
@@ -436,38 +1027,75 @@
436 ** cp Xup.c Xbase.c
437 ** # Verify that everything still works
438 ** fossil commit
439 **
440 */
441 void delta_3waymerge_cmd(void){
442 Blob pivot, v1, v2, merged;
443 int nConflict;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
445 /* We should be done with options.. */
446 verify_all_options();
447
448 if( g.argc!=6 ){
449 usage("PIVOT V1 V2 MERGED");
450 }
451 if( blob_read_from_file(&pivot, g.argv[2], ExtFILE)<0 ){
 
 
 
452 fossil_fatal("cannot read %s", g.argv[2]);
453 }
454 if( blob_read_from_file(&v1, g.argv[3], ExtFILE)<0 ){
455 fossil_fatal("cannot read %s", g.argv[3]);
456 }
457 if( blob_read_from_file(&v2, g.argv[4], ExtFILE)<0 ){
458 fossil_fatal("cannot read %s", g.argv[4]);
459 }
460 nConflict = blob_merge(&pivot, &v1, &v2, &merged);
461 if( blob_write_to_file(&merged, g.argv[5])<(int)blob_size(&merged) ){
462 fossil_fatal("cannot write %s", g.argv[4]);
 
 
 
 
463 }
 
464 blob_reset(&pivot);
465 blob_reset(&v1);
466 blob_reset(&v2);
467 blob_reset(&merged);
468 if( nConflict>0 ) fossil_warning("WARNING: %d merge conflicts", nConflict);
 
 
469 }
470
471 /*
472 ** aSubst is an array of string pairs. The first element of each pair is
473 ** a string that begins with %. The second element is a replacement for that
@@ -505,32 +1133,32 @@
505
506 #if INTERFACE
507 /*
508 ** Flags to the 3-way merger
509 */
510 #define MERGE_DRYRUN 0x0001
511 /*
512 ** The MERGE_KEEP_FILES flag specifies that merge_3way() should retain
513 ** its temporary files on error. By default they are removed after the
514 ** merge, regardless of success or failure.
515 */
516 #define MERGE_KEEP_FILES 0x0002
517 #endif
518
519
520 /*
521 ** This routine is a wrapper around blob_merge() with the following
522 ** enhancements:
523 **
524 ** (1) If the merge-command is defined, then use the external merging
525 ** program specified instead of the built-in blob-merge to do the
526 ** merging. Panic if the external merger fails.
527 ** ** Not currently implemented **
528 **
529 ** (2) If gmerge-command is defined and there are merge conflicts in
530 ** blob_merge() then invoke the external graphical merger to resolve
531 ** the conflicts.
532 **
533 ** (3) If a merge conflict occurs and gmerge-command is not defined,
534 ** then write the pivot, original, and merge-in files to the
535 ** filesystem.
536 */
@@ -539,30 +1167,37 @@
539 const char *zV1, /* Name of file for version merging into (mine) */
540 Blob *pV2, /* Version merging from (yours) */
541 Blob *pOut, /* Output written here */
542 unsigned mergeFlags /* Flags that control operation */
543 ){
544 Blob v1; /* Content of zV1 */
545 int rc; /* Return code of subroutines and this routine */
546 const char *zGMerge; /* Name of the gmerge command */
 
547
548 blob_read_from_file(&v1, zV1, ExtFILE);
549 rc = blob_merge(pPivot, &v1, pV2, pOut);
 
 
 
 
 
 
550 zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0);
551 if( (mergeFlags & MERGE_DRYRUN)==0
552 && ((zGMerge!=0 && zGMerge[0]!=0)
553 || (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){
554 char *zPivot; /* Name of the pivot file */
555 char *zOrig; /* Name of the original content file */
556 char *zOther; /* Name of the merge file */
557
558 zPivot = file_newname(zV1, "baseline", 1);
559 blob_write_to_file(pPivot, zPivot);
560 zOrig = file_newname(zV1, "original", 1);
561 blob_write_to_file(&v1, zOrig);
562 zOther = file_newname(zV1, "merge", 1);
563 blob_write_to_file(pV2, zOther);
564 if( rc>0 ){
565 if( zGMerge && zGMerge[0] ){
566 char *zOut; /* Temporary output file */
567 char *zCmd; /* Command to invoke */
568 const char *azSubst[8]; /* Strings to be substituted */
@@ -590,7 +1225,8 @@
590 fossil_free(zPivot);
591 fossil_free(zOrig);
592 fossil_free(zOther);
593 }
594 blob_reset(&v1);
 
595 return rc;
596 }
597
--- src/merge3.c
+++ src/merge3.c
@@ -75,75 +75,17 @@
75 if( aC1[2]!=aC2[2] ) return 0;
76 if( sameLines(pV1, pV2, aC1[2]) ) return 1;
77 return 0;
78 }
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80 /*
81 ** Text of boundary markers for merge conflicts.
82 */
83 static const char *const mergeMarker[] = {
84 /*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/
85 "<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<",
86 "####### SUGGESTED CONFLICT RESOLUTION follows ###################",
87 "||||||| COMMON ANCESTOR content follows |||||||||||||||||||||||||",
88 "======= MERGED IN content follows ===============================",
89 ">>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
90 };
91
@@ -186,10 +128,615 @@
128 ensure_line_end(pOut, useCrLf);
129 blob_append(pOut, mergeMarker[iMark], -1);
130 if( ln>0 ) blob_appendf(pOut, " (line %d)", ln);
131 ensure_line_end(pOut, useCrLf);
132 }
133
134 #if INTERFACE
135 /*
136 ** This is an abstract class for constructing a merge.
137 ** Subclasses of this object format the merge output in different ways.
138 **
139 ** To subclass, create an instance of the MergeBuilder object and fill
140 ** in appropriate method implementations.
141 */
142 struct MergeBuilder {
143 void (*xStart)(MergeBuilder*);
144 void (*xSame)(MergeBuilder*, unsigned int);
145 void (*xChngV1)(MergeBuilder*, unsigned int, unsigned int);
146 void (*xChngV2)(MergeBuilder*, unsigned int, unsigned int);
147 void (*xChngBoth)(MergeBuilder*, unsigned int, unsigned int);
148 void (*xConflict)(MergeBuilder*, unsigned int, unsigned int, unsigned int);
149 void (*xEnd)(MergeBuilder*);
150 void (*xDestroy)(MergeBuilder*);
151 const char *zPivot; /* Label or name for the pivot */
152 const char *zV1; /* Label or name for the V1 file */
153 const char *zV2; /* Label or name for the V2 file */
154 const char *zOut; /* Label or name for the output */
155 Blob *pPivot; /* The common ancestor */
156 Blob *pV1; /* First variant (local copy) */
157 Blob *pV2; /* Second variant (merged in) */
158 Blob *pOut; /* Write merge results here */
159 int useCrLf; /* Use CRLF line endings */
160 int nContext; /* Size of unchanged line boundaries */
161 unsigned int mxPivot; /* Number of lines in the pivot */
162 unsigned int mxV1; /* Number of lines in V1 */
163 unsigned int mxV2; /* Number of lines in V2 */
164 unsigned int lnPivot; /* Lines read from pivot */
165 unsigned int lnV1; /* Lines read from v1 */
166 unsigned int lnV2; /* Lines read from v2 */
167 unsigned int lnOut; /* Lines written to out */
168 unsigned int nConflict; /* Number of conflicts seen */
169 u64 diffFlags; /* Flags for difference engine */
170 };
171 #endif /* INTERFACE */
172
173
174 /************************* Generic MergeBuilder ******************************/
175 /* These are generic methods for MergeBuilder. They just output debugging
176 ** information. But some of them are useful as base methods for other useful
177 ** implementations of MergeBuilder.
178 */
179
180 /* xStart() and xEnd() are called to generate header and fotter information
181 ** in the output. This is a no-op in the generic implementation.
182 */
183 static void dbgStartEnd(MergeBuilder *p){ (void)p; }
184
185 /* The next N lines of PIVOT are unchanged in both V1 and V2
186 */
187 static void dbgSame(MergeBuilder *p, unsigned int N){
188 blob_appendf(p->pOut,
189 "COPY %u from BASELINE(%u..%u) or V1(%u..%u) or V2(%u..%u)\n",
190 N, p->lnPivot+1, p->lnPivot+N, p->lnV1+1, p->lnV1+N,
191 p->lnV2+1, p->lnV2+N);
192 p->lnPivot += N;
193 p->lnV1 += N;
194 p->lnV2 += N;
195 }
196
197 /* The next nPivot lines of the PIVOT are changed into nV1 lines by V1
198 */
199 static void dbgChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
200 blob_appendf(p->pOut, "COPY %u from V1(%u..%u)\n",
201 nV1, p->lnV1+1, p->lnV1+nV1);
202 p->lnPivot += nPivot;
203 p->lnV2 += nPivot;
204 p->lnV1 += nV1;
205 }
206
207 /* The next nPivot lines of the PIVOT are changed into nV2 lines by V2
208 */
209 static void dbgChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
210 blob_appendf(p->pOut, "COPY %u lines FROM V2(%u..%u)\n",
211 nV2, p->lnV2+1, p->lnV2+nV2);
212 p->lnPivot += nPivot;
213 p->lnV1 += nPivot;
214 p->lnV2 += nV2;
215 }
216
217 /* The next nPivot lines of the PIVOT are changed into nV lines from V1 and
218 ** V2, which should be the same. In other words, the same change is found
219 ** in both V1 and V2.
220 */
221 static void dbgChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
222 blob_appendf(p->pOut, "COPY %u lines from V1(%u..%u) or V2(%u..%u)\n",
223 nV, p->lnV1+1, p->lnV1+nV, p->lnV2+1, p->lnV2+nV);
224 p->lnPivot += nPivot;
225 p->lnV1 += nV;
226 p->lnV2 += nV;
227 }
228
229 /* V1 and V2 have different and overlapping changes. The next nPivot lines
230 ** of the PIVOT are converted into nV1 lines of V1 and nV2 lines of V2.
231 */
232 static void dbgConflict(
233 MergeBuilder *p,
234 unsigned int nPivot,
235 unsigned int nV1,
236 unsigned int nV2
237 ){
238 blob_appendf(p->pOut,
239 "CONFLICT %u,%u,%u BASELINE(%u..%u) versus V1(%u..%u) versus V2(%u..%u)\n",
240 nPivot, nV1, nV2,
241 p->lnPivot+1, p->lnPivot+nPivot,
242 p->lnV1+1, p->lnV1+nV1,
243 p->lnV2+1, p->lnV2+nV2);
244 p->lnV1 += nV1;
245 p->lnPivot += nPivot;
246 p->lnV2 += nV2;
247 }
248
249 /* Generic destructor for the MergeBuilder object
250 */
251 static void dbgDestroy(MergeBuilder *p){
252 memset(p, 0, sizeof(*p));
253 }
254
255 /* Generic initializer for a MergeBuilder object
256 */
257 static void mergebuilder_init(MergeBuilder *p){
258 memset(p, 0, sizeof(*p));
259 p->xStart = dbgStartEnd;
260 p->xSame = dbgSame;
261 p->xChngV1 = dbgChngV1;
262 p->xChngV2 = dbgChngV2;
263 p->xChngBoth = dbgChngBoth;
264 p->xConflict = dbgConflict;
265 p->xEnd = dbgStartEnd;
266 p->xDestroy = dbgDestroy;
267 }
268
269 /************************* MergeBuilderToken ********************************/
270 /* This version of MergeBuilder actually performs a merge on file that
271 ** are broken up into tokens instead of lines, and puts the result in pOut.
272 */
273 static void tokenSame(MergeBuilder *p, unsigned int N){
274 blob_append(p->pOut, p->pPivot->aData+p->pPivot->iCursor, N);
275 p->pPivot->iCursor += N;
276 p->pV1->iCursor += N;
277 p->pV2->iCursor += N;
278 }
279 static void tokenChngV1(MergeBuilder *p, unsigned int nPivot, unsigned nV1){
280 blob_append(p->pOut, p->pV1->aData+p->pV1->iCursor, nV1);
281 p->pPivot->iCursor += nPivot;
282 p->pV1->iCursor += nV1;
283 p->pV2->iCursor += nPivot;
284 }
285 static void tokenChngV2(MergeBuilder *p, unsigned int nPivot, unsigned nV2){
286 blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV2);
287 p->pPivot->iCursor += nPivot;
288 p->pV1->iCursor += nPivot;
289 p->pV2->iCursor += nV2;
290 }
291 static void tokenChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned nV){
292 blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV);
293 p->pPivot->iCursor += nPivot;
294 p->pV1->iCursor += nV;
295 p->pV2->iCursor += nV;
296 }
297 static void tokenConflict(
298 MergeBuilder *p,
299 unsigned int nPivot,
300 unsigned int nV1,
301 unsigned int nV2
302 ){
303 /* For a token-merge conflict, use the text from the merge-in */
304 blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV2);
305 p->pPivot->iCursor += nPivot;
306 p->pV1->iCursor += nV1;
307 p->pV2->iCursor += nV2;
308 }
309 static void mergebuilder_init_token(MergeBuilder *p){
310 mergebuilder_init(p);
311 p->xSame = tokenSame;
312 p->xChngV1 = tokenChngV1;
313 p->xChngV2 = tokenChngV2;
314 p->xChngBoth = tokenChngBoth;
315 p->xConflict = tokenConflict;
316 p->diffFlags = DIFF_BY_TOKEN;
317 }
318
319 /*
320 ** Attempt to do a low-level merge on a conflict. The conflict is
321 ** described by the first four parameters, which are the same as the
322 ** arguments to the xConflict method of the MergeBuilder object.
323 ** This routine attempts to resolve the conflict by looking at
324 ** elements of the conflict region that are finer grain than complete
325 ** lines of text.
326 **
327 ** The result is written into Blob pOut. pOut is initialized by this
328 ** routine.
329 */
330 int merge_try_to_resolve_conflict(
331 MergeBuilder *pMB, /* MergeBuilder that encounter conflict */
332 unsigned int nPivot, /* Lines of conflict in the pivot */
333 unsigned int nV1, /* Lines of conflict in V1 */
334 unsigned int nV2, /* Lines of conflict in V2 */
335 Blob *pOut /* Write resolution text here */
336 ){
337 int nConflict;
338 MergeBuilder mb;
339 Blob pv, v1, v2;
340 mergebuilder_init_token(&mb);
341 blob_extract_lines(pMB->pPivot, nPivot, &pv);
342 blob_extract_lines(pMB->pV1, nV1, &v1);
343 blob_extract_lines(pMB->pV2, nV2, &v2);
344 blob_zero(pOut);
345 blob_materialize(&pv);
346 blob_materialize(&v1);
347 blob_materialize(&v2);
348 mb.pPivot = &pv;
349 mb.pV1 = &v1;
350 mb.pV2 = &v2;
351 mb.pOut = pOut;
352 nConflict = merge_three_blobs(&mb);
353 blob_reset(&pv);
354 blob_reset(&v1);
355 blob_reset(&v2);
356 /* mb has not allocated any resources, so we do not need to invoke
357 ** the xDestroy method. */
358 blob_add_final_newline(pOut);
359 return nConflict;
360 }
361
362
363 /************************* MergeBuilderText **********************************/
364 /* This version of MergeBuilder actually performs a merge on file and puts
365 ** the result in pOut
366 */
367 static void txtStart(MergeBuilder *p){
368 /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM),
369 ** keep it in the output. This should be secure enough not to cause
370 ** unintended changes to the merged file and consistent with what
371 ** users are using in their source files.
372 */
373 if( starts_with_utf8_bom(p->pV1, 0) && starts_with_utf8_bom(p->pV2, 0) ){
374 blob_append(p->pOut, (char*)get_utf8_bom(0), -1);
375 }
376 if( contains_crlf(p->pV1) && contains_crlf(p->pV2) ){
377 p->useCrLf = 1;
378 }
379 }
380 static void txtSame(MergeBuilder *p, unsigned int N){
381 blob_copy_lines(p->pOut, p->pPivot, N); p->lnPivot += N;
382 blob_copy_lines(0, p->pV1, N); p->lnV1 += N;
383 blob_copy_lines(0, p->pV2, N); p->lnV2 += N;
384 }
385 static void txtChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
386 blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
387 blob_copy_lines(0, p->pV2, nPivot); p->lnV2 += nPivot;
388 blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1;
389 }
390 static void txtChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
391 blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
392 blob_copy_lines(0, p->pV1, nPivot); p->lnV1 += nPivot;
393 blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2;
394 }
395 static void txtChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
396 blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
397 blob_copy_lines(0, p->pV1, nV); p->lnV1 += nV;
398 blob_copy_lines(p->pOut, p->pV2, nV); p->lnV2 += nV;
399 }
400 static void txtConflict(
401 MergeBuilder *p,
402 unsigned int nPivot,
403 unsigned int nV1,
404 unsigned int nV2
405 ){
406 int nRes; /* Lines in the computed conflict resolution */
407 Blob res; /* Text of the conflict resolution */
408
409 merge_try_to_resolve_conflict(p, nPivot, nV1, nV2, &res);
410 nRes = blob_linecount(&res);
411
412 append_merge_mark(p->pOut, 0, p->lnV1+1, p->useCrLf);
413 blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1;
414
415 if( nRes>0 ){
416 append_merge_mark(p->pOut, 1, 0, p->useCrLf);
417 blob_copy_lines(p->pOut, &res, nRes);
418 }
419 blob_reset(&res);
420
421 append_merge_mark(p->pOut, 2, p->lnPivot+1, p->useCrLf);
422 blob_copy_lines(p->pOut, p->pPivot, nPivot); p->lnPivot += nPivot;
423
424 append_merge_mark(p->pOut, 3, p->lnV2+1, p->useCrLf);
425 blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2;
426
427 append_merge_mark(p->pOut, 4, -1, p->useCrLf);
428 }
429 static void mergebuilder_init_text(MergeBuilder *p){
430 mergebuilder_init(p);
431 p->xStart = txtStart;
432 p->xSame = txtSame;
433 p->xChngV1 = txtChngV1;
434 p->xChngV2 = txtChngV2;
435 p->xChngBoth = txtChngBoth;
436 p->xConflict = txtConflict;
437 }
438
439 /************************* MergeBuilderTcl **********************************/
440 /* Generate merge output formatted for reading by a TCL script.
441 **
442 ** The output consists of lines of text, each with 4 tokens. The tokens
443 ** represent the content for one line from baseline, v1, v2, and output
444 ** respectively. The first character of each token provides auxiliary
445 ** information:
446 **
447 ** . This line is omitted.
448 ** N Name of the file.
449 ** T Literal text follows that should have a \n terminator.
450 ** R Literal text follows that needs a \r\n terminator.
451 ** X Merge conflict.
452 ** Z Literal text without a line terminator.
453 ** S Skipped lines. Followed by number of lines to skip.
454 ** 1 Text is a copy of token 1
455 ** 2 Use data from data-token 2
456 ** 3 Use data from data-token 3
457 */
458
459 /* Write text that goes into the interior of a double-quoted string in TCL */
460 static void tclWriteQuotedText(Blob *pOut, const char *zIn, int nIn){
461 int j;
462 for(j=0; j<nIn; j++){
463 char c = zIn[j];
464 if( c=='\\' ){
465 blob_append(pOut, "\\\\", 2);
466 }else if( c=='"' ){
467 blob_append(pOut, "\\\"", 2);
468 }else if( c<' ' || c>0x7e ){
469 char z[5];
470 z[0] = '\\';
471 z[1] = "01234567"[(c>>6)&0x3];
472 z[2] = "01234567"[(c>>3)&0x7];
473 z[3] = "01234567"[c&0x7];
474 z[4] = 0;
475 blob_append(pOut, z, 4);
476 }else{
477 blob_append_char(pOut, c);
478 }
479 }
480 }
481
482 /* Copy one line of text from pIn and append to pOut, encoded as TCL */
483 static void tclLineOfText(Blob *pOut, Blob *pIn, char cType){
484 int i, k;
485 for(i=pIn->iCursor; i<pIn->nUsed && pIn->aData[i]!='\n'; i++){}
486 if( i==pIn->nUsed ){
487 k = i;
488 }else if( i>pIn->iCursor && pIn->aData[i-1]=='\r' ){
489 k = i-1;
490 i++;
491 }else{
492 k = i;
493 i++;
494 }
495 blob_append_char(pOut, '"');
496 blob_append_char(pOut, cType);
497 tclWriteQuotedText(pOut, pIn->aData+pIn->iCursor, k-pIn->iCursor);
498 pIn->iCursor = i;
499 blob_append_char(pOut, '"');
500 }
501 static void tclStart(MergeBuilder *p){
502 Blob *pOut = p->pOut;
503 blob_append(pOut, "\"N", 2);
504 tclWriteQuotedText(pOut, p->zPivot, (int)strlen(p->zPivot));
505 blob_append(pOut, "\" \"N", 4);
506 tclWriteQuotedText(pOut, p->zV1, (int)strlen(p->zV1));
507 blob_append(pOut, "\" \"N", 4);
508 tclWriteQuotedText(pOut, p->zV2, (int)strlen(p->zV2));
509 blob_append(pOut, "\" \"N", 4);
510 if( p->zOut ){
511 tclWriteQuotedText(pOut, p->zOut, (int)strlen(p->zOut));
512 }else{
513 blob_append(pOut, "(Merge Result)", -1);
514 }
515 blob_append(pOut, "\"\n", 2);
516 }
517 static void tclSame(MergeBuilder *p, unsigned int N){
518 int i = 0;
519 int nSkip;
520
521 if( p->lnPivot>=2 || p->lnV1>2 || p->lnV2>2 ){
522 while( i<N && i<p->nContext ){
523 tclLineOfText(p->pOut, p->pPivot, 'T');
524 blob_append(p->pOut, " 1 1 1\n", 7);
525 i++;
526 }
527 nSkip = N - p->nContext*2;
528 }else{
529 nSkip = N - p->nContext;
530 }
531 if( nSkip>0 ){
532 blob_appendf(p->pOut, "\"S%d %d %d %d\" . . .\n",
533 nSkip, nSkip, nSkip, nSkip);
534 blob_copy_lines(0, p->pPivot, nSkip);
535 i += nSkip;
536 }
537
538 p->lnPivot += N;
539 p->lnV1 += N;
540 p->lnV2 += N;
541
542 if( p->lnPivot<p->mxPivot || p->lnV1<p->mxV1 || p->lnV2<p->mxV2 ){
543 while( i<N ){
544 tclLineOfText(p->pOut, p->pPivot, 'T');
545 blob_append(p->pOut, " 1 1 1\n", 7);
546 i++;
547 }
548 }
549
550 blob_copy_lines(0, p->pV1, N);
551 blob_copy_lines(0, p->pV2, N);
552 }
553 static void tclChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
554 int i;
555 for(i=0; i<nPivot && i<nV1; i++){
556 tclLineOfText(p->pOut, p->pPivot, 'T');
557 blob_append_char(p->pOut, ' ');
558 tclLineOfText(p->pOut, p->pV1, 'T');
559 blob_append(p->pOut, " 1 2\n", 5);
560 }
561 while( i<nPivot ){
562 tclLineOfText(p->pOut, p->pPivot, 'T');
563 blob_append(p->pOut, " . 1 .\n", 7);
564 i++;
565 }
566 while( i<nV1 ){
567 blob_append(p->pOut, ". ", 2);
568 tclLineOfText(p->pOut, p->pV1, 'T');
569 blob_append(p->pOut, " . 2\n", 5);
570 i++;
571 }
572 p->lnPivot += nPivot;
573 p->lnV1 += nV1;
574 p->lnV2 += nPivot;
575 blob_copy_lines(0, p->pV2, nPivot);
576 }
577 static void tclChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
578 int i;
579 for(i=0; i<nPivot && i<nV2; i++){
580 tclLineOfText(p->pOut, p->pPivot, 'T');
581 blob_append(p->pOut, " 1 ", 3);
582 tclLineOfText(p->pOut, p->pV2, 'T');
583 blob_append(p->pOut, " 3\n", 3);
584 }
585 while( i<nPivot ){
586 tclLineOfText(p->pOut, p->pPivot, 'T');
587 blob_append(p->pOut, " 1 . .\n", 7);
588 i++;
589 }
590 while( i<nV2 ){
591 blob_append(p->pOut, ". . ", 4);
592 tclLineOfText(p->pOut, p->pV2, 'T');
593 blob_append(p->pOut, " 3\n", 3);
594 i++;
595 }
596 p->lnPivot += nPivot;
597 p->lnV1 += nPivot;
598 p->lnV2 += nV2;
599 blob_copy_lines(0, p->pV1, nPivot);
600 }
601 static void tclChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
602 int i;
603 for(i=0; i<nPivot && i<nV; i++){
604 tclLineOfText(p->pOut, p->pPivot, 'T');
605 blob_append_char(p->pOut, ' ');
606 tclLineOfText(p->pOut, p->pV1, 'T');
607 blob_append(p->pOut, " 2 2\n", 5);
608 }
609 while( i<nPivot ){
610 tclLineOfText(p->pOut, p->pPivot, 'T');
611 blob_append(p->pOut, " . . .\n", 7);
612 i++;
613 }
614 while( i<nV ){
615 blob_append(p->pOut, ". ", 2);
616 tclLineOfText(p->pOut, p->pV1, 'T');
617 blob_append(p->pOut, " 2 2\n", 5);
618 i++;
619 }
620 p->lnPivot += nPivot;
621 p->lnV1 += nV;
622 p->lnV2 += nV;
623 blob_copy_lines(0, p->pV2, nV);
624 }
625 static void tclConflict(
626 MergeBuilder *p,
627 unsigned int nPivot,
628 unsigned int nV1,
629 unsigned int nV2
630 ){
631 int mx = nPivot;
632 int i;
633 int nRes;
634 Blob res;
635
636 merge_try_to_resolve_conflict(p, nPivot, nV1, nV2, &res);
637 nRes = blob_linecount(&res);
638 if( nV1>mx ) mx = nV1;
639 if( nV2>mx ) mx = nV2;
640 if( nRes>mx ) mx = nRes;
641 if( nRes>0 ){
642 blob_appendf(p->pOut, "\"S0 0 0 %d\" . . .\n", nV2+2);
643 }
644 for(i=0; i<mx; i++){
645 if( i<nPivot ){
646 tclLineOfText(p->pOut, p->pPivot, 'X');
647 }else{
648 blob_append_char(p->pOut, '.');
649 }
650 blob_append_char(p->pOut, ' ');
651 if( i<nV1 ){
652 tclLineOfText(p->pOut, p->pV1, 'X');
653 }else{
654 blob_append_char(p->pOut, '.');
655 }
656 blob_append_char(p->pOut, ' ');
657 if( i<nV2 ){
658 tclLineOfText(p->pOut, p->pV2, 'X');
659 }else{
660 blob_append_char(p->pOut, '.');
661 }
662 if( i<nRes ){
663 blob_append_char(p->pOut, ' ');
664 tclLineOfText(p->pOut, &res, 'X');
665 blob_append_char(p->pOut, '\n');
666 }else{
667 blob_append(p->pOut, " .\n", 3);
668 }
669 if( i==mx-1 ){
670 blob_appendf(p->pOut, "\"S0 0 0 %d\" . . .\n", nPivot+nV1+3);
671 }
672 }
673 blob_reset(&res);
674 p->lnPivot += nPivot;
675 p->lnV1 += nV1;
676 p->lnV2 += nV2;
677 }
678 void mergebuilder_init_tcl(MergeBuilder *p){
679 mergebuilder_init(p);
680 p->xStart = tclStart;
681 p->xSame = tclSame;
682 p->xChngV1 = tclChngV1;
683 p->xChngV2 = tclChngV2;
684 p->xChngBoth = tclChngBoth;
685 p->xConflict = tclConflict;
686 }
687 /*****************************************************************************/
688
689 /*
690 ** The aC[] array contains triples of integers. Within each triple, the
691 ** elements are:
692 **
693 ** (0) The number of lines to copy
694 ** (1) The number of lines to delete
695 ** (2) The number of liens to insert
696 **
697 ** Suppose we want to advance over sz lines of the original file. This routine
698 ** returns true if that advance would land us on a copy operation. It
699 ** returns false if the advance would end on a delete.
700 */
701 static int ends_with_copy(int *aC, int sz){
702 while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){
703 if( aC[0]>=sz ) return 1;
704 sz -= aC[0];
705 if( aC[1]>sz ) return 0;
706 sz -= aC[1];
707 aC += 3;
708 }
709 return 1;
710 }
711
712 /*
713 ** aC[] is an "edit triple" for changes from A to B. Advance through
714 ** this triple to determine the number of lines to bypass on B in order
715 ** to match an advance of sz lines on A.
716 */
717 static int skip_conflict(
718 int *aC, /* Array of integer triples describing the edit */
719 int i, /* Index in aC[] of current location */
720 int sz, /* Lines of A that have been skipped */
721 unsigned int *pLn /* OUT: Lines of B to skip to keep aligment with A */
722 ){
723 *pLn = 0;
724 while( sz>0 ){
725 if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break;
726 if( aC[i]>=sz ){
727 aC[i] -= sz;
728 *pLn += sz;
729 break;
730 }
731 *pLn += aC[i];
732 *pLn += aC[i+2];
733 sz -= aC[i] + aC[i+1];
734 i += 3;
735 }
736 return i;
737 }
738
739 /*
740 ** Do a three-way merge. Initialize pOut to contain the result.
741 **
742 ** The merge is an edit against pV2. Both pV1 and pV2 have a
@@ -199,156 +746,113 @@
746 ** The return is 0 upon complete success. If any input file is binary,
747 ** -1 is returned and pOut is unmodified. If there are merge
748 ** conflicts, the merge proceeds as best as it can and the number
749 ** of conflicts is returns
750 */
751 int merge_three_blobs(MergeBuilder *p){
752 int *aC1; /* Changes from pPivot to pV1 */
753 int *aC2; /* Changes from pPivot to pV2 */
754 int i1, i2; /* Index into aC1[] and aC2[] */
755 int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */
756 int limit1, limit2; /* Sizes of aC1[] and aC2[] */
757 int nConflict = 0; /* Number of merge conflicts seen so far */
 
 
758 DiffConfig DCfg;
759
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
760 /* Compute the edits that occur from pPivot => pV1 (into aC1)
761 ** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is
762 ** an array of integer triples. Within each triple, the first integer
763 ** is the number of lines of text to copy directly from the pivot,
764 ** the second integer is the number of lines of text to omit from the
765 ** pivot, and the third integer is the number of lines of text that are
766 ** inserted. The edit array ends with a triple of 0,0,0.
767 */
768 diff_config_init(&DCfg, 0);
769 DCfg.diffFlags = p->diffFlags;
770 aC1 = text_diff(p->pPivot, p->pV1, 0, &DCfg);
771 aC2 = text_diff(p->pPivot, p->pV2, 0, &DCfg);
772 if( aC1==0 || aC2==0 ){
773 free(aC1);
774 free(aC2);
775 return -1;
776 }
777
778 blob_rewind(p->pV1); /* Rewind inputs: Needed to reconstruct output */
779 blob_rewind(p->pV2);
780 blob_rewind(p->pPivot);
781
782 /* Determine the length of the aC1[] and aC2[] change vectors */
783 p->mxPivot = 0;
784 p->mxV1 = 0;
785 for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){
786 p->mxPivot += aC1[i1] + aC1[i1+1];
787 p->mxV1 += aC1[i1] + aC1[i1+2];
788 }
789 limit1 = i1;
790 p->mxV2 = 0;
791 for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){
792 p->mxV2 += aC2[i2] + aC2[i2+2];
793 }
794 limit2 = i2;
795
796 /* Output header text and do any other required initialization */
797 p->xStart(p);
 
 
 
 
 
 
798
799 /* Loop over the two edit vectors and use them to compute merged text
800 ** which is written into pOut. i1 and i2 are multiples of 3 which are
801 ** indices into aC1[] and aC2[] to the edit triple currently being
802 ** processed
803 */
804 i1 = i2 = 0;
 
805 while( i1<limit1 && i2<limit2 ){
 
 
 
 
806 if( aC1[i1]>0 && aC2[i2]>0 ){
807 /* Output text that is unchanged in both V1 and V2 */
808 nCpy = min(aC1[i1], aC2[i2]);
809 p->xSame(p, nCpy);
 
 
 
810 aC1[i1] -= nCpy;
811 aC2[i2] -= nCpy;
812 }else
813 if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){
814 /* Output edits to V2 that occurs within unchanged regions of V1 */
815 nDel = aC2[i2+1];
816 nIns = aC2[i2+2];
817 p->xChngV2(p, nDel, nIns);
 
 
 
818 aC1[i1] -= nDel;
819 i2 += 3;
820 }else
821 if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){
822 /* Output edits to V1 that occur within unchanged regions of V2 */
823 nDel = aC1[i1+1];
824 nIns = aC1[i1+2];
825 p->xChngV1(p, nDel, nIns);
 
 
 
826 aC2[i2] -= nDel;
827 i1 += 3;
828 }else
829 if( sameEdit(&aC1[i1], &aC2[i2], p->pV1, p->pV2) ){
830 /* Output edits that are identical in both V1 and V2. */
831 assert( aC1[i1]==0 );
832 nDel = aC1[i1+1];
833 nIns = aC1[i1+2];
834 p->xChngBoth(p, nDel, nIns);
 
 
 
835 i1 += 3;
836 i2 += 3;
837 }else
838 {
839 /* We have found a region where different edits to V1 and V2 overlap.
840 ** This is a merge conflict. Find the size of the conflict, then
841 ** output both possible edits separated by distinctive marks.
842 */
843 unsigned int sz = 1; /* Size of the conflict in the pivot, in lines */
844 unsigned int nV1, nV2; /* Size of conflict in V1 and V2, in lines */
845 nConflict++;
846 while( !ends_with_copy(&aC1[i1], sz) || !ends_with_copy(&aC2[i2], sz) ){
847 sz++;
848 }
849 i1 = skip_conflict(aC1, i1, sz, &nV1);
850 i2 = skip_conflict(aC2, i2, sz, &nV2);
851 p->xConflict(p, sz, nV1, nV2);
852 }
853
 
 
 
 
 
 
 
 
 
854 /* If we are finished with an edit triple, advance to the next
855 ** triple.
856 */
857 if( i1<limit1 && aC1[i1]==0 && aC1[i1+1]==0 && aC1[i1+2]==0 ) i1+=3;
858 if( i2<limit2 && aC2[i2]==0 && aC2[i2+1]==0 && aC2[i2+2]==0 ) i2+=3;
@@ -356,20 +860,18 @@
860
861 /* When one of the two edit vectors reaches its end, there might still
862 ** be an insert in the other edit vector. Output this remaining
863 ** insert.
864 */
 
 
 
865 if( i1<limit1 && aC1[i1+2]>0 ){
866 p->xChngV1(p, 0, aC1[i1+2]);
 
867 }else if( i2<limit2 && aC2[i2+2]>0 ){
868 p->xChngV2(p, 0, aC2[i2+2]);
 
869 }
870
871 /* Output footer text */
872 p->xEnd(p);
873
874 free(aC1);
875 free(aC2);
876 return nConflict;
877 }
@@ -384,11 +886,12 @@
886 const char *z = blob_buffer(p);
887 int n = blob_size(p) - len + 1;
888 assert( len==(int)strlen(mergeMarker[1]) );
889 assert( len==(int)strlen(mergeMarker[2]) );
890 assert( len==(int)strlen(mergeMarker[3]) );
891 assert( len==(int)strlen(mergeMarker[4]) );
892 assert( count(mergeMarker)==5 );
893 for(i=0; i<n; ){
894 for(j=0; j<4; j++){
895 if( (memcmp(&z[i], mergeMarker[j], len)==0) ){
896 return 1;
897 }
@@ -408,18 +911,106 @@
911 blob_read_from_file(&file, zFullpath, ExtFILE);
912 rc = contains_merge_marker(&file);
913 blob_reset(&file);
914 return rc;
915 }
916
917 /*
918 ** Show merge output in a Tcl/Tk window, in response to the --tk option
919 ** to the "merge" or "3-way-merge" command.
920 **
921 ** If fossil has direct access to a Tcl interpreter (either loaded
922 ** dynamically through stubs or linked in statically), we can use it
923 ** directly. Otherwise:
924 ** (1) Write the Tcl/Tk script used for rendering into a temp file.
925 ** (2) Invoke "tclsh" on the temp file using fossil_system().
926 ** (3) Delete the temp file.
927 */
928 void merge_tk(const char *zSubCmd, int firstArg){
929 int i;
930 Blob script;
931 const char *zTempFile = 0;
932 char *zCmd;
933 const char *zTclsh;
934 const char *zCnt;
935 int bDarkMode = find_option("dark",0,0)!=0;
936 int nContext;
937 zCnt = find_option("context", "c", 1);
938 if( zCnt==0 ){
939 nContext = 6;
940 }else{
941 nContext = atoi(zCnt);
942 if( nContext<0 ) nContext = 0xfffffff;
943 }
944 blob_zero(&script);
945 blob_appendf(&script, "set ncontext %d\n", nContext);
946 blob_appendf(&script, "set fossilcmd {| \"%/\" %s -tcl",
947 g.nameOfExe, zSubCmd);
948 find_option("tcl",0,0);
949 find_option("debug",0,0);
950 zTclsh = find_option("tclsh",0,1);
951 if( zTclsh==0 ){
952 zTclsh = db_get("tclsh",0);
953 }
954 /* The undocumented --script FILENAME option causes the Tk script to
955 ** be written into the FILENAME instead of being run. This is used
956 ** for testing and debugging. */
957 zTempFile = find_option("script",0,1);
958 verify_all_options();
959
960 if( (g.argc - firstArg)!=3 ){
961 fossil_fatal("Requires 3 filename arguments");
962 }
963
964 for(i=firstArg; i<g.argc; i++){
965 const char *z = g.argv[i];
966 if( sqlite3_strglob("*}*",z) ){
967 blob_appendf(&script, " {%/}", z);
968 }else{
969 int j;
970 blob_append(&script, " ", 1);
971 for(j=0; z[j]; j++) blob_appendf(&script, "\\%03o", (unsigned char)z[j]);
972 }
973 }
974 blob_appendf(&script, "}\nset darkmode %d\n", bDarkMode);
975 blob_appendf(&script, "%s", builtin_file("merge.tcl", 0));
976 if( zTempFile ){
977 blob_write_to_file(&script, zTempFile);
978 fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile);
979 }else{
980 #if defined(FOSSIL_ENABLE_TCL)
981 Th_FossilInit(TH_INIT_DEFAULT);
982 if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script),
983 blob_size(&script), 1, 1, 0)==TCL_OK ){
984 blob_reset(&script);
985 return;
986 }
987 /*
988 * If evaluation of the Tcl script fails, the reason may be that Tk
989 * could not be found by the loaded Tcl, or that Tcl cannot be loaded
990 * dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback
991 * to using the external "tclsh", if available.
992 */
993 #endif
994 zTempFile = write_blob_to_temp_file(&script);
995 zCmd = mprintf("%$ %$", zTclsh, zTempFile);
996 fossil_system(zCmd);
997 file_delete(zTempFile);
998 fossil_free(zCmd);
999 }
1000 blob_reset(&script);
1001 }
1002
1003
1004 /*
1005 ** COMMAND: 3-way-merge*
1006 **
1007 ** Usage: %fossil 3-way-merge BASELINE V1 V2 [MERGED]
1008 **
1009 ** Inputs are files BASELINE, V1, and V2. The file MERGED is generated
1010 ** as output. If no MERGED file is specified, output is sent to
1011 ** stdout.
1012 **
1013 ** BASELINE is a common ancestor of two files V1 and V2 that have diverging
1014 ** edits. The generated output file MERGED is the combination of all
1015 ** changes in both V1 and V2.
1016 **
@@ -436,38 +1027,75 @@
1027 ** cp Xup.c Xbase.c
1028 ** # Verify that everything still works
1029 ** fossil commit
1030 **
1031 */
1032 void merge_3way_cmd(void){
1033 MergeBuilder s;
1034 int nConflict;
1035 Blob pivot, v1, v2, out;
1036 int noWarn = 0;
1037 const char *zCnt;
1038
1039 if( find_option("tk", 0, 0)!=0 ){
1040 merge_tk("3-way-merge", 2);
1041 return;
1042 }
1043 mergebuilder_init_text(&s);
1044 if( find_option("debug", 0, 0) ){
1045 mergebuilder_init(&s);
1046 }
1047 if( find_option("tcl", 0, 0) ){
1048 mergebuilder_init_tcl(&s);
1049 noWarn = 1;
1050 }
1051 zCnt = find_option("context", "c", 1);
1052 if( zCnt ){
1053 s.nContext = atoi(zCnt);
1054 if( s.nContext<0 ) s.nContext = 0xfffffff;
1055 }else{
1056 s.nContext = 6;
1057 }
1058 blob_zero(&pivot); s.pPivot = &pivot;
1059 blob_zero(&v1); s.pV1 = &v1;
1060 blob_zero(&v2); s.pV2 = &v2;
1061 blob_zero(&out); s.pOut = &out;
1062
1063 /* We should be done with options.. */
1064 verify_all_options();
1065
1066 if( g.argc!=6 && g.argc!=5 ){
1067 usage("[OPTIONS] PIVOT V1 V2 [MERGED]");
1068 }
1069 s.zPivot = file_tail(g.argv[2]);
1070 s.zV1 = file_tail(g.argv[3]);
1071 s.zV2 = file_tail(g.argv[4]);
1072 if( blob_read_from_file(s.pPivot, g.argv[2], ExtFILE)<0 ){
1073 fossil_fatal("cannot read %s", g.argv[2]);
1074 }
1075 if( blob_read_from_file(s.pV1, g.argv[3], ExtFILE)<0 ){
1076 fossil_fatal("cannot read %s", g.argv[3]);
1077 }
1078 if( blob_read_from_file(s.pV2, g.argv[4], ExtFILE)<0 ){
1079 fossil_fatal("cannot read %s", g.argv[4]);
1080 }
1081 nConflict = merge_three_blobs(&s);
1082 if( g.argc==6 ){
1083 s.zOut = file_tail(g.argv[5]);
1084 blob_write_to_file(s.pOut, g.argv[5]);
1085 }else{
1086 s.zOut = "(Merge Result)";
1087 blob_write_to_file(s.pOut, "-");
1088 }
1089 s.xDestroy(&s);
1090 blob_reset(&pivot);
1091 blob_reset(&v1);
1092 blob_reset(&v2);
1093 blob_reset(&out);
1094 if( nConflict>0 && !noWarn ){
1095 fossil_warning("WARNING: %d merge conflicts", nConflict);
1096 }
1097 }
1098
1099 /*
1100 ** aSubst is an array of string pairs. The first element of each pair is
1101 ** a string that begins with %. The second element is a replacement for that
@@ -505,32 +1133,32 @@
1133
1134 #if INTERFACE
1135 /*
1136 ** Flags to the 3-way merger
1137 */
1138 #define MERGE_DRYRUN 0x0001
1139 /*
1140 ** The MERGE_KEEP_FILES flag specifies that merge_3way() should retain
1141 ** its temporary files on error. By default they are removed after the
1142 ** merge, regardless of success or failure.
1143 */
1144 #define MERGE_KEEP_FILES 0x0002
1145 #endif
1146
1147
1148 /*
1149 ** This routine is a wrapper around merge_three_blobs() with the following
1150 ** enhancements:
1151 **
1152 ** (1) If the merge-command is defined, then use the external merging
1153 ** program specified instead of the built-in blob-merge to do the
1154 ** merging. Panic if the external merger fails.
1155 ** ** Not currently implemented **
1156 **
1157 ** (2) If gmerge-command is defined and there are merge conflicts in
1158 ** merge_three_blobs() then invoke the external graphical merger
1159 ** to resolve the conflicts.
1160 **
1161 ** (3) If a merge conflict occurs and gmerge-command is not defined,
1162 ** then write the pivot, original, and merge-in files to the
1163 ** filesystem.
1164 */
@@ -539,30 +1167,37 @@
1167 const char *zV1, /* Name of file for version merging into (mine) */
1168 Blob *pV2, /* Version merging from (yours) */
1169 Blob *pOut, /* Output written here */
1170 unsigned mergeFlags /* Flags that control operation */
1171 ){
1172 Blob v1; /* Content of zV1 */
1173 int rc; /* Return code of subroutines and this routine */
1174 const char *zGMerge; /* Name of the gmerge command */
1175 MergeBuilder s; /* The merge state */
1176
1177 mergebuilder_init_text(&s);
1178 s.pPivot = pPivot;
1179 s.pV1 = &v1;
1180 s.pV2 = pV2;
1181 blob_zero(pOut);
1182 s.pOut = pOut;
1183 blob_read_from_file(s.pV1, zV1, ExtFILE);
1184 rc = merge_three_blobs(&s);
1185 zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0);
1186 if( (mergeFlags & MERGE_DRYRUN)==0
1187 && ((zGMerge!=0 && zGMerge[0]!=0)
1188 || (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){
1189 char *zPivot; /* Name of the pivot file */
1190 char *zOrig; /* Name of the original content file */
1191 char *zOther; /* Name of the merge file */
1192
1193 zPivot = file_newname(zV1, "baseline", 1);
1194 blob_write_to_file(s.pPivot, zPivot);
1195 zOrig = file_newname(zV1, "original", 1);
1196 blob_write_to_file(s.pV1, zOrig);
1197 zOther = file_newname(zV1, "merge", 1);
1198 blob_write_to_file(s.pV2, zOther);
1199 if( rc>0 ){
1200 if( zGMerge && zGMerge[0] ){
1201 char *zOut; /* Temporary output file */
1202 char *zCmd; /* Command to invoke */
1203 const char *azSubst[8]; /* Strings to be substituted */
@@ -590,7 +1225,8 @@
1225 fossil_free(zPivot);
1226 fossil_free(zOrig);
1227 fossil_free(zOther);
1228 }
1229 blob_reset(&v1);
1230 s.xDestroy(&s);
1231 return rc;
1232 }
1233
+151 -24
--- src/name.c
+++ src/name.c
@@ -65,33 +65,51 @@
6565
** the string is a hash prefix and NULL is returned if it is. If the
6666
** bVerifyNotAHash flag is false, then the result is determined by syntax
6767
** of the input string only, without reference to the artifact table.
6868
*/
6969
char *fossil_expand_datetime(const char *zIn, int bVerifyNotAHash){
70
- static char zEDate[20];
70
+ static char zEDate[24];
7171
static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' };
7272
int n = (int)strlen(zIn);
7373
int i, j;
74
+ int addZulu = 0;
7475
75
- /* Only three forms allowed:
76
- ** (1) YYYYMMDD
77
- ** (2) YYYYMMDDHHMM
78
- ** (3) YYYYMMDDHHMMSS
76
+ /* These forms are allowed:
77
+ **
78
+ ** 123456789 1234 123456789 123456789
79
+ ** (1) YYYYMMDD => YYYY-MM-DD
80
+ ** (2) YYYYMMDDHHMM => YYYY-MM-DD HH:MM
81
+ ** (3) YYYYMMDDHHMMSS => YYYY-MM-DD HH:MM:SS
82
+ **
83
+ ** An optional "Z" zulu timezone designator is allowed at the end.
7984
*/
80
- if( n!=8 && n!=12 && n!=14 ) return 0;
85
+ if( n>0 && (zIn[n-1]=='Z' || zIn[n-1]=='z') ){
86
+ n--;
87
+ addZulu = 1;
88
+ }
89
+ if( n!=8 && n!=12 && n!=14 ){
90
+ return 0;
91
+ }
8192
8293
/* Every character must be a digit */
8394
for(i=0; fossil_isdigit(zIn[i]); i++){}
84
- if( i!=n ) return 0;
95
+ if( i!=n && (!addZulu || i!=n+1) ) return 0;
8596
8697
/* Expand the date */
87
- for(i=j=0; zIn[i]; i++){
98
+ for(i=j=0; i<n; i++){
8899
if( i>=4 && (i%2)==0 ){
89100
zEDate[j++] = aPunct[i/2];
90101
}
91102
zEDate[j++] = zIn[i];
92103
}
104
+ if( addZulu ){
105
+ if( j==10 ){
106
+ memcpy(&zEDate[10]," 00:00", 6);
107
+ j += 6;
108
+ }
109
+ zEDate[j++] = 'Z';
110
+ }
93111
zEDate[j] = 0;
94112
95113
/* Check for reasonable date values.
96114
** Offset references:
97115
** YYYY-MM-DD HH:MM:SS
@@ -111,11 +129,11 @@
111129
if( i>60 ) return 0;
112130
if( n==14 && atoi(zEDate+17)>60 ) return 0;
113131
}
114132
115133
/* The string is not also a hash prefix */
116
- if( bVerifyNotAHash ){
134
+ if( bVerifyNotAHash && !addZulu ){
117135
if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'",zIn) ) return 0;
118136
}
119137
120138
/* It looks like this may be a date. Return it with punctuation added. */
121139
return zEDate;
@@ -453,10 +471,12 @@
453471
}else if( fossil_strcmp(zTag, "next")==0 ){
454472
rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d"
455473
" ORDER BY isprim DESC, mtime DESC", ridCkout);
456474
}else if( isCheckin>1 && fossil_strcmp(zTag, "ckout")==0 ){
457475
rid = RID_CKOUT;
476
+ assert(ridCkout>0);
477
+ g.localOpen = ridCkout;
458478
}
459479
if( rid ) return rid;
460480
}
461481
462482
/* Date and times */
@@ -699,10 +719,13 @@
699719
}else if( rid==0 ){
700720
fossil_error(iErrPriority, "cannot resolve name: %s", zName);
701721
return 1;
702722
}else{
703723
blob_reset(pName);
724
+ if( RID_CKOUT==rid ) {
725
+ rid = g.localOpen;
726
+ }
704727
db_blob(pName, "SELECT uuid FROM blob WHERE rid=%d", rid);
705728
return 0;
706729
}
707730
}
708731
@@ -1676,34 +1699,44 @@
16761699
** n=N Show N artifacts
16771700
** s=S Start with artifact number S
16781701
** priv Show only unpublished or private artifacts
16791702
** phan Show only phantom artifacts
16801703
** hclr Color code hash types (SHA1 vs SHA3)
1704
+** recent Show the most recent N artifacts
16811705
*/
16821706
void bloblist_page(void){
16831707
Stmt q;
16841708
int s = atoi(PD("s","0"));
16851709
int n = atoi(PD("n","5000"));
16861710
int mx = db_int(0, "SELECT max(rid) FROM blob");
16871711
int privOnly = PB("priv");
16881712
int phantomOnly = PB("phan");
16891713
int hashClr = PB("hclr");
1714
+ int bRecent = PB("recent");
1715
+ int bUnclst = PB("unclustered");
16901716
char *zRange;
16911717
char *zSha1Bg;
16921718
char *zSha3Bg;
16931719
16941720
login_check_credentials();
16951721
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
16961722
cgi_check_for_malice();
16971723
style_header("List Of Artifacts");
16981724
style_submenu_element("250 Largest", "bigbloblist");
1725
+ if( bRecent==0 || n!=250 ){
1726
+ style_submenu_element("Recent","bloblist?n=250&recent");
1727
+ }
1728
+ if( bUnclst==0 ){
1729
+ style_submenu_element("Unclustered","bloblist?unclustered");
1730
+ }
16991731
if( g.perm.Admin ){
17001732
style_submenu_element("Artifact Log", "rcvfromlist");
17011733
}
17021734
if( !phantomOnly ){
17031735
style_submenu_element("Phantoms", "bloblist?phan");
17041736
}
1737
+ style_submenu_element("Clusters","clusterlist");
17051738
if( g.perm.Private || g.perm.Admin ){
17061739
if( !privOnly ){
17071740
style_submenu_element("Private", "bloblist?priv");
17081741
}
17091742
}else{
@@ -1710,58 +1743,70 @@
17101743
privOnly = 0;
17111744
}
17121745
if( g.perm.Write ){
17131746
style_submenu_element("Artifact Stats", "artifact_stats");
17141747
}
1715
- if( !privOnly && !phantomOnly && mx>n && P("s")==0 ){
1748
+ if( !privOnly && !phantomOnly && mx>n && P("s")==0 && !bRecent && !bUnclst ){
17161749
int i;
17171750
@ <p>Select a range of artifacts to view:</p>
17181751
@ <ul>
17191752
for(i=1; i<=mx; i+=n){
17201753
@ <li> %z(href("%R/bloblist?s=%d&n=%d",i,n))
17211754
@ %d(i)..%d(i+n-1<mx?i+n-1:mx)</a>
17221755
}
1756
+ @ <li> %z(href("%R/bloblist?n=250&recent"))250 most recent</a>
1757
+ @ <li> %z(href("%R/bloblist?unclustered"))All unclustered</a>
17231758
@ </ul>
17241759
style_finish_page();
17251760
return;
17261761
}
17271762
if( phantomOnly || privOnly || mx>n ){
17281763
style_submenu_element("Index", "bloblist");
17291764
}
17301765
if( privOnly ){
1766
+ @ <h2>Private Artifacts</h2>
17311767
zRange = mprintf("IN private");
17321768
}else if( phantomOnly ){
1769
+ @ <h2>Phantom Artifacts</h2>
17331770
zRange = mprintf("IN phantom");
1771
+ }else if( bUnclst ){
1772
+ @ <h2>Unclustered Artifacts</h2>
1773
+ zRange = mprintf("IN unclustered");
1774
+ }else if( bRecent ){
1775
+ @ <h2>%d(n) Most Recent Artifacts</h2>
1776
+ zRange = mprintf(">=(SELECT rid FROM blob"
1777
+ " ORDER BY rid DESC LIMIT 1 OFFSET %d)",n);
17341778
}else{
17351779
zRange = mprintf("BETWEEN %d AND %d", s, s+n-1);
17361780
}
17371781
describe_artifacts(zRange);
17381782
fossil_free(zRange);
17391783
db_prepare(&q,
1740
- "SELECT rid, uuid, summary, isPrivate, type='phantom', rcvid, ref"
1741
- " FROM description ORDER BY rid"
1784
+ /* 0 1 2 3 4 5 6 */
1785
+ "SELECT rid, uuid, summary, isPrivate, type='phantom', ref, rcvid, "
1786
+ " datetime(rcvfrom.mtime)"
1787
+ " FROM description LEFT JOIN rcvfrom USING(rcvid)"
1788
+ " ORDER BY rid %s",
1789
+ ((bRecent||bUnclst)?"DESC":"ASC")/*safe-for-%s*/
17421790
);
17431791
if( skin_detail_boolean("white-foreground") ){
17441792
zSha1Bg = "#714417";
17451793
zSha3Bg = "#177117";
17461794
}else{
17471795
zSha1Bg = "#ebffb0";
17481796
zSha3Bg = "#b0ffb0";
17491797
}
17501798
@ <table cellpadding="2" cellspacing="0" border="1">
1751
- if( g.perm.Admin ){
1752
- @ <tr><th>RID<th>Hash<th>Rcvid<th>Description<th>Ref<th>Remarks
1753
- }else{
1754
- @ <tr><th>RID<th>Hash<th>Description<th>Ref<th>Remarks
1755
- }
1799
+ @ <tr><th>RID<th>Hash<th>Received<th>Description<th>Ref<th>Remarks
17561800
while( db_step(&q)==SQLITE_ROW ){
17571801
int rid = db_column_int(&q,0);
17581802
const char *zUuid = db_column_text(&q, 1);
17591803
const char *zDesc = db_column_text(&q, 2);
17601804
int isPriv = db_column_int(&q,3);
17611805
int isPhantom = db_column_int(&q,4);
1762
- const char *zRef = db_column_text(&q,6);
1806
+ const char *zRef = db_column_text(&q,5);
1807
+ const char *zDate = db_column_text(&q,7);
17631808
if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){
17641809
/* Don't show private artifacts to users without Private (x) permission */
17651810
continue;
17661811
}
17671812
if( hashClr ){
@@ -1770,16 +1815,14 @@
17701815
}else{
17711816
@ <tr><td align="right">%d(rid)</td>
17721817
}
17731818
@ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
17741819
if( g.perm.Admin ){
1775
- int rcvid = db_column_int(&q,5);
1776
- if( rcvid<=0 ){
1777
- @ <td>&nbsp;
1778
- }else{
1779
- @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%d(rcvid)</a>
1780
- }
1820
+ int rcvid = db_column_int(&q, 6);
1821
+ @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%h(zDate)</a>
1822
+ }else{
1823
+ @ <td>%h(zDate)
17811824
}
17821825
@ <td align="left">%h(zDesc)</td>
17831826
if( zRef && zRef[0] ){
17841827
@ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a>
17851828
}else{
@@ -2163,5 +2206,89 @@
21632206
" ORDER BY 1");
21642207
@ <h1>Hash Prefix Collisions on All Artifacts</h1>
21652208
collision_report("SELECT uuid FROM blob ORDER BY 1");
21662209
style_finish_page();
21672210
}
2211
+
2212
+/*
2213
+** WEBPAGE: clusterlist
2214
+**
2215
+** Show information about all cluster artifacts in the database.
2216
+*/
2217
+void clusterlist_page(void){
2218
+ Stmt q;
2219
+ int cnt = 1;
2220
+ sqlite3_int64 szTotal = 0;
2221
+ sqlite3_int64 szCTotal = 0;
2222
+ login_check_credentials();
2223
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2224
+ style_header("All Cluster Artifacts");
2225
+ style_submenu_element("All Artifactst", "bloblist");
2226
+ if( g.perm.Admin ){
2227
+ style_submenu_element("Artifact Log", "rcvfromlist");
2228
+ }
2229
+ style_submenu_element("Phantoms", "bloblist?phan");
2230
+ if( g.perm.Write ){
2231
+ style_submenu_element("Artifact Stats", "artifact_stats");
2232
+ }
2233
+
2234
+ db_prepare(&q,
2235
+ "SELECT blob.uuid, "
2236
+ " blob.size, "
2237
+ " octet_length(blob.content), "
2238
+ " datetime(rcvfrom.mtime),"
2239
+ " user.login,"
2240
+ " rcvfrom.ipaddr"
2241
+ " FROM tagxref JOIN blob ON tagxref.rid=blob.rid"
2242
+ " LEFT JOIN rcvfrom ON blob.rcvid=rcvfrom.rcvid"
2243
+ " LEFT JOIN user ON user.uid=rcvfrom.uid"
2244
+ " WHERE tagxref.tagid=%d"
2245
+ " ORDER BY rcvfrom.mtime, blob.uuid",
2246
+ TAG_CLUSTER
2247
+ );
2248
+ @ <table cellpadding="2" cellspacing="0" border="1">
2249
+ @ <tr><th>&nbsp;
2250
+ @ <th>Hash
2251
+ @ <th>Date&nbsp;Received
2252
+ @ <th>Size
2253
+ @ <th>Compressed&nbsp;Size
2254
+ if( g.perm.Admin ){
2255
+ @ <th>User<th>IP-Address
2256
+ }
2257
+ while( db_step(&q)==SQLITE_ROW ){
2258
+ const char *zUuid = db_column_text(&q, 0);
2259
+ sqlite3_int64 sz = db_column_int64(&q, 1);
2260
+ sqlite3_int64 szC = db_column_int64(&q, 2);
2261
+ const char *zDate = db_column_text(&q, 3);
2262
+ const char *zUser = db_column_text(&q, 4);
2263
+ const char *zIp = db_column_text(&q, 5);
2264
+ szTotal += sz;
2265
+ szCTotal += szC;
2266
+ @ <tr><td align="right">%d(cnt++)
2267
+ @ <td><a href="%R/info/%S(zUuid)">%S(zUuid)</a>
2268
+ if( zDate ){
2269
+ @ <td>%h(zDate)
2270
+ }else{
2271
+ @ <td>&nbsp;
2272
+ }
2273
+ @ <td align="right">%,lld(sz)
2274
+ @ <td align="right">%,lld(szC)
2275
+ if( g.perm.Admin ){
2276
+ if( zUser ){
2277
+ @ <td>%h(zUser)
2278
+ }else{
2279
+ @ <td>&nbsp;
2280
+ }
2281
+ if( zIp ){
2282
+ @ <td>%h(zIp)
2283
+ }else{
2284
+ @ <td>&nbsp;
2285
+ }
2286
+ }
2287
+ @ </tr>
2288
+ }
2289
+ @ </table>
2290
+ db_finalize(&q);
2291
+ @ <p>Total size of all clusters: %,lld(szTotal) bytes,
2292
+ @ %,lld(szCTotal) bytes compressed</p>
2293
+ style_finish_page();
2294
+}
21682295
--- src/name.c
+++ src/name.c
@@ -65,33 +65,51 @@
65 ** the string is a hash prefix and NULL is returned if it is. If the
66 ** bVerifyNotAHash flag is false, then the result is determined by syntax
67 ** of the input string only, without reference to the artifact table.
68 */
69 char *fossil_expand_datetime(const char *zIn, int bVerifyNotAHash){
70 static char zEDate[20];
71 static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' };
72 int n = (int)strlen(zIn);
73 int i, j;
 
74
75 /* Only three forms allowed:
76 ** (1) YYYYMMDD
77 ** (2) YYYYMMDDHHMM
78 ** (3) YYYYMMDDHHMMSS
 
 
 
 
79 */
80 if( n!=8 && n!=12 && n!=14 ) return 0;
 
 
 
 
 
 
81
82 /* Every character must be a digit */
83 for(i=0; fossil_isdigit(zIn[i]); i++){}
84 if( i!=n ) return 0;
85
86 /* Expand the date */
87 for(i=j=0; zIn[i]; i++){
88 if( i>=4 && (i%2)==0 ){
89 zEDate[j++] = aPunct[i/2];
90 }
91 zEDate[j++] = zIn[i];
92 }
 
 
 
 
 
 
 
93 zEDate[j] = 0;
94
95 /* Check for reasonable date values.
96 ** Offset references:
97 ** YYYY-MM-DD HH:MM:SS
@@ -111,11 +129,11 @@
111 if( i>60 ) return 0;
112 if( n==14 && atoi(zEDate+17)>60 ) return 0;
113 }
114
115 /* The string is not also a hash prefix */
116 if( bVerifyNotAHash ){
117 if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'",zIn) ) return 0;
118 }
119
120 /* It looks like this may be a date. Return it with punctuation added. */
121 return zEDate;
@@ -453,10 +471,12 @@
453 }else if( fossil_strcmp(zTag, "next")==0 ){
454 rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d"
455 " ORDER BY isprim DESC, mtime DESC", ridCkout);
456 }else if( isCheckin>1 && fossil_strcmp(zTag, "ckout")==0 ){
457 rid = RID_CKOUT;
 
 
458 }
459 if( rid ) return rid;
460 }
461
462 /* Date and times */
@@ -699,10 +719,13 @@
699 }else if( rid==0 ){
700 fossil_error(iErrPriority, "cannot resolve name: %s", zName);
701 return 1;
702 }else{
703 blob_reset(pName);
 
 
 
704 db_blob(pName, "SELECT uuid FROM blob WHERE rid=%d", rid);
705 return 0;
706 }
707 }
708
@@ -1676,34 +1699,44 @@
1676 ** n=N Show N artifacts
1677 ** s=S Start with artifact number S
1678 ** priv Show only unpublished or private artifacts
1679 ** phan Show only phantom artifacts
1680 ** hclr Color code hash types (SHA1 vs SHA3)
 
1681 */
1682 void bloblist_page(void){
1683 Stmt q;
1684 int s = atoi(PD("s","0"));
1685 int n = atoi(PD("n","5000"));
1686 int mx = db_int(0, "SELECT max(rid) FROM blob");
1687 int privOnly = PB("priv");
1688 int phantomOnly = PB("phan");
1689 int hashClr = PB("hclr");
 
 
1690 char *zRange;
1691 char *zSha1Bg;
1692 char *zSha3Bg;
1693
1694 login_check_credentials();
1695 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1696 cgi_check_for_malice();
1697 style_header("List Of Artifacts");
1698 style_submenu_element("250 Largest", "bigbloblist");
 
 
 
 
 
 
1699 if( g.perm.Admin ){
1700 style_submenu_element("Artifact Log", "rcvfromlist");
1701 }
1702 if( !phantomOnly ){
1703 style_submenu_element("Phantoms", "bloblist?phan");
1704 }
 
1705 if( g.perm.Private || g.perm.Admin ){
1706 if( !privOnly ){
1707 style_submenu_element("Private", "bloblist?priv");
1708 }
1709 }else{
@@ -1710,58 +1743,70 @@
1710 privOnly = 0;
1711 }
1712 if( g.perm.Write ){
1713 style_submenu_element("Artifact Stats", "artifact_stats");
1714 }
1715 if( !privOnly && !phantomOnly && mx>n && P("s")==0 ){
1716 int i;
1717 @ <p>Select a range of artifacts to view:</p>
1718 @ <ul>
1719 for(i=1; i<=mx; i+=n){
1720 @ <li> %z(href("%R/bloblist?s=%d&n=%d",i,n))
1721 @ %d(i)..%d(i+n-1<mx?i+n-1:mx)</a>
1722 }
 
 
1723 @ </ul>
1724 style_finish_page();
1725 return;
1726 }
1727 if( phantomOnly || privOnly || mx>n ){
1728 style_submenu_element("Index", "bloblist");
1729 }
1730 if( privOnly ){
 
1731 zRange = mprintf("IN private");
1732 }else if( phantomOnly ){
 
1733 zRange = mprintf("IN phantom");
 
 
 
 
 
 
 
1734 }else{
1735 zRange = mprintf("BETWEEN %d AND %d", s, s+n-1);
1736 }
1737 describe_artifacts(zRange);
1738 fossil_free(zRange);
1739 db_prepare(&q,
1740 "SELECT rid, uuid, summary, isPrivate, type='phantom', rcvid, ref"
1741 " FROM description ORDER BY rid"
 
 
 
 
1742 );
1743 if( skin_detail_boolean("white-foreground") ){
1744 zSha1Bg = "#714417";
1745 zSha3Bg = "#177117";
1746 }else{
1747 zSha1Bg = "#ebffb0";
1748 zSha3Bg = "#b0ffb0";
1749 }
1750 @ <table cellpadding="2" cellspacing="0" border="1">
1751 if( g.perm.Admin ){
1752 @ <tr><th>RID<th>Hash<th>Rcvid<th>Description<th>Ref<th>Remarks
1753 }else{
1754 @ <tr><th>RID<th>Hash<th>Description<th>Ref<th>Remarks
1755 }
1756 while( db_step(&q)==SQLITE_ROW ){
1757 int rid = db_column_int(&q,0);
1758 const char *zUuid = db_column_text(&q, 1);
1759 const char *zDesc = db_column_text(&q, 2);
1760 int isPriv = db_column_int(&q,3);
1761 int isPhantom = db_column_int(&q,4);
1762 const char *zRef = db_column_text(&q,6);
 
1763 if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){
1764 /* Don't show private artifacts to users without Private (x) permission */
1765 continue;
1766 }
1767 if( hashClr ){
@@ -1770,16 +1815,14 @@
1770 }else{
1771 @ <tr><td align="right">%d(rid)</td>
1772 }
1773 @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
1774 if( g.perm.Admin ){
1775 int rcvid = db_column_int(&q,5);
1776 if( rcvid<=0 ){
1777 @ <td>&nbsp;
1778 }else{
1779 @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%d(rcvid)</a>
1780 }
1781 }
1782 @ <td align="left">%h(zDesc)</td>
1783 if( zRef && zRef[0] ){
1784 @ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a>
1785 }else{
@@ -2163,5 +2206,89 @@
2163 " ORDER BY 1");
2164 @ <h1>Hash Prefix Collisions on All Artifacts</h1>
2165 collision_report("SELECT uuid FROM blob ORDER BY 1");
2166 style_finish_page();
2167 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2168
--- src/name.c
+++ src/name.c
@@ -65,33 +65,51 @@
65 ** the string is a hash prefix and NULL is returned if it is. If the
66 ** bVerifyNotAHash flag is false, then the result is determined by syntax
67 ** of the input string only, without reference to the artifact table.
68 */
69 char *fossil_expand_datetime(const char *zIn, int bVerifyNotAHash){
70 static char zEDate[24];
71 static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' };
72 int n = (int)strlen(zIn);
73 int i, j;
74 int addZulu = 0;
75
76 /* These forms are allowed:
77 **
78 ** 123456789 1234 123456789 123456789
79 ** (1) YYYYMMDD => YYYY-MM-DD
80 ** (2) YYYYMMDDHHMM => YYYY-MM-DD HH:MM
81 ** (3) YYYYMMDDHHMMSS => YYYY-MM-DD HH:MM:SS
82 **
83 ** An optional "Z" zulu timezone designator is allowed at the end.
84 */
85 if( n>0 && (zIn[n-1]=='Z' || zIn[n-1]=='z') ){
86 n--;
87 addZulu = 1;
88 }
89 if( n!=8 && n!=12 && n!=14 ){
90 return 0;
91 }
92
93 /* Every character must be a digit */
94 for(i=0; fossil_isdigit(zIn[i]); i++){}
95 if( i!=n && (!addZulu || i!=n+1) ) return 0;
96
97 /* Expand the date */
98 for(i=j=0; i<n; i++){
99 if( i>=4 && (i%2)==0 ){
100 zEDate[j++] = aPunct[i/2];
101 }
102 zEDate[j++] = zIn[i];
103 }
104 if( addZulu ){
105 if( j==10 ){
106 memcpy(&zEDate[10]," 00:00", 6);
107 j += 6;
108 }
109 zEDate[j++] = 'Z';
110 }
111 zEDate[j] = 0;
112
113 /* Check for reasonable date values.
114 ** Offset references:
115 ** YYYY-MM-DD HH:MM:SS
@@ -111,11 +129,11 @@
129 if( i>60 ) return 0;
130 if( n==14 && atoi(zEDate+17)>60 ) return 0;
131 }
132
133 /* The string is not also a hash prefix */
134 if( bVerifyNotAHash && !addZulu ){
135 if( db_exists("SELECT 1 FROM blob WHERE uuid GLOB '%q*'",zIn) ) return 0;
136 }
137
138 /* It looks like this may be a date. Return it with punctuation added. */
139 return zEDate;
@@ -453,10 +471,12 @@
471 }else if( fossil_strcmp(zTag, "next")==0 ){
472 rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d"
473 " ORDER BY isprim DESC, mtime DESC", ridCkout);
474 }else if( isCheckin>1 && fossil_strcmp(zTag, "ckout")==0 ){
475 rid = RID_CKOUT;
476 assert(ridCkout>0);
477 g.localOpen = ridCkout;
478 }
479 if( rid ) return rid;
480 }
481
482 /* Date and times */
@@ -699,10 +719,13 @@
719 }else if( rid==0 ){
720 fossil_error(iErrPriority, "cannot resolve name: %s", zName);
721 return 1;
722 }else{
723 blob_reset(pName);
724 if( RID_CKOUT==rid ) {
725 rid = g.localOpen;
726 }
727 db_blob(pName, "SELECT uuid FROM blob WHERE rid=%d", rid);
728 return 0;
729 }
730 }
731
@@ -1676,34 +1699,44 @@
1699 ** n=N Show N artifacts
1700 ** s=S Start with artifact number S
1701 ** priv Show only unpublished or private artifacts
1702 ** phan Show only phantom artifacts
1703 ** hclr Color code hash types (SHA1 vs SHA3)
1704 ** recent Show the most recent N artifacts
1705 */
1706 void bloblist_page(void){
1707 Stmt q;
1708 int s = atoi(PD("s","0"));
1709 int n = atoi(PD("n","5000"));
1710 int mx = db_int(0, "SELECT max(rid) FROM blob");
1711 int privOnly = PB("priv");
1712 int phantomOnly = PB("phan");
1713 int hashClr = PB("hclr");
1714 int bRecent = PB("recent");
1715 int bUnclst = PB("unclustered");
1716 char *zRange;
1717 char *zSha1Bg;
1718 char *zSha3Bg;
1719
1720 login_check_credentials();
1721 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1722 cgi_check_for_malice();
1723 style_header("List Of Artifacts");
1724 style_submenu_element("250 Largest", "bigbloblist");
1725 if( bRecent==0 || n!=250 ){
1726 style_submenu_element("Recent","bloblist?n=250&recent");
1727 }
1728 if( bUnclst==0 ){
1729 style_submenu_element("Unclustered","bloblist?unclustered");
1730 }
1731 if( g.perm.Admin ){
1732 style_submenu_element("Artifact Log", "rcvfromlist");
1733 }
1734 if( !phantomOnly ){
1735 style_submenu_element("Phantoms", "bloblist?phan");
1736 }
1737 style_submenu_element("Clusters","clusterlist");
1738 if( g.perm.Private || g.perm.Admin ){
1739 if( !privOnly ){
1740 style_submenu_element("Private", "bloblist?priv");
1741 }
1742 }else{
@@ -1710,58 +1743,70 @@
1743 privOnly = 0;
1744 }
1745 if( g.perm.Write ){
1746 style_submenu_element("Artifact Stats", "artifact_stats");
1747 }
1748 if( !privOnly && !phantomOnly && mx>n && P("s")==0 && !bRecent && !bUnclst ){
1749 int i;
1750 @ <p>Select a range of artifacts to view:</p>
1751 @ <ul>
1752 for(i=1; i<=mx; i+=n){
1753 @ <li> %z(href("%R/bloblist?s=%d&n=%d",i,n))
1754 @ %d(i)..%d(i+n-1<mx?i+n-1:mx)</a>
1755 }
1756 @ <li> %z(href("%R/bloblist?n=250&recent"))250 most recent</a>
1757 @ <li> %z(href("%R/bloblist?unclustered"))All unclustered</a>
1758 @ </ul>
1759 style_finish_page();
1760 return;
1761 }
1762 if( phantomOnly || privOnly || mx>n ){
1763 style_submenu_element("Index", "bloblist");
1764 }
1765 if( privOnly ){
1766 @ <h2>Private Artifacts</h2>
1767 zRange = mprintf("IN private");
1768 }else if( phantomOnly ){
1769 @ <h2>Phantom Artifacts</h2>
1770 zRange = mprintf("IN phantom");
1771 }else if( bUnclst ){
1772 @ <h2>Unclustered Artifacts</h2>
1773 zRange = mprintf("IN unclustered");
1774 }else if( bRecent ){
1775 @ <h2>%d(n) Most Recent Artifacts</h2>
1776 zRange = mprintf(">=(SELECT rid FROM blob"
1777 " ORDER BY rid DESC LIMIT 1 OFFSET %d)",n);
1778 }else{
1779 zRange = mprintf("BETWEEN %d AND %d", s, s+n-1);
1780 }
1781 describe_artifacts(zRange);
1782 fossil_free(zRange);
1783 db_prepare(&q,
1784 /* 0 1 2 3 4 5 6 */
1785 "SELECT rid, uuid, summary, isPrivate, type='phantom', ref, rcvid, "
1786 " datetime(rcvfrom.mtime)"
1787 " FROM description LEFT JOIN rcvfrom USING(rcvid)"
1788 " ORDER BY rid %s",
1789 ((bRecent||bUnclst)?"DESC":"ASC")/*safe-for-%s*/
1790 );
1791 if( skin_detail_boolean("white-foreground") ){
1792 zSha1Bg = "#714417";
1793 zSha3Bg = "#177117";
1794 }else{
1795 zSha1Bg = "#ebffb0";
1796 zSha3Bg = "#b0ffb0";
1797 }
1798 @ <table cellpadding="2" cellspacing="0" border="1">
1799 @ <tr><th>RID<th>Hash<th>Received<th>Description<th>Ref<th>Remarks
 
 
 
 
1800 while( db_step(&q)==SQLITE_ROW ){
1801 int rid = db_column_int(&q,0);
1802 const char *zUuid = db_column_text(&q, 1);
1803 const char *zDesc = db_column_text(&q, 2);
1804 int isPriv = db_column_int(&q,3);
1805 int isPhantom = db_column_int(&q,4);
1806 const char *zRef = db_column_text(&q,5);
1807 const char *zDate = db_column_text(&q,7);
1808 if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){
1809 /* Don't show private artifacts to users without Private (x) permission */
1810 continue;
1811 }
1812 if( hashClr ){
@@ -1770,16 +1815,14 @@
1815 }else{
1816 @ <tr><td align="right">%d(rid)</td>
1817 }
1818 @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
1819 if( g.perm.Admin ){
1820 int rcvid = db_column_int(&q, 6);
1821 @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%h(zDate)</a>
1822 }else{
1823 @ <td>%h(zDate)
 
 
1824 }
1825 @ <td align="left">%h(zDesc)</td>
1826 if( zRef && zRef[0] ){
1827 @ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a>
1828 }else{
@@ -2163,5 +2206,89 @@
2206 " ORDER BY 1");
2207 @ <h1>Hash Prefix Collisions on All Artifacts</h1>
2208 collision_report("SELECT uuid FROM blob ORDER BY 1");
2209 style_finish_page();
2210 }
2211
2212 /*
2213 ** WEBPAGE: clusterlist
2214 **
2215 ** Show information about all cluster artifacts in the database.
2216 */
2217 void clusterlist_page(void){
2218 Stmt q;
2219 int cnt = 1;
2220 sqlite3_int64 szTotal = 0;
2221 sqlite3_int64 szCTotal = 0;
2222 login_check_credentials();
2223 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2224 style_header("All Cluster Artifacts");
2225 style_submenu_element("All Artifactst", "bloblist");
2226 if( g.perm.Admin ){
2227 style_submenu_element("Artifact Log", "rcvfromlist");
2228 }
2229 style_submenu_element("Phantoms", "bloblist?phan");
2230 if( g.perm.Write ){
2231 style_submenu_element("Artifact Stats", "artifact_stats");
2232 }
2233
2234 db_prepare(&q,
2235 "SELECT blob.uuid, "
2236 " blob.size, "
2237 " octet_length(blob.content), "
2238 " datetime(rcvfrom.mtime),"
2239 " user.login,"
2240 " rcvfrom.ipaddr"
2241 " FROM tagxref JOIN blob ON tagxref.rid=blob.rid"
2242 " LEFT JOIN rcvfrom ON blob.rcvid=rcvfrom.rcvid"
2243 " LEFT JOIN user ON user.uid=rcvfrom.uid"
2244 " WHERE tagxref.tagid=%d"
2245 " ORDER BY rcvfrom.mtime, blob.uuid",
2246 TAG_CLUSTER
2247 );
2248 @ <table cellpadding="2" cellspacing="0" border="1">
2249 @ <tr><th>&nbsp;
2250 @ <th>Hash
2251 @ <th>Date&nbsp;Received
2252 @ <th>Size
2253 @ <th>Compressed&nbsp;Size
2254 if( g.perm.Admin ){
2255 @ <th>User<th>IP-Address
2256 }
2257 while( db_step(&q)==SQLITE_ROW ){
2258 const char *zUuid = db_column_text(&q, 0);
2259 sqlite3_int64 sz = db_column_int64(&q, 1);
2260 sqlite3_int64 szC = db_column_int64(&q, 2);
2261 const char *zDate = db_column_text(&q, 3);
2262 const char *zUser = db_column_text(&q, 4);
2263 const char *zIp = db_column_text(&q, 5);
2264 szTotal += sz;
2265 szCTotal += szC;
2266 @ <tr><td align="right">%d(cnt++)
2267 @ <td><a href="%R/info/%S(zUuid)">%S(zUuid)</a>
2268 if( zDate ){
2269 @ <td>%h(zDate)
2270 }else{
2271 @ <td>&nbsp;
2272 }
2273 @ <td align="right">%,lld(sz)
2274 @ <td align="right">%,lld(szC)
2275 if( g.perm.Admin ){
2276 if( zUser ){
2277 @ <td>%h(zUser)
2278 }else{
2279 @ <td>&nbsp;
2280 }
2281 if( zIp ){
2282 @ <td>%h(zIp)
2283 }else{
2284 @ <td>&nbsp;
2285 }
2286 }
2287 @ </tr>
2288 }
2289 @ </table>
2290 db_finalize(&q);
2291 @ <p>Total size of all clusters: %,lld(szTotal) bytes,
2292 @ %,lld(szCTotal) bytes compressed</p>
2293 style_finish_page();
2294 }
2295
+50 -17
--- src/patch.c
+++ src/patch.c
@@ -81,13 +81,12 @@
8181
sqlite3_context *context,
8282
int argc,
8383
sqlite3_value **argv
8484
){
8585
const char *zFile;
86
- Blob x, y;
86
+ Blob x, y, out;
8787
int rid;
88
- char *aOut;
8988
int nOut;
9089
sqlite3_int64 sz;
9190
9291
rid = sqlite3_value_int(argv[0]);
9392
if( !content_get(rid, &x) ){
@@ -104,34 +103,29 @@
104103
if( sz<0 ){
105104
sqlite3_result_error(context, "mkdelta(X,Y): cannot read file Y", -1);
106105
blob_reset(&x);
107106
return;
108107
}
109
- aOut = sqlite3_malloc64(sz+70);
110
- if( aOut==0 ){
111
- sqlite3_result_error_nomem(context);
112
- blob_reset(&y);
113
- blob_reset(&x);
114
- return;
115
- }
108
+ blob_init(&out, 0, 0);
109
+ blob_resize(&out, sz+70);
116110
if( blob_size(&x)==blob_size(&y)
117111
&& memcmp(blob_buffer(&x), blob_buffer(&y), blob_size(&x))==0
118112
){
119113
blob_reset(&y);
120114
blob_reset(&x);
121115
sqlite3_result_blob64(context, "", 0, SQLITE_STATIC);
122116
return;
123117
}
124118
nOut = delta_create(blob_buffer(&x),blob_size(&x),
125
- blob_buffer(&y),blob_size(&y), aOut);
119
+ blob_buffer(&y),blob_size(&y), blob_buffer(&out));
120
+ blob_resize(&out, nOut);
126121
blob_reset(&x);
127122
blob_reset(&y);
128
- blob_init(&x, aOut, nOut);
129
- blob_compress(&x, &x);
130
- sqlite3_result_blob64(context, blob_buffer(&x), blob_size(&x),
123
+ blob_compress(&out, &out);
124
+ sqlite3_result_blob64(context, blob_buffer(&out), blob_size(&out),
131125
SQLITE_TRANSIENT);
132
- blob_reset(&x);
126
+ blob_reset(&out);
133127
}
134128
135129
136130
/*
137131
** Generate a binary patch file and store it into the file
@@ -387,11 +381,11 @@
387381
if( unsaved_changes(0) ){
388382
if( (mFlags & PATCH_FORCE)==0 ){
389383
fossil_fatal("Cannot apply patch: there are unsaved changes "
390384
"in the current check-out");
391385
}else{
392
- blob_appendf(&cmd, "%$ revert", g.nameOfExe);
386
+ blob_appendf(&cmd, "%$ revert --noundo", g.nameOfExe);
393387
if( mFlags & PATCH_DRYRUN ){
394388
fossil_print("%s\n", blob_str(&cmd));
395389
}else{
396390
int rc = fossil_system(blob_str(&cmd));
397391
if( rc ){
@@ -429,23 +423,27 @@
429423
}
430424
}
431425
}
432426
blob_reset(&cmd);
433427
if( db_table_exists("patch","patchmerge") ){
428
+ int nMerge = 0;
434429
db_prepare(&q,
435430
"SELECT type, mhash, upper(type) FROM patch.patchmerge"
436431
" WHERE type IN ('merge','cherrypick','backout','integrate')"
437432
" AND mhash NOT GLOB '*[^a-fA-F0-9]*';"
438433
);
439434
while( db_step(&q)==SQLITE_ROW ){
440435
const char *zType = db_column_text(&q,0);
441436
blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
442437
if( strcmp(zType,"merge")==0 ){
443
- blob_appendf(&cmd, " merge %s\n", db_column_text(&q,1));
438
+ blob_appendf(&cmd, " merge --noundo --nosync %s\n",
439
+ db_column_text(&q,1));
444440
}else{
445
- blob_appendf(&cmd, " merge --%s %s\n", zType, db_column_text(&q,1));
441
+ blob_appendf(&cmd, " merge --%s --noundo --nosync %s\n",
442
+ zType, db_column_text(&q,1));
446443
}
444
+ nMerge++;
447445
if( mFlags & PATCH_VERBOSE ){
448446
fossil_print("%-10s %s\n", db_column_text(&q,2),
449447
db_column_text(&q,0));
450448
}
451449
}
@@ -458,10 +456,45 @@
458456
fossil_fatal("unable to do merges:\n%s",
459457
blob_str(&cmd));
460458
}
461459
}
462460
blob_reset(&cmd);
461
+
462
+ /* 2024-12-16 https://fossil-scm.org/home/forumpost/51a37054
463
+ ** If one or more merge operations occurred in the patch and there are
464
+ ** files that are marked as "chnged' in the local VFILE but which
465
+ ** are not mentioned as having been modified in the patch, then
466
+ ** revert those files.
467
+ */
468
+ if( nMerge ){
469
+ int vid = db_lget_int("checkout", 0);
470
+ int nRevert = 0;
471
+ blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
472
+ blob_appendf(&cmd, " revert --noundo ");
473
+ db_prepare(&q,
474
+ "SELECT pathname FROM vfile WHERE vid=%d AND chnged "
475
+ "EXCEPT SELECT pathname FROM chng",
476
+ vid
477
+ );
478
+ while( db_step(&q)==SQLITE_ROW ){
479
+ blob_append_escaped_arg(&cmd, db_column_text(&q,0), 1);
480
+ nRevert++;
481
+ }
482
+ db_finalize(&q);
483
+ if( nRevert ){
484
+ if( mFlags & PATCH_DRYRUN ){
485
+ fossil_print("%s", blob_str(&cmd));
486
+ }else{
487
+ int rc = fossil_unsafe_system(blob_str(&cmd));
488
+ if( rc ){
489
+ fossil_fatal("unable to do reverts:\n%s",
490
+ blob_str(&cmd));
491
+ }
492
+ }
493
+ }
494
+ blob_reset(&cmd);
495
+ }
463496
}
464497
465498
/* Deletions */
466499
db_prepare(&q, "SELECT pathname FROM patch.chng"
467500
" WHERE origname IS NULL AND delta IS NULL");
468501
--- src/patch.c
+++ src/patch.c
@@ -81,13 +81,12 @@
81 sqlite3_context *context,
82 int argc,
83 sqlite3_value **argv
84 ){
85 const char *zFile;
86 Blob x, y;
87 int rid;
88 char *aOut;
89 int nOut;
90 sqlite3_int64 sz;
91
92 rid = sqlite3_value_int(argv[0]);
93 if( !content_get(rid, &x) ){
@@ -104,34 +103,29 @@
104 if( sz<0 ){
105 sqlite3_result_error(context, "mkdelta(X,Y): cannot read file Y", -1);
106 blob_reset(&x);
107 return;
108 }
109 aOut = sqlite3_malloc64(sz+70);
110 if( aOut==0 ){
111 sqlite3_result_error_nomem(context);
112 blob_reset(&y);
113 blob_reset(&x);
114 return;
115 }
116 if( blob_size(&x)==blob_size(&y)
117 && memcmp(blob_buffer(&x), blob_buffer(&y), blob_size(&x))==0
118 ){
119 blob_reset(&y);
120 blob_reset(&x);
121 sqlite3_result_blob64(context, "", 0, SQLITE_STATIC);
122 return;
123 }
124 nOut = delta_create(blob_buffer(&x),blob_size(&x),
125 blob_buffer(&y),blob_size(&y), aOut);
 
126 blob_reset(&x);
127 blob_reset(&y);
128 blob_init(&x, aOut, nOut);
129 blob_compress(&x, &x);
130 sqlite3_result_blob64(context, blob_buffer(&x), blob_size(&x),
131 SQLITE_TRANSIENT);
132 blob_reset(&x);
133 }
134
135
136 /*
137 ** Generate a binary patch file and store it into the file
@@ -387,11 +381,11 @@
387 if( unsaved_changes(0) ){
388 if( (mFlags & PATCH_FORCE)==0 ){
389 fossil_fatal("Cannot apply patch: there are unsaved changes "
390 "in the current check-out");
391 }else{
392 blob_appendf(&cmd, "%$ revert", g.nameOfExe);
393 if( mFlags & PATCH_DRYRUN ){
394 fossil_print("%s\n", blob_str(&cmd));
395 }else{
396 int rc = fossil_system(blob_str(&cmd));
397 if( rc ){
@@ -429,23 +423,27 @@
429 }
430 }
431 }
432 blob_reset(&cmd);
433 if( db_table_exists("patch","patchmerge") ){
 
434 db_prepare(&q,
435 "SELECT type, mhash, upper(type) FROM patch.patchmerge"
436 " WHERE type IN ('merge','cherrypick','backout','integrate')"
437 " AND mhash NOT GLOB '*[^a-fA-F0-9]*';"
438 );
439 while( db_step(&q)==SQLITE_ROW ){
440 const char *zType = db_column_text(&q,0);
441 blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
442 if( strcmp(zType,"merge")==0 ){
443 blob_appendf(&cmd, " merge %s\n", db_column_text(&q,1));
 
444 }else{
445 blob_appendf(&cmd, " merge --%s %s\n", zType, db_column_text(&q,1));
 
446 }
 
447 if( mFlags & PATCH_VERBOSE ){
448 fossil_print("%-10s %s\n", db_column_text(&q,2),
449 db_column_text(&q,0));
450 }
451 }
@@ -458,10 +456,45 @@
458 fossil_fatal("unable to do merges:\n%s",
459 blob_str(&cmd));
460 }
461 }
462 blob_reset(&cmd);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463 }
464
465 /* Deletions */
466 db_prepare(&q, "SELECT pathname FROM patch.chng"
467 " WHERE origname IS NULL AND delta IS NULL");
468
--- src/patch.c
+++ src/patch.c
@@ -81,13 +81,12 @@
81 sqlite3_context *context,
82 int argc,
83 sqlite3_value **argv
84 ){
85 const char *zFile;
86 Blob x, y, out;
87 int rid;
 
88 int nOut;
89 sqlite3_int64 sz;
90
91 rid = sqlite3_value_int(argv[0]);
92 if( !content_get(rid, &x) ){
@@ -104,34 +103,29 @@
103 if( sz<0 ){
104 sqlite3_result_error(context, "mkdelta(X,Y): cannot read file Y", -1);
105 blob_reset(&x);
106 return;
107 }
108 blob_init(&out, 0, 0);
109 blob_resize(&out, sz+70);
 
 
 
 
 
110 if( blob_size(&x)==blob_size(&y)
111 && memcmp(blob_buffer(&x), blob_buffer(&y), blob_size(&x))==0
112 ){
113 blob_reset(&y);
114 blob_reset(&x);
115 sqlite3_result_blob64(context, "", 0, SQLITE_STATIC);
116 return;
117 }
118 nOut = delta_create(blob_buffer(&x),blob_size(&x),
119 blob_buffer(&y),blob_size(&y), blob_buffer(&out));
120 blob_resize(&out, nOut);
121 blob_reset(&x);
122 blob_reset(&y);
123 blob_compress(&out, &out);
124 sqlite3_result_blob64(context, blob_buffer(&out), blob_size(&out),
 
125 SQLITE_TRANSIENT);
126 blob_reset(&out);
127 }
128
129
130 /*
131 ** Generate a binary patch file and store it into the file
@@ -387,11 +381,11 @@
381 if( unsaved_changes(0) ){
382 if( (mFlags & PATCH_FORCE)==0 ){
383 fossil_fatal("Cannot apply patch: there are unsaved changes "
384 "in the current check-out");
385 }else{
386 blob_appendf(&cmd, "%$ revert --noundo", g.nameOfExe);
387 if( mFlags & PATCH_DRYRUN ){
388 fossil_print("%s\n", blob_str(&cmd));
389 }else{
390 int rc = fossil_system(blob_str(&cmd));
391 if( rc ){
@@ -429,23 +423,27 @@
423 }
424 }
425 }
426 blob_reset(&cmd);
427 if( db_table_exists("patch","patchmerge") ){
428 int nMerge = 0;
429 db_prepare(&q,
430 "SELECT type, mhash, upper(type) FROM patch.patchmerge"
431 " WHERE type IN ('merge','cherrypick','backout','integrate')"
432 " AND mhash NOT GLOB '*[^a-fA-F0-9]*';"
433 );
434 while( db_step(&q)==SQLITE_ROW ){
435 const char *zType = db_column_text(&q,0);
436 blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
437 if( strcmp(zType,"merge")==0 ){
438 blob_appendf(&cmd, " merge --noundo --nosync %s\n",
439 db_column_text(&q,1));
440 }else{
441 blob_appendf(&cmd, " merge --%s --noundo --nosync %s\n",
442 zType, db_column_text(&q,1));
443 }
444 nMerge++;
445 if( mFlags & PATCH_VERBOSE ){
446 fossil_print("%-10s %s\n", db_column_text(&q,2),
447 db_column_text(&q,0));
448 }
449 }
@@ -458,10 +456,45 @@
456 fossil_fatal("unable to do merges:\n%s",
457 blob_str(&cmd));
458 }
459 }
460 blob_reset(&cmd);
461
462 /* 2024-12-16 https://fossil-scm.org/home/forumpost/51a37054
463 ** If one or more merge operations occurred in the patch and there are
464 ** files that are marked as "chnged' in the local VFILE but which
465 ** are not mentioned as having been modified in the patch, then
466 ** revert those files.
467 */
468 if( nMerge ){
469 int vid = db_lget_int("checkout", 0);
470 int nRevert = 0;
471 blob_append_escaped_arg(&cmd, g.nameOfExe, 1);
472 blob_appendf(&cmd, " revert --noundo ");
473 db_prepare(&q,
474 "SELECT pathname FROM vfile WHERE vid=%d AND chnged "
475 "EXCEPT SELECT pathname FROM chng",
476 vid
477 );
478 while( db_step(&q)==SQLITE_ROW ){
479 blob_append_escaped_arg(&cmd, db_column_text(&q,0), 1);
480 nRevert++;
481 }
482 db_finalize(&q);
483 if( nRevert ){
484 if( mFlags & PATCH_DRYRUN ){
485 fossil_print("%s", blob_str(&cmd));
486 }else{
487 int rc = fossil_unsafe_system(blob_str(&cmd));
488 if( rc ){
489 fossil_fatal("unable to do reverts:\n%s",
490 blob_str(&cmd));
491 }
492 }
493 }
494 blob_reset(&cmd);
495 }
496 }
497
498 /* Deletions */
499 db_prepare(&q, "SELECT pathname FROM patch.chng"
500 " WHERE origname IS NULL AND delta IS NULL");
501
+1
--- src/path.c
+++ src/path.c
@@ -542,10 +542,11 @@
542542
pChng = pAll;
543543
pAll = pAll->pNext;
544544
fossil_free(pChng);
545545
}
546546
}
547
+ path_reset();
547548
}
548549
549550
/*
550551
** COMMAND: test-name-changes
551552
**
552553
--- src/path.c
+++ src/path.c
@@ -542,10 +542,11 @@
542 pChng = pAll;
543 pAll = pAll->pNext;
544 fossil_free(pChng);
545 }
546 }
 
547 }
548
549 /*
550 ** COMMAND: test-name-changes
551 **
552
--- src/path.c
+++ src/path.c
@@ -542,10 +542,11 @@
542 pChng = pAll;
543 pAll = pAll->pNext;
544 fossil_free(pChng);
545 }
546 }
547 path_reset();
548 }
549
550 /*
551 ** COMMAND: test-name-changes
552 **
553
+14 -1
--- src/printf.c
+++ src/printf.c
@@ -105,10 +105,11 @@
105105
Use %!j to include double-quotes around it. */
106106
#define etSHELLESC 26 /* Escape a filename for use in a shell command: %$
107107
See blob_append_escaped_arg() for details
108108
"%$" -> adds "./" prefix if necessary.
109109
"%!$" -> omits the "./" prefix. */
110
+#define etHEX 27 /* Encode a string as hexadecimal */
110111
111112
112113
/*
113114
** An "etByte" is an 8-bit unsigned value.
114115
*/
@@ -142,11 +143,11 @@
142143
** NB: When modifying this table is it vital that you also update the fmtchr[]
143144
** variable to match!!!
144145
*/
145146
static const char aDigits[] = "0123456789ABCDEF0123456789abcdef";
146147
static const char aPrefix[] = "-x0\000X0";
147
-static const char fmtchr[] = "dsgzqQbBWhRtTwFSjcouxXfeEGin%p/$";
148
+static const char fmtchr[] = "dsgzqQbBWhRtTwFSjcouxXfeEGin%p/$H";
148149
static const et_info fmtinfo[] = {
149150
{ 'd', 10, 1, etRADIX, 0, 0 },
150151
{ 's', 0, 4, etSTRING, 0, 0 },
151152
{ 'g', 0, 1, etGENERIC, 30, 0 },
152153
{ 'z', 0, 6, etDYNSTRING, 0, 0 },
@@ -176,10 +177,11 @@
176177
{ 'n', 0, 0, etSIZE, 0, 0 },
177178
{ '%', 0, 0, etPERCENT, 0, 0 },
178179
{ 'p', 16, 0, etPOINTER, 0, 1 },
179180
{ '/', 0, 0, etPATH, 0, 0 },
180181
{ '$', 0, 0, etSHELLESC, 0, 0 },
182
+ { 'H', 0, 0, etHEX, 0, 0 },
181183
{ etERROR, 0,0,0,0,0} /* Must be last */
182184
};
183185
#define etNINFO count(fmtinfo)
184186
185187
/*
@@ -843,10 +845,21 @@
843845
case etSHELLESC: {
844846
char *zArg = va_arg(ap, char*);
845847
blob_append_escaped_arg(pBlob, zArg, !flag_altform2);
846848
length = width = 0;
847849
break;
850
+ }
851
+ case etHEX: {
852
+ char *zArg = va_arg(ap, char*);
853
+ int szArg = (int)strlen(zArg);
854
+ int szBlob = blob_size(pBlob);
855
+ u8 *aBuf;
856
+ blob_resize(pBlob, szBlob+szArg*2+1);
857
+ aBuf = (u8*)&blob_buffer(pBlob)[szBlob];
858
+ encode16((const u8*)zArg, aBuf, szArg);
859
+ length = width = 0;
860
+ break;
848861
}
849862
case etERROR:
850863
buf[0] = '%';
851864
buf[1] = c;
852865
errorflag = 0;
853866
--- src/printf.c
+++ src/printf.c
@@ -105,10 +105,11 @@
105 Use %!j to include double-quotes around it. */
106 #define etSHELLESC 26 /* Escape a filename for use in a shell command: %$
107 See blob_append_escaped_arg() for details
108 "%$" -> adds "./" prefix if necessary.
109 "%!$" -> omits the "./" prefix. */
 
110
111
112 /*
113 ** An "etByte" is an 8-bit unsigned value.
114 */
@@ -142,11 +143,11 @@
142 ** NB: When modifying this table is it vital that you also update the fmtchr[]
143 ** variable to match!!!
144 */
145 static const char aDigits[] = "0123456789ABCDEF0123456789abcdef";
146 static const char aPrefix[] = "-x0\000X0";
147 static const char fmtchr[] = "dsgzqQbBWhRtTwFSjcouxXfeEGin%p/$";
148 static const et_info fmtinfo[] = {
149 { 'd', 10, 1, etRADIX, 0, 0 },
150 { 's', 0, 4, etSTRING, 0, 0 },
151 { 'g', 0, 1, etGENERIC, 30, 0 },
152 { 'z', 0, 6, etDYNSTRING, 0, 0 },
@@ -176,10 +177,11 @@
176 { 'n', 0, 0, etSIZE, 0, 0 },
177 { '%', 0, 0, etPERCENT, 0, 0 },
178 { 'p', 16, 0, etPOINTER, 0, 1 },
179 { '/', 0, 0, etPATH, 0, 0 },
180 { '$', 0, 0, etSHELLESC, 0, 0 },
 
181 { etERROR, 0,0,0,0,0} /* Must be last */
182 };
183 #define etNINFO count(fmtinfo)
184
185 /*
@@ -843,10 +845,21 @@
843 case etSHELLESC: {
844 char *zArg = va_arg(ap, char*);
845 blob_append_escaped_arg(pBlob, zArg, !flag_altform2);
846 length = width = 0;
847 break;
 
 
 
 
 
 
 
 
 
 
 
848 }
849 case etERROR:
850 buf[0] = '%';
851 buf[1] = c;
852 errorflag = 0;
853
--- src/printf.c
+++ src/printf.c
@@ -105,10 +105,11 @@
105 Use %!j to include double-quotes around it. */
106 #define etSHELLESC 26 /* Escape a filename for use in a shell command: %$
107 See blob_append_escaped_arg() for details
108 "%$" -> adds "./" prefix if necessary.
109 "%!$" -> omits the "./" prefix. */
110 #define etHEX 27 /* Encode a string as hexadecimal */
111
112
113 /*
114 ** An "etByte" is an 8-bit unsigned value.
115 */
@@ -142,11 +143,11 @@
143 ** NB: When modifying this table is it vital that you also update the fmtchr[]
144 ** variable to match!!!
145 */
146 static const char aDigits[] = "0123456789ABCDEF0123456789abcdef";
147 static const char aPrefix[] = "-x0\000X0";
148 static const char fmtchr[] = "dsgzqQbBWhRtTwFSjcouxXfeEGin%p/$H";
149 static const et_info fmtinfo[] = {
150 { 'd', 10, 1, etRADIX, 0, 0 },
151 { 's', 0, 4, etSTRING, 0, 0 },
152 { 'g', 0, 1, etGENERIC, 30, 0 },
153 { 'z', 0, 6, etDYNSTRING, 0, 0 },
@@ -176,10 +177,11 @@
177 { 'n', 0, 0, etSIZE, 0, 0 },
178 { '%', 0, 0, etPERCENT, 0, 0 },
179 { 'p', 16, 0, etPOINTER, 0, 1 },
180 { '/', 0, 0, etPATH, 0, 0 },
181 { '$', 0, 0, etSHELLESC, 0, 0 },
182 { 'H', 0, 0, etHEX, 0, 0 },
183 { etERROR, 0,0,0,0,0} /* Must be last */
184 };
185 #define etNINFO count(fmtinfo)
186
187 /*
@@ -843,10 +845,21 @@
845 case etSHELLESC: {
846 char *zArg = va_arg(ap, char*);
847 blob_append_escaped_arg(pBlob, zArg, !flag_altform2);
848 length = width = 0;
849 break;
850 }
851 case etHEX: {
852 char *zArg = va_arg(ap, char*);
853 int szArg = (int)strlen(zArg);
854 int szBlob = blob_size(pBlob);
855 u8 *aBuf;
856 blob_resize(pBlob, szBlob+szArg*2+1);
857 aBuf = (u8*)&blob_buffer(pBlob)[szBlob];
858 encode16((const u8*)zArg, aBuf, szArg);
859 length = width = 0;
860 break;
861 }
862 case etERROR:
863 buf[0] = '%';
864 buf[1] = c;
865 errorflag = 0;
866
+9 -3
--- src/rebuild.c
+++ src/rebuild.c
@@ -390,25 +390,31 @@
390390
}
391391
manifest_disable_event_triggers();
392392
rebuild_update_schema();
393393
blob_init(&sql, 0, 0);
394394
db_unprotect(PROTECT_ALL);
395
+#ifndef SQLITE_PREPARE_DONT_LOG
396
+ g.dbIgnoreErrors++;
397
+#endif
395398
db_prepare(&q,
396
- "SELECT name FROM sqlite_schema /*scan*/"
397
- " WHERE type='table'"
399
+ "SELECT name FROM pragma_table_list /*scan*/"
400
+ " WHERE schema='repository' AND type IN ('table','virtual')"
398401
" AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
399402
"'config','shun','private','reportfmt',"
400403
"'concealed','accesslog','modreq',"
401404
"'purgeevent','purgeitem','unversioned',"
402405
"'subscriber','pending_alert','chat')"
403406
" AND name NOT GLOB 'sqlite_*'"
404
- " AND name NOT GLOB 'fx_*'"
407
+ " AND name NOT GLOB 'fx_*';"
405408
);
406409
while( db_step(&q)==SQLITE_ROW ){
407410
blob_appendf(&sql, "DROP TABLE IF EXISTS \"%w\";\n", db_column_text(&q,0));
408411
}
409412
db_finalize(&q);
413
+#ifndef SQLITE_PREPARE_DONT_LOG
414
+ g.dbIgnoreErrors--;
415
+#endif
410416
db_multi_exec("%s", blob_str(&sql)/*safe-for-%s*/);
411417
blob_reset(&sql);
412418
db_multi_exec("%s", zRepositorySchema2/*safe-for-%s*/);
413419
ticket_create_table(0);
414420
shun_artifacts();
415421
--- src/rebuild.c
+++ src/rebuild.c
@@ -390,25 +390,31 @@
390 }
391 manifest_disable_event_triggers();
392 rebuild_update_schema();
393 blob_init(&sql, 0, 0);
394 db_unprotect(PROTECT_ALL);
 
 
 
395 db_prepare(&q,
396 "SELECT name FROM sqlite_schema /*scan*/"
397 " WHERE type='table'"
398 " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
399 "'config','shun','private','reportfmt',"
400 "'concealed','accesslog','modreq',"
401 "'purgeevent','purgeitem','unversioned',"
402 "'subscriber','pending_alert','chat')"
403 " AND name NOT GLOB 'sqlite_*'"
404 " AND name NOT GLOB 'fx_*'"
405 );
406 while( db_step(&q)==SQLITE_ROW ){
407 blob_appendf(&sql, "DROP TABLE IF EXISTS \"%w\";\n", db_column_text(&q,0));
408 }
409 db_finalize(&q);
 
 
 
410 db_multi_exec("%s", blob_str(&sql)/*safe-for-%s*/);
411 blob_reset(&sql);
412 db_multi_exec("%s", zRepositorySchema2/*safe-for-%s*/);
413 ticket_create_table(0);
414 shun_artifacts();
415
--- src/rebuild.c
+++ src/rebuild.c
@@ -390,25 +390,31 @@
390 }
391 manifest_disable_event_triggers();
392 rebuild_update_schema();
393 blob_init(&sql, 0, 0);
394 db_unprotect(PROTECT_ALL);
395 #ifndef SQLITE_PREPARE_DONT_LOG
396 g.dbIgnoreErrors++;
397 #endif
398 db_prepare(&q,
399 "SELECT name FROM pragma_table_list /*scan*/"
400 " WHERE schema='repository' AND type IN ('table','virtual')"
401 " AND name NOT IN ('admin_log', 'blob','delta','rcvfrom','user','alias',"
402 "'config','shun','private','reportfmt',"
403 "'concealed','accesslog','modreq',"
404 "'purgeevent','purgeitem','unversioned',"
405 "'subscriber','pending_alert','chat')"
406 " AND name NOT GLOB 'sqlite_*'"
407 " AND name NOT GLOB 'fx_*';"
408 );
409 while( db_step(&q)==SQLITE_ROW ){
410 blob_appendf(&sql, "DROP TABLE IF EXISTS \"%w\";\n", db_column_text(&q,0));
411 }
412 db_finalize(&q);
413 #ifndef SQLITE_PREPARE_DONT_LOG
414 g.dbIgnoreErrors--;
415 #endif
416 db_multi_exec("%s", blob_str(&sql)/*safe-for-%s*/);
417 blob_reset(&sql);
418 db_multi_exec("%s", zRepositorySchema2/*safe-for-%s*/);
419 ticket_create_table(0);
420 shun_artifacts();
421
+7 -7
--- src/schema.c
+++ src/schema.c
@@ -28,11 +28,11 @@
2828
@ -- ~/.fossil file and that stores information about the users setup.
2929
@ --
3030
@ CREATE TABLE global_config(
3131
@ name TEXT PRIMARY KEY,
3232
@ value TEXT
33
-@ );
33
+@ ) WITHOUT ROWID;
3434
@
3535
@ -- Identifier for this file type.
3636
@ -- The integer is the same as 'FSLG'.
3737
@ PRAGMA application_id=252006675;
3838
;
@@ -138,11 +138,11 @@
138138
@ CREATE TABLE config(
139139
@ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry
140140
@ value CLOB, -- Content of the named parameter
141141
@ mtime DATE, -- last modified. seconds since 1970
142142
@ CHECK( typeof(name)='text' AND length(name)>=1 )
143
-@ );
143
+@ ) WITHOUT ROWID;
144144
@
145145
@ -- Artifacts that should not be processed are identified in the
146146
@ -- "shun" table. Artifacts that are control-file forgeries or
147147
@ -- spam or artifacts whose contents violate administrative policy
148148
@ -- can be shunned in order to prevent them from contaminating
@@ -151,14 +151,14 @@
151151
@ -- Shunned artifacts do not exist in the blob table. Hence they
152152
@ -- have not artifact ID (rid) and we thus must store their full
153153
@ -- UUID.
154154
@ --
155155
@ CREATE TABLE shun(
156
-@ uuid UNIQUE, -- UUID of artifact to be shunned. Canonical form
156
+@ uuid TEXT PRIMARY KEY,-- UUID of artifact to be shunned. Canonical form
157157
@ mtime DATE, -- When added. seconds since 1970
158158
@ scom TEXT -- Optional text explaining why the shun occurred
159
-@ );
159
+@ ) WITHOUT ROWID;
160160
@
161161
@ -- Artifacts that should not be pushed are stored in the "private"
162162
@ -- table. Private artifacts are omitted from the "unclustered" and
163163
@ -- "unsent" tables.
164164
@ --
@@ -193,11 +193,11 @@
193193
@ --
194194
@ CREATE TABLE concealed(
195195
@ hash TEXT PRIMARY KEY, -- The SHA1 hash of content
196196
@ mtime DATE, -- Time created. Seconds since 1970
197197
@ content TEXT -- Content intended to be concealed
198
-@ );
198
+@ ) WITHOUT ROWID;
199199
@
200200
@ -- The application ID helps the unix "file" command to identify the
201201
@ -- database as a fossil repository.
202202
@ PRAGMA application_id=252006673;
203203
;
@@ -528,11 +528,11 @@
528528
** The schema for the local FOSSIL database file found at the root
529529
** of every check-out. This database contains the complete state of
530530
** the check-out. See also the addendum in zLocalSchemaVmerge[].
531531
*/
532532
const char zLocalSchema[] =
533
-@ -- The VVAR table holds miscellanous information about the local database
533
+@ -- The VVAR table holds miscellanous information about the local checkout
534534
@ -- in the form of name-value pairs. This is similar to the VAR table
535535
@ -- table in the repository except that this table holds information that
536536
@ -- is specific to the local check-out.
537537
@ --
538538
@ -- Important Variables:
@@ -542,11 +542,11 @@
542542
@ --
543543
@ CREATE TABLE vvar(
544544
@ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry
545545
@ value CLOB, -- Content of the named parameter
546546
@ CHECK( typeof(name)='text' AND length(name)>=1 )
547
-@ );
547
+@ ) WITHOUT ROWID;
548548
@
549549
@ -- Each entry in the vfile table represents a single file in the
550550
@ -- current check-out.
551551
@ --
552552
@ -- The file.rid field is 0 for files or folders that have been
553553
--- src/schema.c
+++ src/schema.c
@@ -28,11 +28,11 @@
28 @ -- ~/.fossil file and that stores information about the users setup.
29 @ --
30 @ CREATE TABLE global_config(
31 @ name TEXT PRIMARY KEY,
32 @ value TEXT
33 @ );
34 @
35 @ -- Identifier for this file type.
36 @ -- The integer is the same as 'FSLG'.
37 @ PRAGMA application_id=252006675;
38 ;
@@ -138,11 +138,11 @@
138 @ CREATE TABLE config(
139 @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry
140 @ value CLOB, -- Content of the named parameter
141 @ mtime DATE, -- last modified. seconds since 1970
142 @ CHECK( typeof(name)='text' AND length(name)>=1 )
143 @ );
144 @
145 @ -- Artifacts that should not be processed are identified in the
146 @ -- "shun" table. Artifacts that are control-file forgeries or
147 @ -- spam or artifacts whose contents violate administrative policy
148 @ -- can be shunned in order to prevent them from contaminating
@@ -151,14 +151,14 @@
151 @ -- Shunned artifacts do not exist in the blob table. Hence they
152 @ -- have not artifact ID (rid) and we thus must store their full
153 @ -- UUID.
154 @ --
155 @ CREATE TABLE shun(
156 @ uuid UNIQUE, -- UUID of artifact to be shunned. Canonical form
157 @ mtime DATE, -- When added. seconds since 1970
158 @ scom TEXT -- Optional text explaining why the shun occurred
159 @ );
160 @
161 @ -- Artifacts that should not be pushed are stored in the "private"
162 @ -- table. Private artifacts are omitted from the "unclustered" and
163 @ -- "unsent" tables.
164 @ --
@@ -193,11 +193,11 @@
193 @ --
194 @ CREATE TABLE concealed(
195 @ hash TEXT PRIMARY KEY, -- The SHA1 hash of content
196 @ mtime DATE, -- Time created. Seconds since 1970
197 @ content TEXT -- Content intended to be concealed
198 @ );
199 @
200 @ -- The application ID helps the unix "file" command to identify the
201 @ -- database as a fossil repository.
202 @ PRAGMA application_id=252006673;
203 ;
@@ -528,11 +528,11 @@
528 ** The schema for the local FOSSIL database file found at the root
529 ** of every check-out. This database contains the complete state of
530 ** the check-out. See also the addendum in zLocalSchemaVmerge[].
531 */
532 const char zLocalSchema[] =
533 @ -- The VVAR table holds miscellanous information about the local database
534 @ -- in the form of name-value pairs. This is similar to the VAR table
535 @ -- table in the repository except that this table holds information that
536 @ -- is specific to the local check-out.
537 @ --
538 @ -- Important Variables:
@@ -542,11 +542,11 @@
542 @ --
543 @ CREATE TABLE vvar(
544 @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry
545 @ value CLOB, -- Content of the named parameter
546 @ CHECK( typeof(name)='text' AND length(name)>=1 )
547 @ );
548 @
549 @ -- Each entry in the vfile table represents a single file in the
550 @ -- current check-out.
551 @ --
552 @ -- The file.rid field is 0 for files or folders that have been
553
--- src/schema.c
+++ src/schema.c
@@ -28,11 +28,11 @@
28 @ -- ~/.fossil file and that stores information about the users setup.
29 @ --
30 @ CREATE TABLE global_config(
31 @ name TEXT PRIMARY KEY,
32 @ value TEXT
33 @ ) WITHOUT ROWID;
34 @
35 @ -- Identifier for this file type.
36 @ -- The integer is the same as 'FSLG'.
37 @ PRAGMA application_id=252006675;
38 ;
@@ -138,11 +138,11 @@
138 @ CREATE TABLE config(
139 @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry
140 @ value CLOB, -- Content of the named parameter
141 @ mtime DATE, -- last modified. seconds since 1970
142 @ CHECK( typeof(name)='text' AND length(name)>=1 )
143 @ ) WITHOUT ROWID;
144 @
145 @ -- Artifacts that should not be processed are identified in the
146 @ -- "shun" table. Artifacts that are control-file forgeries or
147 @ -- spam or artifacts whose contents violate administrative policy
148 @ -- can be shunned in order to prevent them from contaminating
@@ -151,14 +151,14 @@
151 @ -- Shunned artifacts do not exist in the blob table. Hence they
152 @ -- have not artifact ID (rid) and we thus must store their full
153 @ -- UUID.
154 @ --
155 @ CREATE TABLE shun(
156 @ uuid TEXT PRIMARY KEY,-- UUID of artifact to be shunned. Canonical form
157 @ mtime DATE, -- When added. seconds since 1970
158 @ scom TEXT -- Optional text explaining why the shun occurred
159 @ ) WITHOUT ROWID;
160 @
161 @ -- Artifacts that should not be pushed are stored in the "private"
162 @ -- table. Private artifacts are omitted from the "unclustered" and
163 @ -- "unsent" tables.
164 @ --
@@ -193,11 +193,11 @@
193 @ --
194 @ CREATE TABLE concealed(
195 @ hash TEXT PRIMARY KEY, -- The SHA1 hash of content
196 @ mtime DATE, -- Time created. Seconds since 1970
197 @ content TEXT -- Content intended to be concealed
198 @ ) WITHOUT ROWID;
199 @
200 @ -- The application ID helps the unix "file" command to identify the
201 @ -- database as a fossil repository.
202 @ PRAGMA application_id=252006673;
203 ;
@@ -528,11 +528,11 @@
528 ** The schema for the local FOSSIL database file found at the root
529 ** of every check-out. This database contains the complete state of
530 ** the check-out. See also the addendum in zLocalSchemaVmerge[].
531 */
532 const char zLocalSchema[] =
533 @ -- The VVAR table holds miscellanous information about the local checkout
534 @ -- in the form of name-value pairs. This is similar to the VAR table
535 @ -- table in the repository except that this table holds information that
536 @ -- is specific to the local check-out.
537 @ --
538 @ -- Important Variables:
@@ -542,11 +542,11 @@
542 @ --
543 @ CREATE TABLE vvar(
544 @ name TEXT PRIMARY KEY NOT NULL, -- Primary name of the entry
545 @ value CLOB, -- Content of the named parameter
546 @ CHECK( typeof(name)='text' AND length(name)>=1 )
547 @ ) WITHOUT ROWID;
548 @
549 @ -- Each entry in the vfile table represents a single file in the
550 @ -- current check-out.
551 @ --
552 @ -- The file.rid field is 0 for files or folders that have been
553
+12 -1
--- src/setup.c
+++ src/setup.c
@@ -501,13 +501,24 @@
501501
@ behavior or to find an SQL injection opportunity or similar. This can
502502
@ waste hours of CPU time and gigabytes of bandwidth on the server. A
503503
@ suggested value for this setting is:
504504
@ "<tt>timeline,*diff,vpatch,annotate,blame,praise,dir,tree</tt>".
505505
@ (Property: robot-restrict)
506
- @ <p>
506
+ @ <br>
507507
textarea_attribute("", 2, 80,
508508
"robot-restrict", "rbrestrict", "", 0);
509
+ @ <br> The following comma-separated GLOB pattern allows for exceptions
510
+ @ in the maximum number of query parameters before a request is considered
511
+ @ complex. If this GLOB pattern exists and is non-empty and if it
512
+ @ matches against the pagename followed by "/" and the number of query
513
+ @ parameters, then the request is allowed through. For example, the
514
+ @ suggested pattern of "timeline/[012]" allows the /timeline page to
515
+ @ pass with up to 2 query parameters besides "name".
516
+ @ (Property: robot-restrict-qp)
517
+ @ <br>
518
+ textarea_attribute("", 2, 80,
519
+ "robot-restrict-qp", "rbrestrictqp", "", 0);
509520
510521
@ <hr>
511522
@ <p><input type="submit" name="submit" value="Apply Changes"></p>
512523
@ </div></form>
513524
db_end_transaction(0);
514525
--- src/setup.c
+++ src/setup.c
@@ -501,13 +501,24 @@
501 @ behavior or to find an SQL injection opportunity or similar. This can
502 @ waste hours of CPU time and gigabytes of bandwidth on the server. A
503 @ suggested value for this setting is:
504 @ "<tt>timeline,*diff,vpatch,annotate,blame,praise,dir,tree</tt>".
505 @ (Property: robot-restrict)
506 @ <p>
507 textarea_attribute("", 2, 80,
508 "robot-restrict", "rbrestrict", "", 0);
 
 
 
 
 
 
 
 
 
 
 
509
510 @ <hr>
511 @ <p><input type="submit" name="submit" value="Apply Changes"></p>
512 @ </div></form>
513 db_end_transaction(0);
514
--- src/setup.c
+++ src/setup.c
@@ -501,13 +501,24 @@
501 @ behavior or to find an SQL injection opportunity or similar. This can
502 @ waste hours of CPU time and gigabytes of bandwidth on the server. A
503 @ suggested value for this setting is:
504 @ "<tt>timeline,*diff,vpatch,annotate,blame,praise,dir,tree</tt>".
505 @ (Property: robot-restrict)
506 @ <br>
507 textarea_attribute("", 2, 80,
508 "robot-restrict", "rbrestrict", "", 0);
509 @ <br> The following comma-separated GLOB pattern allows for exceptions
510 @ in the maximum number of query parameters before a request is considered
511 @ complex. If this GLOB pattern exists and is non-empty and if it
512 @ matches against the pagename followed by "/" and the number of query
513 @ parameters, then the request is allowed through. For example, the
514 @ suggested pattern of "timeline/[012]" allows the /timeline page to
515 @ pass with up to 2 query parameters besides "name".
516 @ (Property: robot-restrict-qp)
517 @ <br>
518 textarea_attribute("", 2, 80,
519 "robot-restrict-qp", "rbrestrictqp", "", 0);
520
521 @ <hr>
522 @ <p><input type="submit" name="submit" value="Apply Changes"></p>
523 @ </div></form>
524 db_end_transaction(0);
525
+7 -5
--- src/setupuser.c
+++ src/setupuser.c
@@ -115,11 +115,11 @@
115115
}
116116
if( !bUnusedOnly ){
117117
style_submenu_element("Unused", "setup_ulist?unused");
118118
}
119119
@ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \
120
- @ data-column-types='ktxTTKt' data-init-sort='2'>
120
+ @ data-column-types='ktxKTKt' data-init-sort='4'>
121121
@ <thead><tr>
122122
@ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login\
123123
@ <th>Alerts</tr></thead>
124124
@ <tbody>
125125
db_multi_exec(
@@ -161,15 +161,16 @@
161161
" lower(login) AS sortkey, "
162162
" CASE WHEN info LIKE '%%expires 20%%'"
163163
" THEN substr(info,instr(lower(info),'expires')+8,10)"
164164
" END AS exp,"
165165
"atime,"
166
- " subscriber.ssub, subscriber.subscriberId"
166
+ " subscriber.ssub, subscriber.subscriberId,"
167
+ " user.mtime AS sorttime"
167168
" FROM user LEFT JOIN lastAccess ON login=uname"
168169
" LEFT JOIN subscriber ON login=suname"
169170
" WHERE login NOT IN ('anonymous','nobody','developer','reader') %s"
170
- " ORDER BY sortkey", zWith/*safe-for-%s*/
171
+ " ORDER BY sorttime DESC", zWith/*safe-for-%s*/
171172
);
172173
rNow = db_double(0.0, "SELECT julianday('now');");
173174
while( db_step(&s)==SQLITE_ROW ){
174175
int uid = db_column_int(&s, 0);
175176
const char *zLogin = db_column_text(&s, 1);
@@ -180,10 +181,11 @@
180181
const char *zExp = db_column_text(&s,6);
181182
double rATime = db_column_double(&s,7);
182183
char *zAge = 0;
183184
const char *zSub;
184185
int sid = db_column_int(&s,9);
186
+ sqlite3_int64 sorttime = db_column_int64(&s, 10);
185187
if( rATime>0.0 ){
186188
zAge = human_readable_age(rNow - rATime);
187189
}
188190
if( bUbg ){
189191
@ <tr style='background-color: %h(user_color(zLogin));'>
@@ -192,11 +194,11 @@
192194
}
193195
@ <td data-sortkey='%h(zSortKey)'>\
194196
@ <a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a>
195197
@ <td>%h(zCap)
196198
@ <td>%h(zInfo)
197
- @ <td>%h(zDate?zDate:"")
199
+ @ <td data-sortkey='%09llx(sorttime)'>%h(zDate?zDate:"")
198200
@ <td>%h(zExp?zExp:"")
199201
@ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"")
200202
if( db_column_type(&s,8)==SQLITE_NULL ){
201203
@ <td>
202204
}else if( (zSub = db_column_text(&s,8))==0 || zSub[0]==0 ){
@@ -1016,11 +1018,11 @@
10161018
style_header("User %h", db_column_text(&q,1));
10171019
@ <table class="label-value">
10181020
@ <tr><th>uid:</th><td>%d(db_column_int(&q,0))
10191021
@ (<a href="%R/setup_uedit?id=%d(db_column_int(&q,0))">edit</a>)</td></tr>
10201022
@ <tr><th>login:</th><td>%h(db_column_text(&q,1))</td></tr>
1021
- @ <tr><th>capabilities:</th><td>%h(db_column_text(&q,2))</th></tr>
1023
+ @ <tr><th>capabilities:</th><td>%h(db_column_text(&q,2))</td></tr>
10221024
@ <tr><th valign="top">info:</th>
10231025
@ <td valign="top"><span style='white-space:pre-line;'>\
10241026
@ %h(db_column_text(&q,5))</span></td></tr>
10251027
@ <tr><th>user.mtime:</th><td>%h(db_column_text(&q,6))</td></tr>
10261028
if( db_column_type(&q,7)!=SQLITE_NULL ){
10271029
--- src/setupuser.c
+++ src/setupuser.c
@@ -115,11 +115,11 @@
115 }
116 if( !bUnusedOnly ){
117 style_submenu_element("Unused", "setup_ulist?unused");
118 }
119 @ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \
120 @ data-column-types='ktxTTKt' data-init-sort='2'>
121 @ <thead><tr>
122 @ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login\
123 @ <th>Alerts</tr></thead>
124 @ <tbody>
125 db_multi_exec(
@@ -161,15 +161,16 @@
161 " lower(login) AS sortkey, "
162 " CASE WHEN info LIKE '%%expires 20%%'"
163 " THEN substr(info,instr(lower(info),'expires')+8,10)"
164 " END AS exp,"
165 "atime,"
166 " subscriber.ssub, subscriber.subscriberId"
 
167 " FROM user LEFT JOIN lastAccess ON login=uname"
168 " LEFT JOIN subscriber ON login=suname"
169 " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s"
170 " ORDER BY sortkey", zWith/*safe-for-%s*/
171 );
172 rNow = db_double(0.0, "SELECT julianday('now');");
173 while( db_step(&s)==SQLITE_ROW ){
174 int uid = db_column_int(&s, 0);
175 const char *zLogin = db_column_text(&s, 1);
@@ -180,10 +181,11 @@
180 const char *zExp = db_column_text(&s,6);
181 double rATime = db_column_double(&s,7);
182 char *zAge = 0;
183 const char *zSub;
184 int sid = db_column_int(&s,9);
 
185 if( rATime>0.0 ){
186 zAge = human_readable_age(rNow - rATime);
187 }
188 if( bUbg ){
189 @ <tr style='background-color: %h(user_color(zLogin));'>
@@ -192,11 +194,11 @@
192 }
193 @ <td data-sortkey='%h(zSortKey)'>\
194 @ <a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a>
195 @ <td>%h(zCap)
196 @ <td>%h(zInfo)
197 @ <td>%h(zDate?zDate:"")
198 @ <td>%h(zExp?zExp:"")
199 @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"")
200 if( db_column_type(&s,8)==SQLITE_NULL ){
201 @ <td>
202 }else if( (zSub = db_column_text(&s,8))==0 || zSub[0]==0 ){
@@ -1016,11 +1018,11 @@
1016 style_header("User %h", db_column_text(&q,1));
1017 @ <table class="label-value">
1018 @ <tr><th>uid:</th><td>%d(db_column_int(&q,0))
1019 @ (<a href="%R/setup_uedit?id=%d(db_column_int(&q,0))">edit</a>)</td></tr>
1020 @ <tr><th>login:</th><td>%h(db_column_text(&q,1))</td></tr>
1021 @ <tr><th>capabilities:</th><td>%h(db_column_text(&q,2))</th></tr>
1022 @ <tr><th valign="top">info:</th>
1023 @ <td valign="top"><span style='white-space:pre-line;'>\
1024 @ %h(db_column_text(&q,5))</span></td></tr>
1025 @ <tr><th>user.mtime:</th><td>%h(db_column_text(&q,6))</td></tr>
1026 if( db_column_type(&q,7)!=SQLITE_NULL ){
1027
--- src/setupuser.c
+++ src/setupuser.c
@@ -115,11 +115,11 @@
115 }
116 if( !bUnusedOnly ){
117 style_submenu_element("Unused", "setup_ulist?unused");
118 }
119 @ <table border=1 cellpadding=2 cellspacing=0 class='userTable sortable' \
120 @ data-column-types='ktxKTKt' data-init-sort='4'>
121 @ <thead><tr>
122 @ <th>Login Name<th>Caps<th>Info<th>Date<th>Expire<th>Last Login\
123 @ <th>Alerts</tr></thead>
124 @ <tbody>
125 db_multi_exec(
@@ -161,15 +161,16 @@
161 " lower(login) AS sortkey, "
162 " CASE WHEN info LIKE '%%expires 20%%'"
163 " THEN substr(info,instr(lower(info),'expires')+8,10)"
164 " END AS exp,"
165 "atime,"
166 " subscriber.ssub, subscriber.subscriberId,"
167 " user.mtime AS sorttime"
168 " FROM user LEFT JOIN lastAccess ON login=uname"
169 " LEFT JOIN subscriber ON login=suname"
170 " WHERE login NOT IN ('anonymous','nobody','developer','reader') %s"
171 " ORDER BY sorttime DESC", zWith/*safe-for-%s*/
172 );
173 rNow = db_double(0.0, "SELECT julianday('now');");
174 while( db_step(&s)==SQLITE_ROW ){
175 int uid = db_column_int(&s, 0);
176 const char *zLogin = db_column_text(&s, 1);
@@ -180,10 +181,11 @@
181 const char *zExp = db_column_text(&s,6);
182 double rATime = db_column_double(&s,7);
183 char *zAge = 0;
184 const char *zSub;
185 int sid = db_column_int(&s,9);
186 sqlite3_int64 sorttime = db_column_int64(&s, 10);
187 if( rATime>0.0 ){
188 zAge = human_readable_age(rNow - rATime);
189 }
190 if( bUbg ){
191 @ <tr style='background-color: %h(user_color(zLogin));'>
@@ -192,11 +194,11 @@
194 }
195 @ <td data-sortkey='%h(zSortKey)'>\
196 @ <a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a>
197 @ <td>%h(zCap)
198 @ <td>%h(zInfo)
199 @ <td data-sortkey='%09llx(sorttime)'>%h(zDate?zDate:"")
200 @ <td>%h(zExp?zExp:"")
201 @ <td data-sortkey='%f(rATime)' style='white-space:nowrap'>%s(zAge?zAge:"")
202 if( db_column_type(&s,8)==SQLITE_NULL ){
203 @ <td>
204 }else if( (zSub = db_column_text(&s,8))==0 || zSub[0]==0 ){
@@ -1016,11 +1018,11 @@
1018 style_header("User %h", db_column_text(&q,1));
1019 @ <table class="label-value">
1020 @ <tr><th>uid:</th><td>%d(db_column_int(&q,0))
1021 @ (<a href="%R/setup_uedit?id=%d(db_column_int(&q,0))">edit</a>)</td></tr>
1022 @ <tr><th>login:</th><td>%h(db_column_text(&q,1))</td></tr>
1023 @ <tr><th>capabilities:</th><td>%h(db_column_text(&q,2))</td></tr>
1024 @ <tr><th valign="top">info:</th>
1025 @ <td valign="top"><span style='white-space:pre-line;'>\
1026 @ %h(db_column_text(&q,5))</span></td></tr>
1027 @ <tr><th>user.mtime:</th><td>%h(db_column_text(&q,6))</td></tr>
1028 if( db_column_type(&q,7)!=SQLITE_NULL ){
1029
+5 -40
--- src/sitemap.c
+++ src/sitemap.c
@@ -56,22 +56,10 @@
5656
int i;
5757
int isPopup = 0; /* This is an XMLHttpRequest() for /sitemap */
5858
int e = atoi(PD("e","0"));
5959
const char *zExtra;
6060
61
-#if 0 /* Removed 2021-01-26 */
62
- const struct {
63
- const char *zTitle;
64
- const char *zProperty;
65
- } aExtra[] = {
66
- { "Documentation", "sitemap-docidx" },
67
- { "Download", "sitemap-download" },
68
- { "License", "sitemap-license" },
69
- { "Contact", "sitemap-contact" },
70
- };
71
-#endif
72
-
7361
login_check_credentials();
7462
if( P("popup")!=0 ){
7563
/* The "popup" query parameter
7664
** then disable anti-robot defenses */
7765
isPopup = 1;
@@ -87,26 +75,10 @@
8775
@ <ul id="sitemap" class="columns" style="column-width:20em">
8876
if( (e&1)==0 ){
8977
@ <li>%z(href("%R/home"))Home Page</a>
9078
}
9179
92
-#if 0 /* Removed 2021-01-26 */
93
- for(i=0; i<sizeof(aExtra)/sizeof(aExtra[0]); i++){
94
- char *z = db_get(aExtra[i].zProperty,0);
95
- if( z==0 || z[0]==0 ) continue;
96
- if( !inSublist ){
97
- @ <ul>
98
- inSublist = 1;
99
- }
100
- if( z[0]=='/' ){
101
- @ <li>%z(href("%R%s",z))%s(aExtra[i].zTitle)</a></li>
102
- }else{
103
- @ <li>%z(href("%s",z))%s(aExtra[i].zTitle)</a></li>
104
- }
105
- }
106
-#endif
107
-
10880
zExtra = db_get("sitemap-extra",0);
10981
if( zExtra && (e&2)==0 ){
11082
int rc;
11183
char **azExtra = 0;
11284
int *anExtra;
@@ -139,26 +111,18 @@
139111
}
140112
Th_Free(g.interp, azExtra);
141113
}
142114
if( (e&1)!=0 ) goto end_of_sitemap;
143115
144
-#if 0 /* Removed on 2021-02-11. Make a sitemap-extra entry if you */
145
- /* really want this */
146
- if( srchFlags & SRCH_DOC ){
147
- if( !inSublist ){
148
- @ <ul>
149
- inSublist = 1;
150
- }
151
- @ <li>%z(href("%R/docsrch"))Documentation Search</a></li>
152
- }
153
-#endif
154
-
155116
if( inSublist ){
156117
@ </ul>
157118
inSublist = 0;
158119
}
159120
@ </li>
121
+ if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){
122
+ @ <li>%z(href("%R/ckout"))Checkout Status</a></li>
123
+ }
160124
if( g.perm.Read ){
161125
const char *zEditGlob = db_get("fileedit-glob","");
162126
@ <li>%z(href("%R/tree"))File Browser</a>
163127
@ <ul>
164128
@ <li>%z(href("%R/tree?type=tree&ci=trunk"))Tree-view,
@@ -192,11 +156,12 @@
192156
}
193157
if( g.perm.Chat ){
194158
@ <li>%z(href("%R/chat"))Chat</a></li>
195159
}
196160
if( g.perm.RdForum ){
197
- @ <li>%z(href("%R/forum"))Forum</a>
161
+ const char *zTitle = db_get("forum-title","Forum");
162
+ @ <li>%z(href("%R/forum"))%h(zTitle)</a>
198163
@ <ul>
199164
@ <li>%z(href("%R/timeline?y=f"))Recent activity</a></li>
200165
@ </ul>
201166
@ </li>
202167
}
203168
--- src/sitemap.c
+++ src/sitemap.c
@@ -56,22 +56,10 @@
56 int i;
57 int isPopup = 0; /* This is an XMLHttpRequest() for /sitemap */
58 int e = atoi(PD("e","0"));
59 const char *zExtra;
60
61 #if 0 /* Removed 2021-01-26 */
62 const struct {
63 const char *zTitle;
64 const char *zProperty;
65 } aExtra[] = {
66 { "Documentation", "sitemap-docidx" },
67 { "Download", "sitemap-download" },
68 { "License", "sitemap-license" },
69 { "Contact", "sitemap-contact" },
70 };
71 #endif
72
73 login_check_credentials();
74 if( P("popup")!=0 ){
75 /* The "popup" query parameter
76 ** then disable anti-robot defenses */
77 isPopup = 1;
@@ -87,26 +75,10 @@
87 @ <ul id="sitemap" class="columns" style="column-width:20em">
88 if( (e&1)==0 ){
89 @ <li>%z(href("%R/home"))Home Page</a>
90 }
91
92 #if 0 /* Removed 2021-01-26 */
93 for(i=0; i<sizeof(aExtra)/sizeof(aExtra[0]); i++){
94 char *z = db_get(aExtra[i].zProperty,0);
95 if( z==0 || z[0]==0 ) continue;
96 if( !inSublist ){
97 @ <ul>
98 inSublist = 1;
99 }
100 if( z[0]=='/' ){
101 @ <li>%z(href("%R%s",z))%s(aExtra[i].zTitle)</a></li>
102 }else{
103 @ <li>%z(href("%s",z))%s(aExtra[i].zTitle)</a></li>
104 }
105 }
106 #endif
107
108 zExtra = db_get("sitemap-extra",0);
109 if( zExtra && (e&2)==0 ){
110 int rc;
111 char **azExtra = 0;
112 int *anExtra;
@@ -139,26 +111,18 @@
139 }
140 Th_Free(g.interp, azExtra);
141 }
142 if( (e&1)!=0 ) goto end_of_sitemap;
143
144 #if 0 /* Removed on 2021-02-11. Make a sitemap-extra entry if you */
145 /* really want this */
146 if( srchFlags & SRCH_DOC ){
147 if( !inSublist ){
148 @ <ul>
149 inSublist = 1;
150 }
151 @ <li>%z(href("%R/docsrch"))Documentation Search</a></li>
152 }
153 #endif
154
155 if( inSublist ){
156 @ </ul>
157 inSublist = 0;
158 }
159 @ </li>
 
 
 
160 if( g.perm.Read ){
161 const char *zEditGlob = db_get("fileedit-glob","");
162 @ <li>%z(href("%R/tree"))File Browser</a>
163 @ <ul>
164 @ <li>%z(href("%R/tree?type=tree&ci=trunk"))Tree-view,
@@ -192,11 +156,12 @@
192 }
193 if( g.perm.Chat ){
194 @ <li>%z(href("%R/chat"))Chat</a></li>
195 }
196 if( g.perm.RdForum ){
197 @ <li>%z(href("%R/forum"))Forum</a>
 
198 @ <ul>
199 @ <li>%z(href("%R/timeline?y=f"))Recent activity</a></li>
200 @ </ul>
201 @ </li>
202 }
203
--- src/sitemap.c
+++ src/sitemap.c
@@ -56,22 +56,10 @@
56 int i;
57 int isPopup = 0; /* This is an XMLHttpRequest() for /sitemap */
58 int e = atoi(PD("e","0"));
59 const char *zExtra;
60
 
 
 
 
 
 
 
 
 
 
 
 
61 login_check_credentials();
62 if( P("popup")!=0 ){
63 /* The "popup" query parameter
64 ** then disable anti-robot defenses */
65 isPopup = 1;
@@ -87,26 +75,10 @@
75 @ <ul id="sitemap" class="columns" style="column-width:20em">
76 if( (e&1)==0 ){
77 @ <li>%z(href("%R/home"))Home Page</a>
78 }
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80 zExtra = db_get("sitemap-extra",0);
81 if( zExtra && (e&2)==0 ){
82 int rc;
83 char **azExtra = 0;
84 int *anExtra;
@@ -139,26 +111,18 @@
111 }
112 Th_Free(g.interp, azExtra);
113 }
114 if( (e&1)!=0 ) goto end_of_sitemap;
115
 
 
 
 
 
 
 
 
 
 
 
116 if( inSublist ){
117 @ </ul>
118 inSublist = 0;
119 }
120 @ </li>
121 if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){
122 @ <li>%z(href("%R/ckout"))Checkout Status</a></li>
123 }
124 if( g.perm.Read ){
125 const char *zEditGlob = db_get("fileedit-glob","");
126 @ <li>%z(href("%R/tree"))File Browser</a>
127 @ <ul>
128 @ <li>%z(href("%R/tree?type=tree&ci=trunk"))Tree-view,
@@ -192,11 +156,12 @@
156 }
157 if( g.perm.Chat ){
158 @ <li>%z(href("%R/chat"))Chat</a></li>
159 }
160 if( g.perm.RdForum ){
161 const char *zTitle = db_get("forum-title","Forum");
162 @ <li>%z(href("%R/forum"))%h(zTitle)</a>
163 @ <ul>
164 @ <li>%z(href("%R/timeline?y=f"))Recent activity</a></li>
165 @ </ul>
166 @ </li>
167 }
168
+4 -4
--- src/statrep.c
+++ src/statrep.c
@@ -290,12 +290,12 @@
290290
nEventsPerYear += nCount;
291291
@<tr class='row%d(rowClass)'>
292292
@ <td>
293293
if(includeMonth){
294294
cgi_printf("<a href='%R/timeline?"
295
- "ym=%t&n=%d&y=%s",
296
- zTimeframe, nCount,
295
+ "ym=%t&y=%s",
296
+ zTimeframe,
297297
statsReportTimelineYFlag );
298298
/* Reminder: n=nCount is not actually correct for bymonth unless
299299
that was the only user who caused events.
300300
*/
301301
if( zUserName ){
@@ -731,12 +731,12 @@
731731
? (int)(100 * nCount / nMaxEvents)
732732
: 0;
733733
if(!nSize) nSize = 1;
734734
total += nCount;
735735
cgi_printf("<tr class='row%d'>", ++rowCount % 2 );
736
- cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s",
737
- zYear, zWeek, nCount,
736
+ cgi_printf("<td><a href='%R/timeline?yw=%t%s&y=%s",
737
+ zYear, zWeek,
738738
statsReportTimelineYFlag);
739739
if( zUserName ){
740740
cgi_printf("&u=%t",zUserName);
741741
}
742742
cgi_printf("'>%s</a></td>",zWeek);
743743
--- src/statrep.c
+++ src/statrep.c
@@ -290,12 +290,12 @@
290 nEventsPerYear += nCount;
291 @<tr class='row%d(rowClass)'>
292 @ <td>
293 if(includeMonth){
294 cgi_printf("<a href='%R/timeline?"
295 "ym=%t&n=%d&y=%s",
296 zTimeframe, nCount,
297 statsReportTimelineYFlag );
298 /* Reminder: n=nCount is not actually correct for bymonth unless
299 that was the only user who caused events.
300 */
301 if( zUserName ){
@@ -731,12 +731,12 @@
731 ? (int)(100 * nCount / nMaxEvents)
732 : 0;
733 if(!nSize) nSize = 1;
734 total += nCount;
735 cgi_printf("<tr class='row%d'>", ++rowCount % 2 );
736 cgi_printf("<td><a href='%R/timeline?yw=%t-%s&n=%d&y=%s",
737 zYear, zWeek, nCount,
738 statsReportTimelineYFlag);
739 if( zUserName ){
740 cgi_printf("&u=%t",zUserName);
741 }
742 cgi_printf("'>%s</a></td>",zWeek);
743
--- src/statrep.c
+++ src/statrep.c
@@ -290,12 +290,12 @@
290 nEventsPerYear += nCount;
291 @<tr class='row%d(rowClass)'>
292 @ <td>
293 if(includeMonth){
294 cgi_printf("<a href='%R/timeline?"
295 "ym=%t&y=%s",
296 zTimeframe,
297 statsReportTimelineYFlag );
298 /* Reminder: n=nCount is not actually correct for bymonth unless
299 that was the only user who caused events.
300 */
301 if( zUserName ){
@@ -731,12 +731,12 @@
731 ? (int)(100 * nCount / nMaxEvents)
732 : 0;
733 if(!nSize) nSize = 1;
734 total += nCount;
735 cgi_printf("<tr class='row%d'>", ++rowCount % 2 );
736 cgi_printf("<td><a href='%R/timeline?yw=%t%s&y=%s",
737 zYear, zWeek,
738 statsReportTimelineYFlag);
739 if( zUserName ){
740 cgi_printf("&u=%t",zUserName);
741 }
742 cgi_printf("'>%s</a></td>",zWeek);
743
--- src/style.c
+++ src/style.c
@@ -821,10 +821,11 @@
821821
if( g.perm.Debug && P("showqp") ){
822822
@ <div class="debug">
823823
cgi_print_all(0, 0, 0);
824824
@ </div>
825825
}
826
+ fossil_free(zTitle);
826827
}
827828
828829
#if INTERFACE
829830
/* Allowed parameters for style_adunit() */
830831
#define ADUNIT_OFF 0x0001 /* Do not allow ads on this page */
@@ -1300,10 +1301,11 @@
13001301
Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
13011302
Th_Store("home", g.zTop);
13021303
image_url_var("logo");
13031304
image_url_var("background");
13041305
Th_Render(blob_str(&css));
1306
+ blob_reset(&css);
13051307
13061308
/* Tell CGI that the content returned by this page is considered cacheable */
13071309
g.isConst = 1;
13081310
}
13091311
13101312
--- src/style.c
+++ src/style.c
@@ -821,10 +821,11 @@
821 if( g.perm.Debug && P("showqp") ){
822 @ <div class="debug">
823 cgi_print_all(0, 0, 0);
824 @ </div>
825 }
 
826 }
827
828 #if INTERFACE
829 /* Allowed parameters for style_adunit() */
830 #define ADUNIT_OFF 0x0001 /* Do not allow ads on this page */
@@ -1300,10 +1301,11 @@
1300 Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
1301 Th_Store("home", g.zTop);
1302 image_url_var("logo");
1303 image_url_var("background");
1304 Th_Render(blob_str(&css));
 
1305
1306 /* Tell CGI that the content returned by this page is considered cacheable */
1307 g.isConst = 1;
1308 }
1309
1310
--- src/style.c
+++ src/style.c
@@ -821,10 +821,11 @@
821 if( g.perm.Debug && P("showqp") ){
822 @ <div class="debug">
823 cgi_print_all(0, 0, 0);
824 @ </div>
825 }
826 fossil_free(zTitle);
827 }
828
829 #if INTERFACE
830 /* Allowed parameters for style_adunit() */
831 #define ADUNIT_OFF 0x0001 /* Do not allow ads on this page */
@@ -1300,10 +1301,11 @@
1301 Th_Store("secureurl", fossil_wants_https(1)? g.zHttpsURL: g.zBaseURL);
1302 Th_Store("home", g.zTop);
1303 image_url_var("logo");
1304 image_url_var("background");
1305 Th_Render(blob_str(&css));
1306 blob_reset(&css);
1307
1308 /* Tell CGI that the content returned by this page is considered cacheable */
1309 g.isConst = 1;
1310 }
1311
1312
+474 -462
--- src/timeline.c
+++ src/timeline.c
@@ -191,11 +191,11 @@
191191
void www_print_timeline(
192192
Stmt *pQuery, /* Query to implement the timeline */
193193
int tmFlags, /* Flags controlling display behavior */
194194
const char *zThisUser, /* Suppress links to this user */
195195
const char *zThisTag, /* Suppress links to this tag */
196
- const char *zLeftBranch, /* Strive to put this branch on the left margin */
196
+ Matcher *pLeftBranch, /* Comparison function to use for zLeftBranch */
197197
int selectedRid, /* Highlight the line with this RID value or zero */
198198
int secondRid, /* Secondary highlight (or zero) */
199199
void (*xExtra)(int) /* Routine to call on each line of display */
200200
){
201201
int mxWikiLen;
@@ -813,11 +813,11 @@
813813
}
814814
if( pendingEndTr ){
815815
@ </td></tr>
816816
}
817817
if( pGraph ){
818
- graph_finish(pGraph, zLeftBranch, tmFlags);
818
+ graph_finish(pGraph, pLeftBranch, tmFlags);
819819
if( pGraph->nErr ){
820820
graph_free(pGraph);
821821
pGraph = 0;
822822
}else{
823823
@ <tr class="timelineBottom" id="btm-%d(iTableId)">\
@@ -1290,12 +1290,13 @@
12901290
const char *zChng, /* The filename GLOB list */
12911291
Blob *pSql /* The SELECT statement under construction */
12921292
){
12931293
if( zChng==0 || zChng[0]==0 ) return;
12941294
blob_append_sql(pSql," AND event.objid IN ("
1295
- "SELECT mlink.mid FROM mlink, filename"
1296
- " WHERE mlink.fnid=filename.fnid AND %s)",
1295
+ "SELECT mlink.mid FROM mlink, filename\n"
1296
+ " WHERE mlink.fnid=filename.fnid\n"
1297
+ " AND %s)",
12971298
glob_expr("filename.name", mprintf("\"%s\"", zChng)));
12981299
}
12991300
static void addFileGlobDescription(
13001301
const char *zChng, /* The filename GLOB list */
13011302
Blob *pDescription /* Result description */
@@ -1303,274 +1304,86 @@
13031304
if( zChng==0 || zChng[0]==0 ) return;
13041305
blob_appendf(pDescription, " that include changes to files matching '%h'",
13051306
zChng);
13061307
}
13071308
1308
-/*
1309
-** Tag match expression type code.
1310
-*/
1311
-typedef enum {
1312
- MS_EXACT, /* Matches a single tag by exact string comparison. */
1313
- MS_GLOB, /* Matches tags against a list of GLOB patterns. */
1314
- MS_LIKE, /* Matches tags against a list of LIKE patterns. */
1315
- MS_REGEXP, /* Matches tags against a list of regular expressions. */
1316
- MS_BRLIST, /* Same as REGEXP, except the regular expression is a list
1317
- ** of branch names */
1318
-} MatchStyle;
1319
-
1320
-/*
1321
-** Quote a tag string by surrounding it with double quotes and preceding
1322
-** internal double quotes and backslashes with backslashes.
1323
-*/
1324
-static const char *tagQuote(
1325
- int len, /* Maximum length of zTag, or negative for unlimited */
1326
- const char *zTag /* Tag string */
1327
-){
1328
- Blob blob = BLOB_INITIALIZER;
1329
- int i, j;
1330
- blob_zero(&blob);
1331
- blob_append(&blob, "\"", 1);
1332
- for( i=j=0; zTag[j] && (len<0 || j<len); ++j ){
1333
- if( zTag[j]=='\"' || zTag[j]=='\\' ){
1334
- if( j>i ){
1335
- blob_append(&blob, zTag+i, j-i);
1336
- }
1337
- blob_append(&blob, "\\", 1);
1338
- i = j;
1339
- }
1340
- }
1341
- if( j>i ){
1342
- blob_append(&blob, zTag+i, j-i);
1343
- }
1344
- blob_append(&blob, "\"", 1);
1345
- return blob_str(&blob);
1346
-}
1347
-
1348
-/*
1349
-** Construct the tag match SQL expression.
1350
-**
1351
-** This function is adapted from glob_expr() to support the MS_EXACT, MS_GLOB,
1352
-** MS_LIKE, MS_REGEXP, and MS_BRLIST match styles.
1353
-**
1354
-** For MS_EXACT, the returned expression
1355
-** checks for integer match against the tag ID which is looked up directly by
1356
-** this function. For the other modes, the returned SQL expression performs
1357
-** string comparisons against the tag names, so it is necessary to join against
1358
-** the tag table to access the "tagname" column.
1359
-**
1360
-** Each pattern is adjusted to to start with "sym-" and be anchored at end.
1361
-**
1362
-** In MS_REGEXP mode, backslash can be used to protect delimiter characters.
1363
-** The backslashes are not removed from the regular expression.
1364
-**
1365
-** In addition to assembling and returning an SQL expression, this function
1366
-** makes an English-language description of the patterns being matched, suitable
1367
-** for display in the web interface.
1368
-**
1369
-** If any errors arise during processing, *zError is set to an error message.
1370
-** Otherwise it is set to NULL.
1371
-*/
1372
-static const char *tagMatchExpression(
1373
- MatchStyle matchStyle, /* Match style code */
1374
- const char *zTag, /* Tag name, match pattern, or pattern list */
1375
- const char **zDesc, /* Output expression description string */
1376
- const char **zError /* Output error string */
1377
-){
1378
- Blob expr = BLOB_INITIALIZER; /* SQL expression string assembly buffer */
1379
- Blob desc = BLOB_INITIALIZER; /* English description of match patterns */
1380
- Blob err = BLOB_INITIALIZER; /* Error text assembly buffer */
1381
- const char *zStart; /* Text at start of expression */
1382
- const char *zDelimiter; /* Text between expression terms */
1383
- const char *zEnd; /* Text at end of expression */
1384
- const char *zPrefix; /* Text before each match pattern */
1385
- const char *zSuffix; /* Text after each match pattern */
1386
- const char *zIntro; /* Text introducing pattern description */
1387
- const char *zPattern = 0; /* Previous quoted pattern */
1388
- const char *zFail = 0; /* Current failure message or NULL if okay */
1389
- const char *zOr = " or "; /* Text before final quoted pattern */
1390
- char cDel; /* Input delimiter character */
1391
- int i; /* Input match pattern length counter */
1392
-
1393
- /* Optimize exact matches by looking up the ID in advance to create a simple
1394
- * numeric comparison. Bypass the remainder of this function. */
1395
- if( matchStyle==MS_EXACT ){
1396
- *zDesc = tagQuote(-1, zTag);
1397
- return mprintf("(tagid=%d)", db_int(-1,
1398
- "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag));
1399
- }
1400
-
1401
- /* Decide pattern prefix and suffix strings according to match style. */
1402
- if( matchStyle==MS_GLOB ){
1403
- zStart = "(";
1404
- zDelimiter = " OR ";
1405
- zEnd = ")";
1406
- zPrefix = "tagname GLOB 'sym-";
1407
- zSuffix = "'";
1408
- zIntro = "glob pattern ";
1409
- }else if( matchStyle==MS_LIKE ){
1410
- zStart = "(";
1411
- zDelimiter = " OR ";
1412
- zEnd = ")";
1413
- zPrefix = "tagname LIKE 'sym-";
1414
- zSuffix = "'";
1415
- zIntro = "SQL LIKE pattern ";
1416
- }else if( matchStyle==MS_REGEXP ){
1417
- zStart = "(tagname REGEXP '^sym-(";
1418
- zDelimiter = "|";
1419
- zEnd = ")$')";
1420
- zPrefix = "";
1421
- zSuffix = "";
1422
- zIntro = "regular expression ";
1423
- }else/* if( matchStyle==MS_BRLIST )*/{
1424
- zStart = "tagname IN ('sym-";
1425
- zDelimiter = "','sym-";
1426
- zEnd = "')";
1427
- zPrefix = "";
1428
- zSuffix = "";
1429
- zIntro = "";
1430
- }
1431
-
1432
- /* Convert the list of matches into an SQL expression and text description. */
1433
- blob_zero(&expr);
1434
- blob_zero(&desc);
1435
- blob_zero(&err);
1436
- while( 1 ){
1437
- /* Skip leading delimiters. */
1438
- for( ; fossil_isspace(*zTag) || *zTag==','; ++zTag );
1439
-
1440
- /* Next non-delimiter character determines quoting. */
1441
- if( !*zTag ){
1442
- /* Terminate loop at end of string. */
1443
- break;
1444
- }else if( *zTag=='\'' || *zTag=='"' ){
1445
- /* If word is quoted, prepare to stop at end quote. */
1446
- cDel = *zTag;
1447
- ++zTag;
1448
- }else{
1449
- /* If word is not quoted, prepare to stop at delimiter. */
1450
- cDel = ',';
1451
- }
1452
-
1453
- /* Find the next delimiter character or end of string. */
1454
- for( i=0; zTag[i] && zTag[i]!=cDel; ++i ){
1455
- /* If delimiter is comma, also recognize spaces as delimiters. */
1456
- if( cDel==',' && fossil_isspace(zTag[i]) ){
1457
- break;
1458
- }
1459
-
1460
- /* In regexp mode, ignore delimiters following backslashes. */
1461
- if( matchStyle==MS_REGEXP && zTag[i]=='\\' && zTag[i+1] ){
1462
- ++i;
1463
- }
1464
- }
1465
-
1466
- /* Check for regular expression syntax errors. */
1467
- if( matchStyle==MS_REGEXP ){
1468
- ReCompiled *regexp;
1469
- char *zTagDup = fossil_strndup(zTag, i);
1470
- zFail = re_compile(&regexp, zTagDup, 0);
1471
- re_free(regexp);
1472
- fossil_free(zTagDup);
1473
- }
1474
-
1475
- /* Process success and error results. */
1476
- if( !zFail ){
1477
- /* Incorporate the match word into the output expression. %q is used to
1478
- * protect against SQL injection attacks by replacing ' with ''. */
1479
- blob_appendf(&expr, "%s%s%#q%s", blob_size(&expr) ? zDelimiter : zStart,
1480
- zPrefix, i, zTag, zSuffix);
1481
-
1482
- /* Build up the description string. */
1483
- if( !blob_size(&desc) ){
1484
- /* First tag: start with intro followed by first quoted tag. */
1485
- blob_append(&desc, zIntro, -1);
1486
- blob_append(&desc, tagQuote(i, zTag), -1);
1487
- }else{
1488
- if( zPattern ){
1489
- /* Third and subsequent tags: append comma then previous tag. */
1490
- blob_append(&desc, ", ", 2);
1491
- blob_append(&desc, zPattern, -1);
1492
- zOr = ", or ";
1493
- }
1494
-
1495
- /* Second and subsequent tags: store quoted tag for next iteration. */
1496
- zPattern = tagQuote(i, zTag);
1497
- }
1498
- }else{
1499
- /* On error, skip the match word and build up the error message buffer. */
1500
- if( !blob_size(&err) ){
1501
- blob_append(&err, "Error: ", 7);
1502
- }else{
1503
- blob_append(&err, ", ", 2);
1504
- }
1505
- blob_appendf(&err, "(%s%s: %s)", zIntro, tagQuote(i, zTag), zFail);
1506
- }
1507
-
1508
- /* Advance past all consumed input characters. */
1509
- zTag += i;
1510
- if( cDel!=',' && *zTag==cDel ){
1511
- ++zTag;
1512
- }
1513
- }
1514
-
1515
- /* Finalize and extract the pattern description. */
1516
- if( zPattern ){
1517
- blob_append(&desc, zOr, -1);
1518
- blob_append(&desc, zPattern, -1);
1519
- }
1520
- *zDesc = blob_str(&desc);
1521
-
1522
- /* Finalize and extract the error text. */
1523
- *zError = blob_size(&err) ? blob_str(&err) : 0;
1524
-
1525
- /* Finalize and extract the SQL expression. */
1526
- if( blob_size(&expr) ){
1527
- blob_append(&expr, zEnd, -1);
1528
- return blob_str(&expr);
1529
- }
1530
-
1531
- /* If execution reaches this point, the pattern was empty. Return NULL. */
1532
- return 0;
1533
-}
1534
-
15351309
/*
15361310
** Similar to fossil_expand_datetime()
15371311
**
15381312
** Add missing "-" characters into a date/time. Examples:
15391313
**
15401314
** 20190419 => 2019-04-19
15411315
** 201904 => 2019-04
15421316
*/
1543
-const char *timeline_expand_datetime(const char *zIn){
1544
- static char zEDate[20];
1545
- static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' };
1317
+const char *timeline_expand_datetime(const char *zIn, int *pbZulu){
1318
+ static char zEDate[16];
15461319
int n = (int)strlen(zIn);
15471320
int i, j;
15481321
1549
- /* Only three forms allowed:
1322
+ /* These forms are recognized:
1323
+ **
15501324
** (1) YYYYMMDD
15511325
** (2) YYYYMM
15521326
** (3) YYYYWW
15531327
*/
1328
+ if( n && (zIn[n-1]=='Z' || zIn[n-1]=='z') ){
1329
+ n--;
1330
+ if( pbZulu ) *pbZulu = 1;
1331
+ }else{
1332
+ if( pbZulu ) *pbZulu = 0;
1333
+ }
15541334
if( n!=8 && n!=6 ) return zIn;
15551335
15561336
/* Every character must be a digit */
1557
- for(i=0; fossil_isdigit(zIn[i]); i++){}
1337
+ for(i=0; i<n && fossil_isdigit(zIn[i]); i++){}
15581338
if( i!=n ) return zIn;
15591339
15601340
/* Expand the date */
1561
- for(i=j=0; zIn[i]; i++){
1562
- if( i>=4 && (i%2)==0 ){
1563
- zEDate[j++] = aPunct[i/2];
1564
- }
1341
+ for(i=j=0; i<n; i++){
1342
+ if( j==4 || j==7 ) zEDate[j++] = '-';
15651343
zEDate[j++] = zIn[i];
15661344
}
15671345
zEDate[j] = 0;
15681346
15691347
/* It looks like this may be a date. Return it with punctuation added. */
15701348
return zEDate;
15711349
}
1350
+
1351
+/*
1352
+** Check to see if the argument is a date-span for the ymd= query
1353
+** parameter. A valid date-span is of the form:
1354
+**
1355
+** 0123456789 123456 <-- index
1356
+** YYYYMMDD-YYYYMMDD
1357
+**
1358
+** with an optional "Z" timeline modifier at the end. Return true if
1359
+** the input is a valid date space and false if not.
1360
+*/
1361
+static int timeline_is_datespan(const char *zDay){
1362
+ size_t n = strlen(zDay);
1363
+ int i, d, m;
1364
+
1365
+ if( n<17 || n>18 ) return 0;
1366
+ if( n==18 ){
1367
+ if( zDay[17]!='Z' && zDay[17]!='z' ) return 0;
1368
+ n--;
1369
+ }
1370
+ if( zDay[8]!='-' ) return 0;
1371
+ for(i=0; i<17 && (fossil_isdigit(zDay[i]) || i==8); i++){}
1372
+ if( i!=17 ) return 0;
1373
+ i = atoi(zDay);
1374
+ d = i%100;
1375
+ if( d<1 || d>31 ) return 0;
1376
+ m = (i/100)%100;
1377
+ if( m<1 || m>12 ) return 0;
1378
+ i = atoi(zDay+9);
1379
+ d = i%100;
1380
+ if( d<1 || d>31 ) return 0;
1381
+ m = (i/100)%100;
1382
+ if( m<1 || m>12 ) return 0;
1383
+ return 1;
1384
+}
15721385
15731386
/*
15741387
** Find the first check-in encountered with a particular tag
15751388
** when moving either forwards are backwards in time from a
15761389
** particular starting point (iFrom). Return the rid of that
@@ -1591,10 +1404,11 @@
15911404
tagId = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zEnd);
15921405
if( tagId==0 ){
15931406
endId = symbolic_name_to_rid(zEnd, "ci");
15941407
if( endId==0 ) return 0;
15951408
}
1409
+ db_pause_dml_log();
15961410
if( bForward ){
15971411
if( tagId ){
15981412
db_prepare(&q,
15991413
"WITH RECURSIVE dx(id,mtime) AS ("
16001414
" SELECT %d, event.mtime FROM event WHERE objid=%d"
@@ -1658,12 +1472,54 @@
16581472
}
16591473
if( db_step(&q)==SQLITE_ROW ){
16601474
ans = db_column_int(&q, 0);
16611475
}
16621476
db_finalize(&q);
1477
+ db_unpause_dml_log();
16631478
return ans;
16641479
}
1480
+
1481
+/*
1482
+** Add to the (temp) table zTab, RID values for every check-in
1483
+** identifier found on the zExtra string. Check-in names can be separated
1484
+** by commas or by whitespace.
1485
+*/
1486
+static void add_extra_rids(const char *zTab, const char *zExtra){
1487
+ int ii;
1488
+ int rid;
1489
+ int cnt;
1490
+ Blob sql;
1491
+ char *zX;
1492
+ char *zToDel;
1493
+ if( zExtra==0 ) return;
1494
+ cnt = 0;
1495
+ blob_init(&sql, 0, 0);
1496
+ zX = zToDel = fossil_strdup(zExtra);
1497
+ blob_append_sql(&sql, "INSERT OR IGNORE INTO \"%w\" VALUES", zTab);
1498
+ while( zX[0] ){
1499
+ char c;
1500
+ if( zX[0]==',' || zX[0]==' ' ){ zX++; continue; }
1501
+ for(ii=1; zX[ii] && zX[ii]!=',' && zX[ii]!=' '; ii++){}
1502
+ c = zX[ii];
1503
+ zX[ii] = 0;
1504
+ rid = name_to_rid(zX);
1505
+ if( rid>0 ){
1506
+ if( (cnt%10)==4 ){
1507
+ blob_append_sql(&sql,",\n ");
1508
+ }else if( cnt>0 ){
1509
+ blob_append_sql(&sql,",");
1510
+ }
1511
+ blob_append_sql(&sql, "(%d)", rid);
1512
+ cnt++;
1513
+ }
1514
+ zX[ii] = c;
1515
+ zX += ii;
1516
+ }
1517
+ if( cnt ) db_exec_sql(blob_sql_text(&sql));
1518
+ blob_reset(&sql);
1519
+ fossil_free(zToDel);
1520
+}
16651521
16661522
/*
16671523
** COMMAND: test-endpoint
16681524
**
16691525
** Usage: fossil test-endpoint BASE TAG ?OPTIONS?
@@ -1698,21 +1554,21 @@
16981554
/*
16991555
** WEBPAGE: timeline
17001556
**
17011557
** Query parameters:
17021558
**
1703
-** a=TIMEORTAG Show events after TIMEORTAG
1704
-** b=TIMEORTAG Show events before TIMEORTAG
1559
+** a=TIMEORTAG Show events after TIMEORTAG.
1560
+** b=TIMEORTAG Show events before TIMEORTAG.
17051561
** c=TIMEORTAG Show events that happen "circa" TIMEORTAG
17061562
** cf=FILEHASH Show events around the time of the first use of
1707
-** the file with FILEHASH
1563
+** the file with FILEHASH.
17081564
** m=TIMEORTAG Highlight the event at TIMEORTAG, or the closest available
17091565
** event if TIMEORTAG is not part of the timeline. If
17101566
** the t= or r= is used, the m event is added to the timeline
17111567
** if it isn't there already.
1712
-** x=HASHLIST Show all check-ins in the comma-separated HASHLIST
1713
-** in addition to check-ins specified by t= or r=
1568
+** x=LIST Show check-ins in the comma- or space-separated LIST
1569
+** in addition to check-ins specified by other parameters.
17141570
** sel1=TIMEORTAG Highlight the check-in at TIMEORTAG if it is part of
17151571
** the timeline. Similar to m= except TIMEORTAG must
17161572
** match a check-in that is already in the timeline.
17171573
** sel2=TIMEORTAG Like sel1= but use the secondary highlight.
17181574
** n=COUNT Maximum number of events. "all" for no limit
@@ -1732,63 +1588,71 @@
17321588
** from=CX ... shortest path from CX back to CHECKIN
17331589
** ft=CHECKIN "Forward To": Show decendents forward to CHECKIN
17341590
** d=CX ... from CX up to the time of CHECKIN
17351591
** from=CX ... shortest path from CX up to CHECKIN
17361592
** t=TAG Show only check-ins with the given TAG
1737
-** r=TAG Show check-ins related to TAG, equivalent to t=TAG&rel
1738
-** tl=TAGLIST Shorthand for t=TAGLIST&ms=brlist
1739
-** rl=TAGLIST Shorthand for r=TAGLIST&ms=brlist
1593
+** r=TAG Same as 't=TAG&rel'. Mnemonic: "Related"
1594
+** tl=TAGLIST Same as 't=TAGLIST&ms=brlist'. Mnemonic: "Tag List"
1595
+** rl=TAGLIST Same as 'r=TAGLIST&ms=brlist'. Mnemonic: "Related List"
1596
+** ml=TAGLIST Same as 'tl=TAGLIST&mionly'. Mnemonic: "Merge-in List"
1597
+** sl=TAGLIST "Sort List". Draw TAGLIST branches ordered left to right.
17401598
** rel Show related check-ins as well as those matching t=TAG
1741
-** mionly Limit rel to show ancestors but not descendants
1599
+** mionly Show related parents but not related children.
17421600
** nowiki Do not show wiki associated with branch or tag
1743
-** ms=MATCHSTYLE Set tag match style to EXACT, GLOB, LIKE, REGEXP
1601
+** ms=MATCHSTYLE Set tag name match algorithm. One of "exact", "glob",
1602
+** "like", or "regexp".
17441603
** u=USER Only show items associated with USER
17451604
** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', or 'all'.
17461605
** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar",
1747
-** x: "Classic".
1606
+* x: "Classic".
17481607
** advm Use the "Advanced" or "Busy" menu design.
17491608
** ng No Graph.
17501609
** ncp Omit cherrypick merges
17511610
** nd Do not highlight the focus check-in
17521611
** nsm Omit the submenu
17531612
** nc Omit all graph colors other than highlights
17541613
** v Show details of files changed
17551614
** vfx Show complete text of forum messages
1756
-** f=CHECKIN Show family (immediate parents and children) of CHECKIN
1757
-** from=CHECKIN Path from...
1615
+** f=CHECKIN Family (immediate parents and children) of CHECKIN
1616
+** from=CHECKIN Path through common ancestor from...
17581617
** to=CHECKIN ... to this
17591618
** to2=CHECKIN ... backup name if to= doesn't resolve
17601619
** shortest ... show only the shortest path
17611620
** rel ... also show related checkins
17621621
** bt=PRIOR ... path from CHECKIN back to PRIOR
17631622
** ft=LATER ... path from CHECKIN forward to LATER
1623
+** me=CHECKIN Most direct path from...
1624
+** you=CHECKIN ... to this
1625
+** rel ... also show related checkins
17641626
** uf=FILE_HASH Show only check-ins that contain the given file version
1765
-** All qualifying check-ins are shown unless there is
1766
-** also an n= or n1= query parameter.
1627
+** All qualifying check-ins are shown unless there is
1628
+** also an n= or n1= query parameter.
17671629
** chng=GLOBLIST Show only check-ins that involve changes to a file whose
1768
-** name matches one of the comma-separate GLOBLIST
1630
+** name matches one of the comma-separate GLOBLIST
17691631
** brbg Background color determined by branch name
17701632
** ubg Background color determined by user
17711633
** deltabg Background color red for delta manifests or green
17721634
** for baseline manifests
17731635
** namechng Show only check-ins that have filename changes
17741636
** forks Show only forks and their children
17751637
** cherrypicks Show all cherrypicks
1776
-** ym=YYYY-MM Show only events for the given year/month
1777
-** yw=YYYY-WW Show only events for the given week of the given year
1778
-** yw=YYYY-MM-DD Show events for the week that includes the given day
1779
-** ymd=YYYY-MM-DD Show only events on the given day. The use "ymd=now"
1780
-** to see all changes for the current week.
1638
+** ym=YYYYMM Show only events for the given year/month
1639
+** yw=YYYYWW Show only events for the given week of the given year
1640
+** yw=YYYYMMDD Show events for the week that includes the given day
1641
+** ymd=YYYYMMDD Show only events on the given day. The use "ymd=now"
1642
+** to see all changes for the current week. Add "z" at end
1643
+** to divide days at UTC instead of localtime days.
1644
+** Use ymd=YYYYMMDD-YYYYMMDD (with optional "z") for a range.
17811645
** year=YYYY Show only events on the given year. The use "year=0"
17821646
** to see all changes for the current year.
17831647
** days=N Show events over the previous N days
17841648
** datefmt=N Override the date format: 0=HH:MM, 1=HH:MM:SS,
17851649
** 2=YYYY-MM-DD HH:MM:SS, 3=YYMMDD HH:MM, and 4 means "off".
17861650
** bisect Show the check-ins that are in the current bisect
17871651
** oldestfirst Show events oldest first.
17881652
** showid Show RIDs
1789
-** showsql Show the SQL text
1653
+** showsql Show the SQL used to generate the report
17901654
**
17911655
** p= and d= can appear individually or together. If either p= or d=
17921656
** appear, then u=, y=, a=, and b= are ignored.
17931657
**
17941658
** If both a= and b= appear then both upper and lower bounds are honored.
@@ -1862,13 +1726,20 @@
18621726
int advancedMenu = 0; /* Use the advanced menu design */
18631727
char *zPlural; /* Ending for plural forms */
18641728
int showCherrypicks = 1; /* True to show cherrypick merges */
18651729
int haveParameterN; /* True if n= query parameter present */
18661730
int from_to_mode = 0; /* 0: from,to. 1: from,ft 2: from,bt */
1731
+ int showSql = PB("showsql"); /* True to show the SQL */
1732
+ Blob allSql; /* Copy of all SQL text */
18671733
1734
+ login_check_credentials();
18681735
url_initialize(&url, "timeline");
18691736
cgi_query_parameters_to_url(&url);
1737
+ blob_init(&allSql, 0, 0);
1738
+
1739
+ /* The "mionly" query parameter is like "rel", but shows merge-ins only */
1740
+ if( P("mionly")!=0 ) related = 2;
18701741
18711742
(void)P_NoBot("ss")
18721743
/* "ss" is processed via the udc but at least one spider likes to
18731744
** try to SQL inject via this argument, so let's catch that. */;
18741745
@@ -1943,11 +1814,10 @@
19431814
*/
19441815
pd_rid = name_choice("dp","dp2",&zDPName);
19451816
if( pd_rid ){
19461817
p_rid = d_rid = pd_rid;
19471818
}
1948
- login_check_credentials();
19491819
if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum)
19501820
|| (bisectLocal && !g.perm.Setup)
19511821
){
19521822
login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
19531823
return;
@@ -1984,48 +1854,52 @@
19841854
19851855
/* Check for tl=TAGLIST and rl=TAGLIST which are abbreviations for
19861856
** t=TAGLIST&ms=brlist and r=TAGLIST&ms=brlist repectively. */
19871857
if( zBrName==0 && zTagName==0 ){
19881858
const char *z;
1859
+ const char *zPattern = 0;
19891860
if( (z = P("tl"))!=0 ){
1990
- zTagName = z;
1991
- zMatchStyle = "brlist";
1861
+ zPattern = zTagName = z;
1862
+ }else if( (z = P("rl"))!=0 ){
1863
+ zPattern = zBrName = z;
1864
+ if( related==0 ) related = 1;
1865
+ }else if( (z = P("ml"))!=0 ){
1866
+ zPattern = zBrName = z;
1867
+ if( related==0 ) related = 2;
19921868
}
1993
- if( (z = P("rl"))!=0 ){
1994
- zBrName = z;
1995
- zMatchStyle = "brlist";
1869
+ if( zPattern!=0 && zMatchStyle==0 ){
1870
+ /* If there was no ms= query parameter, set the match style to
1871
+ ** "glob" if the pattern appears to contain GLOB character, or
1872
+ ** "brlist" if it does not. */
1873
+ if( strpbrk(zPattern,"*[?") ){
1874
+ zMatchStyle = "glob";
1875
+ }else{
1876
+ zMatchStyle = "brlist";
1877
+ }
19961878
}
19971879
}
19981880
19991881
/* Convert r=TAG to t=TAG&rel in order to populate the UI style widgets. */
2000
- if( zBrName && !related ){
1882
+ if( zBrName ){
20011883
cgi_delete_query_parameter("r");
20021884
cgi_set_query_parameter("t", zBrName); (void)P("t");
20031885
cgi_set_query_parameter("rel", "1");
20041886
zTagName = zBrName;
2005
- related = 1;
1887
+ if( related==0 ) related = 1;
20061888
zType = "ci";
20071889
}
20081890
20091891
/* Ignore empty tag query strings. */
20101892
if( zTagName && !*zTagName ){
20111893
zTagName = 0;
20121894
}
20131895
20141896
/* Finish preliminary processing of tag match queries. */
1897
+ matchStyle = match_style(zMatchStyle, MS_EXACT);
20151898
if( zTagName ){
20161899
zType = "ci";
2017
- /* Interpet the tag style string. */
2018
- if( fossil_stricmp(zMatchStyle, "glob")==0 ){
2019
- matchStyle = MS_GLOB;
2020
- }else if( fossil_stricmp(zMatchStyle, "like")==0 ){
2021
- matchStyle = MS_LIKE;
2022
- }else if( fossil_stricmp(zMatchStyle, "regexp")==0 ){
2023
- matchStyle = MS_REGEXP;
2024
- }else if( fossil_stricmp(zMatchStyle, "brlist")==0 ){
2025
- matchStyle = MS_BRLIST;
2026
- }else{
1900
+ if( matchStyle==MS_EXACT ){
20271901
/* For exact maching, inhibit links to the selected tag. */
20281902
zThisTag = zTagName;
20291903
Th_Store("current_checkin", zTagName);
20301904
}
20311905
@@ -2033,11 +1907,11 @@
20331907
if( advancedMenu ){
20341908
style_submenu_checkbox("rel", "Related", 0, 0);
20351909
}
20361910
20371911
/* Construct the tag match expression. */
2038
- zTagSql = tagMatchExpression(matchStyle, zTagName, &zMatchDesc, &zError);
1912
+ zTagSql = match_tag_sqlexpr(matchStyle, zTagName, &zMatchDesc, &zError);
20391913
}
20401914
20411915
if( zMark && zMark[0]==0 ){
20421916
if( zAfter ) zMark = zAfter;
20431917
if( zBefore ) zMark = zBefore;
@@ -2081,10 +1955,11 @@
20811955
}
20821956
if( PB("nc") ){
20831957
tmFlags &= ~(TIMELINE_DELTA|TIMELINE_BRCOLOR|TIMELINE_UCOLOR);
20841958
tmFlags |= TIMELINE_NOCOLOR;
20851959
}
1960
+ if( showSql ) db_append_dml_to_blob(&allSql);
20861961
if( zUses!=0 ){
20871962
int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses);
20881963
if( ufid ){
20891964
zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
20901965
db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
@@ -2098,42 +1973,42 @@
20981973
}
20991974
if( renameOnly ){
21001975
db_multi_exec(
21011976
"CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);"
21021977
"INSERT OR IGNORE INTO rnfile"
2103
- " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;"
1978
+ " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;"
21041979
);
21051980
disableY = 1;
21061981
}
21071982
if( forkOnly ){
21081983
db_multi_exec(
21091984
"CREATE TEMP TABLE rnfork(rid INTEGER PRIMARY KEY);\n"
21101985
"INSERT OR IGNORE INTO rnfork(rid)\n"
21111986
" SELECT pid FROM plink\n"
2112
- " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
2113
- " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
2114
- " GROUP BY pid"
1987
+ " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n"
1988
+ " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
1989
+ " GROUP BY pid\n"
21151990
" HAVING count(*)>1;\n"
2116
- "INSERT OR IGNORE INTO rnfork(rid)"
1991
+ "INSERT OR IGNORE INTO rnfork(rid)\n"
21171992
" SELECT cid FROM plink\n"
2118
- " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
2119
- " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
2120
- " GROUP BY cid"
1993
+ " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n"
1994
+ " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
1995
+ " GROUP BY cid\n"
21211996
" HAVING count(*)>1;\n",
21221997
TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
21231998
);
21241999
db_multi_exec(
21252000
"INSERT OR IGNORE INTO rnfork(rid)\n"
21262001
" SELECT cid FROM plink\n"
2127
- " WHERE pid IN rnfork"
2128
- " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
2129
- " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
2130
- " UNION "
2002
+ " WHERE pid IN rnfork\n"
2003
+ " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n"
2004
+ " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
2005
+ " UNION\n"
21312006
" SELECT pid FROM plink\n"
2132
- " WHERE cid IN rnfork"
2133
- " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
2134
- " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n",
2007
+ " WHERE cid IN rnfork\n"
2008
+ " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n"
2009
+ " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n",
21352010
TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
21362011
);
21372012
tmFlags |= TIMELINE_UNHIDE;
21382013
zType = "ci";
21392014
disableY = 1;
@@ -2206,77 +2081,114 @@
22062081
PathNode *p = 0;
22072082
const char *zFrom = 0;
22082083
const char *zTo = 0;
22092084
Blob ins;
22102085
int nNodeOnPath = 0;
2086
+ int commonAncs = 0; /* Common ancestors of me_rid and you_rid. */
2087
+ int earlierRid = 0, laterRid = 0;
22112088
22122089
if( from_rid && to_rid ){
22132090
if( from_to_mode==0 ){
22142091
p = path_shortest(from_rid, to_rid, noMerge, 0, 0);
22152092
}else if( from_to_mode==1 ){
22162093
p = path_shortest(from_rid, to_rid, 0, 1, 0);
2094
+ earlierRid = commonAncs = from_rid;
2095
+ laterRid = to_rid;
22172096
}else{
22182097
p = path_shortest(to_rid, from_rid, 0, 1, 0);
2098
+ earlierRid = commonAncs = to_rid;
2099
+ laterRid = from_rid;
22192100
}
22202101
zFrom = P("from");
22212102
zTo = zTo2 ? zTo2 : P("to");
22222103
}else{
2223
- if( path_common_ancestor(me_rid, you_rid) ){
2104
+ commonAncs = path_common_ancestor(me_rid, you_rid);
2105
+ if( commonAncs!=0 ){
22242106
p = path_first();
22252107
}
2226
- zFrom = P("me");
2227
- zTo = P("you");
2108
+ if( commonAncs==you_rid ){
2109
+ zFrom = P("you");
2110
+ zTo = P("me");
2111
+ earlierRid = you_rid;
2112
+ laterRid = me_rid;
2113
+ }else{
2114
+ zFrom = P("me");
2115
+ zTo = P("you");
2116
+ earlierRid = me_rid;
2117
+ laterRid = you_rid;
2118
+ }
22282119
}
22292120
blob_init(&ins, 0, 0);
22302121
db_multi_exec(
2231
- "CREATE TABLE IF NOT EXISTS temp.pathnode(x INTEGER PRIMARY KEY);"
2122
+ "CREATE TEMP TABLE IF NOT EXISTS pathnode(x INTEGER PRIMARY KEY);"
22322123
);
22332124
if( p ){
2125
+ int cnt = 4;
22342126
blob_init(&ins, 0, 0);
22352127
blob_append_sql(&ins, "INSERT INTO pathnode(x) VALUES(%d)", p->rid);
22362128
p = p->u.pTo;
22372129
while( p ){
2238
- blob_append_sql(&ins, ",(%d)", p->rid);
2130
+ if( cnt==8 ){
2131
+ blob_append_sql(&ins, ",\n (%d)", p->rid);
2132
+ cnt = 0;
2133
+ }else{
2134
+ cnt++;
2135
+ blob_append_sql(&ins, ",(%d)", p->rid);
2136
+ }
22392137
p = p->u.pTo;
22402138
}
22412139
}
22422140
path_reset();
22432141
db_multi_exec("%s", blob_str(&ins)/*safe-for-%s*/);
22442142
blob_reset(&ins);
2245
- if( related || P("mionly") ){
2143
+ if( related ){
22462144
db_multi_exec(
2247
- "CREATE TABLE IF NOT EXISTS temp.related(x INTEGER PRIMARY KEY);"
2145
+ "CREATE TEMP TABLE IF NOT EXISTS related(x INTEGER PRIMARY KEY);"
22482146
"INSERT OR IGNORE INTO related(x)"
2249
- " SELECT pid FROM plink WHERE cid IN pathnode AND NOT isprim;"
2147
+ " SELECT pid FROM plink WHERE cid IN pathnode AND NOT isprim;"
22502148
);
2251
- if( P("mionly")==0 ){
2149
+ if( related==1 ){
22522150
db_multi_exec(
22532151
"INSERT OR IGNORE INTO related(x)"
2254
- " SELECT cid FROM plink WHERE pid IN pathnode;"
2152
+ " SELECT cid FROM plink WHERE pid IN pathnode;"
22552153
);
22562154
}
22572155
if( showCherrypicks ){
22582156
db_multi_exec(
22592157
"INSERT OR IGNORE INTO related(x)"
2260
- " SELECT parentid FROM cherrypick WHERE childid IN pathnode;"
2158
+ " SELECT parentid FROM cherrypick WHERE childid IN pathnode;"
22612159
);
2262
- if( P("mionly")==0 ){
2160
+ if( related==1 ){
22632161
db_multi_exec(
22642162
"INSERT OR IGNORE INTO related(x)"
2265
- " SELECT childid FROM cherrypick WHERE parentid IN pathnode;"
2163
+ " SELECT childid FROM cherrypick WHERE parentid IN pathnode;"
22662164
);
22672165
}
2166
+ }
2167
+ if( earlierRid && laterRid && commonAncs==earlierRid ){
2168
+ /* On a query with me=XXX, you=YYY, and rel, omit all nodes that
2169
+ ** are not ancestors of either XXX or YYY, as those nodes tend to
2170
+ ** be extraneous */
2171
+ db_multi_exec(
2172
+ "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
2173
+ );
2174
+ compute_ancestors(laterRid, 0, 0, earlierRid);
2175
+ db_multi_exec(
2176
+ "DELETE FROM related WHERE x NOT IN ok;"
2177
+ );
22682178
}
22692179
db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related");
22702180
}
2181
+ add_extra_rids("pathnode",P("x"));
22712182
blob_append_sql(&sql, " AND event.objid IN pathnode");
22722183
if( zChng && zChng[0] ){
22732184
db_multi_exec(
2274
- "DELETE FROM pathnode "
2275
- " WHERE NOT EXISTS(SELECT 1 FROM mlink, filename"
2276
- " WHERE mlink.mid=x"
2277
- " AND mlink.fnid=filename.fnid AND %s)",
2185
+ "DELETE FROM pathnode\n"
2186
+ " WHERE NOT EXISTS(SELECT 1 FROM mlink, filename\n"
2187
+ " WHERE mlink.mid=x\n"
2188
+ " AND mlink.fnid=filename.fnid\n"
2189
+ " AND %s)",
22782190
glob_expr("filename.name", zChng)
22792191
);
22802192
}
22812193
tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
22822194
db_multi_exec("%s", blob_sql_text(&sql));
@@ -2330,18 +2242,18 @@
23302242
if( !haveParameterN ) nEntry = 10;
23312243
}
23322244
db_multi_exec(
23332245
"CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
23342246
);
2247
+ add_extra_rids("ok", P("x"));
23352248
zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d",
23362249
p_rid ? p_rid : d_rid);
23372250
zCiName = zDPName;
23382251
if( zCiName==0 ) zCiName = zUuid;
23392252
blob_append_sql(&sql, " AND event.objid IN ok");
23402253
nd = 0;
23412254
if( d_rid ){
2342
- Stmt s;
23432255
double rStopTime = 9e99;
23442256
zFwdTo = P("ft");
23452257
if( zFwdTo ){
23462258
double rStartDate = db_double(0.0,
23472259
"SELECT mtime FROM event WHERE objid=%d", d_rid);
@@ -2353,27 +2265,25 @@
23532265
if( !haveParameterN ) nEntry = 0;
23542266
rStopTime = db_double(9e99,
23552267
"SELECT mtime FROM event WHERE objid=%d", ridFwdTo);
23562268
}
23572269
}
2358
- db_prepare(&s,
2359
- "WITH RECURSIVE"
2360
- " dx(rid,mtime) AS ("
2361
- " SELECT %d, 0"
2362
- " UNION"
2363
- " SELECT plink.cid, plink.mtime FROM dx, plink"
2364
- " WHERE plink.pid=dx.rid"
2365
- " AND (:stop>=8e99 OR plink.mtime<=:stop)"
2366
- " ORDER BY 2"
2367
- " )"
2270
+ if( rStopTime<9e99 ){
2271
+ rStopTime += 5.8e-6; /* Round up by 1/2 second */
2272
+ }
2273
+ db_multi_exec(
2274
+ "WITH RECURSIVE dx(rid,mtime) AS (\n"
2275
+ " SELECT %d, 0\n"
2276
+ " UNION\n"
2277
+ " SELECT plink.cid, plink.mtime FROM dx, plink\n"
2278
+ " WHERE plink.pid=dx.rid\n"
2279
+ " AND plink.mtime<=%.*g\n"
2280
+ " ORDER BY 2\n"
2281
+ ")\n"
23682282
"INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d",
2369
- d_rid, nEntry<=0 ? -1 : nEntry+1
2283
+ d_rid, rStopTime<8e99 ? 17 : 2, rStopTime, nEntry<=0 ? -1 : nEntry+1
23702284
);
2371
- db_bind_double(&s, ":stop", rStopTime);
2372
- db_step(&s);
2373
- db_finalize(&s);
2374
- /* compute_descendants(d_rid, nEntry==0 ? 0 : nEntry+1); */
23752285
nd = db_int(0, "SELECT count(*)-1 FROM ok");
23762286
if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql));
23772287
if( nd>0 || p_rid==0 ){
23782288
blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s");
23792289
}
@@ -2482,67 +2392,76 @@
24822392
if( zChng && *zChng ){
24832393
addFileGlobExclusion(zChng, &cond);
24842394
tmFlags |= TIMELINE_XMERGE;
24852395
}
24862396
if( zUses ){
2487
- blob_append_sql(&cond, " AND event.objid IN usesfile ");
2397
+ blob_append_sql(&cond, " AND event.objid IN usesfile\n");
24882398
}
24892399
if( renameOnly ){
2490
- blob_append_sql(&cond, " AND event.objid IN rnfile ");
2400
+ blob_append_sql(&cond, " AND event.objid IN rnfile\n");
24912401
}
24922402
if( forkOnly ){
2493
- blob_append_sql(&cond, " AND event.objid IN rnfork ");
2403
+ blob_append_sql(&cond, " AND event.objid IN rnfork\n");
24942404
}
24952405
if( cpOnly && showCherrypicks ){
24962406
db_multi_exec(
24972407
"CREATE TEMP TABLE IF NOT EXISTS cpnodes(rid INTEGER PRIMARY KEY);"
24982408
"INSERT OR IGNORE INTO cpnodes SELECT childid FROM cherrypick;"
24992409
"INSERT OR IGNORE INTO cpnodes SELECT parentid FROM cherrypick;"
25002410
);
2501
- blob_append_sql(&cond, " AND event.objid IN cpnodes ");
2411
+ blob_append_sql(&cond, " AND event.objid IN cpnodes\n");
25022412
}
25032413
if( bisectLocal || zBisect!=0 ){
2504
- blob_append_sql(&cond, " AND event.objid IN (SELECT rid FROM bilog) ");
2414
+ blob_append_sql(&cond, " AND event.objid IN (SELECT rid FROM bilog)\n");
25052415
}
25062416
if( zYearMonth ){
25072417
char *zNext;
2508
- zYearMonth = timeline_expand_datetime(zYearMonth);
2509
- if( strlen(zYearMonth)>7 ){
2510
- zYearMonth = mprintf("%.7s", zYearMonth);
2511
- }
2418
+ int bZulu = 0;
2419
+ const char *zTZMod;
2420
+ zYearMonth = timeline_expand_datetime(zYearMonth, &bZulu);
2421
+ zYearMonth = mprintf("%.7s", zYearMonth);
25122422
if( db_int(0,"SELECT julianday('%q-01') IS NULL", zYearMonth) ){
25132423
zYearMonth = db_text(0, "SELECT strftime('%%Y-%%m','now');");
25142424
}
2515
- zNext = db_text(0, "SELECT strftime('%%Y-%%m','%q-01','+1 month');",
2516
- zYearMonth);
2517
- if( db_int(0,
2518
- "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2519
- " WHERE blob.rid=event.objid AND mtime>=julianday('%q-01')%s)",
2520
- zNext, blob_sql_text(&cond))
2521
- ){
2522
- zNewerButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0));
2523
- zNewerButtonLabel = "Following month";
2524
- }
2525
- fossil_free(zNext);
2526
- zNext = db_text(0, "SELECT strftime('%%Y-%%m','%q-01','-1 month');",
2527
- zYearMonth);
2528
- if( db_int(0,
2529
- "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2530
- " WHERE blob.rid=event.objid AND mtime<julianday('%q-01')%s)",
2531
- zYearMonth, blob_sql_text(&cond))
2532
- ){
2533
- zOlderButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0));
2534
- zOlderButtonLabel = "Previous month";
2535
- }
2536
- fossil_free(zNext);
2537
- blob_append_sql(&cond, " AND %Q=strftime('%%Y-%%m',event.mtime) ",
2538
- zYearMonth);
2539
- nEntry = -1;
2425
+ zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00";
2426
+ if( db_int(0,
2427
+ "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2428
+ " WHERE blob.rid=event.objid"
2429
+ " AND mtime>=julianday('%q-01',%Q)%s)",
2430
+ zYearMonth, zTZMod, blob_sql_text(&cond))
2431
+ ){
2432
+ zNext = db_text(0, "SELECT strftime('%%Y%%m%q','%q-01','+1 month');",
2433
+ &"Z"[!bZulu], zYearMonth);
2434
+ zNewerButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0));
2435
+ zNewerButtonLabel = "Following month";
2436
+ fossil_free(zNext);
2437
+ }
2438
+ if( db_int(0,
2439
+ "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2440
+ " WHERE blob.rid=event.objid"
2441
+ " AND mtime<julianday('%q-01',%Q)%s)",
2442
+ zYearMonth, zTZMod, blob_sql_text(&cond))
2443
+ ){
2444
+ zNext = db_text(0, "SELECT strftime('%%Y%%m%q','%q-01','-1 month');",
2445
+ &"Z"[!bZulu], zYearMonth);
2446
+ zOlderButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0));
2447
+ zOlderButtonLabel = "Previous month";
2448
+ fossil_free(zNext);
2449
+ }
2450
+ blob_append_sql(&cond,
2451
+ " AND event.mtime>=julianday('%q-01',%Q)"
2452
+ " AND event.mtime<julianday('%q-01',%Q,'+1 month')\n",
2453
+ zYearMonth, zTZMod, zYearMonth, zTZMod);
2454
+ nEntry = -1;
2455
+ /* Adjust the zYearMonth for the title */
2456
+ zYearMonth = mprintf("%z-01%s", zYearMonth, &"Z"[!bZulu]);
25402457
}
25412458
else if( zYearWeek ){
25422459
char *z, *zNext;
2543
- zYearWeek = timeline_expand_datetime(zYearWeek);
2460
+ int bZulu = 0;
2461
+ const char *zTZMod;
2462
+ zYearWeek = timeline_expand_datetime(zYearWeek, &bZulu);
25442463
z = db_text(0, "SELECT strftime('%%Y-%%W',%Q)", zYearWeek);
25452464
if( z && z[0] ){
25462465
zYearWeekStart = db_text(0, "SELECT date(%Q,'-6 days','weekday 1')",
25472466
zYearWeek);
25482467
zYearWeek = z;
@@ -2559,64 +2478,152 @@
25592478
"SELECT date('now','-6 days','weekday 1');");
25602479
zYearWeek = db_text(0,
25612480
"SELECT strftime('%%Y-%%W','now','-6 days','weekday 1')");
25622481
}
25632482
}
2564
- zNext = db_text(0, "SELECT date(%Q,'+7 day');", zYearWeekStart);
2565
- if( db_int(0,
2566
- "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2567
- " WHERE blob.rid=event.objid AND mtime>=julianday(%Q)%s)",
2568
- zNext, blob_sql_text(&cond))
2569
- ){
2570
- zNewerButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0));
2571
- zNewerButtonLabel = "Following week";
2572
- }
2573
- fossil_free(zNext);
2574
- zNext = db_text(0, "SELECT date(%Q,'-7 days');", zYearWeekStart);
2575
- if( db_int(0,
2576
- "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2577
- " WHERE blob.rid=event.objid AND mtime<julianday(%Q)%s)",
2578
- zYearWeekStart, blob_sql_text(&cond))
2579
- ){
2580
- zOlderButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0));
2581
- zOlderButtonLabel = "Previous week";
2582
- }
2583
- fossil_free(zNext);
2584
- blob_append_sql(&cond, " AND %Q=strftime('%%Y-%%W',event.mtime) ",
2585
- zYearWeek);
2586
- nEntry = -1;
2483
+ zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00";
2484
+ if( db_int(0,
2485
+ "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2486
+ " WHERE blob.rid=event.objid"
2487
+ " AND mtime>=julianday(%Q,%Q)%s)",
2488
+ zYearWeekStart, zTZMod, blob_sql_text(&cond))
2489
+ ){
2490
+ zNext = db_text(0, "SELECT strftime('%%Y%%W%q',%Q,'+7 day');",
2491
+ &"Z"[!bZulu], zYearWeekStart);
2492
+ zNewerButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0));
2493
+ zNewerButtonLabel = "Following week";
2494
+ fossil_free(zNext);
2495
+ }
2496
+ if( db_int(0,
2497
+ "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2498
+ " WHERE blob.rid=event.objid"
2499
+ " AND mtime<julianday(%Q,%Q)%s)",
2500
+ zYearWeekStart, zTZMod, blob_sql_text(&cond))
2501
+ ){
2502
+ zNext = db_text(0, "SELECT strftime('%%Y%%W%q',%Q,'-7 days');",
2503
+ &"Z"[!bZulu], zYearWeekStart);
2504
+ zOlderButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0));
2505
+ zOlderButtonLabel = "Previous week";
2506
+ fossil_free(zNext);
2507
+ }
2508
+ blob_append_sql(&cond,
2509
+ " AND event.mtime>=julianday(%Q,%Q)"
2510
+ " AND event.mtime<julianday(%Q,%Q,'+7 days')\n",
2511
+ zYearWeekStart, zTZMod, zYearWeekStart, zTZMod);
2512
+ nEntry = -1;
2513
+ if( fossil_ui_localtime() && bZulu ){
2514
+ zYearWeekStart = mprintf("%zZ", zYearWeekStart);
2515
+ }
2516
+ }
2517
+ else if( zDay && timeline_is_datespan(zDay) ){
2518
+ char *zNext;
2519
+ char *zStart, *zEnd;
2520
+ int nDay;
2521
+ int bZulu = 0;
2522
+ const char *zTZMod;
2523
+ zEnd = db_text(0, "SELECT date(%Q)",
2524
+ timeline_expand_datetime(zDay+9, &bZulu));
2525
+ zStart = db_text(0, "SELECT date('%.4q-%.2q-%.2q')",
2526
+ zDay, zDay+4, zDay+6);
2527
+ nDay = db_int(0, "SELECT julianday(%Q)-julianday(%Q)", zEnd, zStart);
2528
+ if( nDay==0 ){
2529
+ zDay = &zDay[9];
2530
+ goto single_ymd;
2531
+ }
2532
+ if( nDay<0 ){
2533
+ char *zTemp = zEnd;
2534
+ zEnd = zStart;
2535
+ zStart = zTemp;
2536
+ nDay = 1 - nDay;
2537
+ }else{
2538
+ nDay += 1;
2539
+ }
2540
+ zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00";
2541
+ if( nDay>0 && db_int(0,
2542
+ "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2543
+ " WHERE blob.rid=event.objid"
2544
+ " AND mtime>=julianday(%Q,'1 day',%Q)%s)",
2545
+ zEnd, zTZMod, blob_sql_text(&cond))
2546
+ ){
2547
+ zNext = db_text(0,
2548
+ "SELECT strftime('%%Y%%m%%d-',%Q,'%d days')||"
2549
+ "strftime('%%Y%%m%%d%q',%Q,'%d day');",
2550
+ zStart, nDay, &"Z"[!bZulu], zEnd, nDay);
2551
+ zNewerButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
2552
+ zNewerButtonLabel = mprintf("Following %d days", nDay);
2553
+ fossil_free(zNext);
2554
+ }
2555
+ if( nDay>1 && db_int(0,
2556
+ "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2557
+ " WHERE blob.rid=event.objid"
2558
+ " AND mtime<julianday(%Q,'-1 day',%Q)%s)",
2559
+ zStart, zTZMod, blob_sql_text(&cond))
2560
+ ){
2561
+ zNext = db_text(0,
2562
+ "SELECT strftime('%%Y%%m%%d-',%Q,'%d days')||"
2563
+ "strftime('%%Y%%m%%d%q',%Q,'%d day');",
2564
+ zStart, -nDay, &"Z"[!bZulu], zEnd, -nDay);
2565
+ zOlderButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
2566
+ zOlderButtonLabel = mprintf("Previous %d days", nDay);
2567
+ fossil_free(zNext);
2568
+ }
2569
+ blob_append_sql(&cond,
2570
+ " AND event.mtime>=julianday(%Q,%Q)"
2571
+ " AND event.mtime<julianday(%Q,%Q,'+1 day')\n",
2572
+ zStart, zTZMod, zEnd, zTZMod);
2573
+ nEntry = -1;
2574
+
2575
+ if( fossil_ui_localtime() && bZulu ){
2576
+ zDay = mprintf("%d days between %zZ and %zZ", nDay, zStart, zEnd);
2577
+ }else{
2578
+ zDay = mprintf("%d days between %z and %z", nDay, zStart, zEnd);
2579
+ }
25872580
}
25882581
else if( zDay ){
25892582
char *zNext;
2590
- zDay = timeline_expand_datetime(zDay);
2583
+ int bZulu = 0;
2584
+ const char *zTZMod;
2585
+ single_ymd:
2586
+ bZulu = 0;
2587
+ zDay = timeline_expand_datetime(zDay, &bZulu);
25912588
zDay = db_text(0, "SELECT date(%Q)", zDay);
25922589
if( zDay==0 || zDay[0]==0 ){
25932590
zDay = db_text(0, "SELECT date('now')");
25942591
}
2595
- zNext = db_text(0, "SELECT date(%Q,'+1 day');", zDay);
2596
- if( db_int(0,
2597
- "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2598
- " WHERE blob.rid=event.objid AND mtime>=julianday(%Q)%s)",
2599
- zNext, blob_sql_text(&cond))
2600
- ){
2601
- zNewerButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
2602
- zNewerButtonLabel = "Following day";
2603
- }
2604
- fossil_free(zNext);
2605
- zNext = db_text(0, "SELECT date(%Q,'-1 day');", zDay);
2606
- if( db_int(0,
2607
- "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2608
- " WHERE blob.rid=event.objid AND mtime<julianday(%Q)%s)",
2609
- zDay, blob_sql_text(&cond))
2610
- ){
2611
- zOlderButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
2612
- zOlderButtonLabel = "Previous day";
2613
- }
2614
- fossil_free(zNext);
2615
- blob_append_sql(&cond, " AND %Q=date(event.mtime) ",
2616
- zDay);
2617
- nEntry = -1;
2592
+ zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00";
2593
+ if( db_int(0,
2594
+ "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2595
+ " WHERE blob.rid=event.objid"
2596
+ " AND mtime>=julianday(%Q,'+1 day',%Q)%s)",
2597
+ zDay, zTZMod, blob_sql_text(&cond))
2598
+ ){
2599
+ zNext = db_text(0,"SELECT strftime('%%Y%%m%%d%q',%Q,'+1 day');",
2600
+ &"Z"[!bZulu], zDay);
2601
+ zNewerButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
2602
+ zNewerButtonLabel = "Following day";
2603
+ fossil_free(zNext);
2604
+ }
2605
+ if( db_int(0,
2606
+ "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2607
+ " WHERE blob.rid=event.objid"
2608
+ " AND mtime<julianday(%Q,'-1 day',%Q)%s)",
2609
+ zDay, zTZMod, blob_sql_text(&cond))
2610
+ ){
2611
+ zNext = db_text(0,"SELECT strftime('%%Y%%m%%d%q',%Q,'-1 day');",
2612
+ &"Z"[!bZulu], zDay);
2613
+ zOlderButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
2614
+ zOlderButtonLabel = "Previous day";
2615
+ fossil_free(zNext);
2616
+ }
2617
+ blob_append_sql(&cond,
2618
+ " AND event.mtime>=julianday(%Q,%Q)"
2619
+ " AND event.mtime<julianday(%Q,%Q,'+1 day')\n",
2620
+ zDay, zTZMod, zDay, zTZMod);
2621
+ nEntry = -1;
2622
+ if( fossil_ui_localtime() && bZulu ){
2623
+ zDay = mprintf("%zZ", zDay); /* Add Z suffix to day for the title */
2624
+ }
26182625
}
26192626
else if( zNDays ){
26202627
nDays = atoi(zNDays);
26212628
if( nDays<1 ) nDays = 1;
26222629
blob_append_sql(&cond, " AND event.mtime>=julianday('now','-%d days') ",
@@ -2662,39 +2669,24 @@
26622669
nEntry = -1;
26632670
}
26642671
if( zTagSql ){
26652672
db_multi_exec(
26662673
"CREATE TEMP TABLE selected_nodes(rid INTEGER PRIMARY KEY);"
2667
- "INSERT OR IGNORE INTO selected_nodes"
2668
- " SELECT tagxref.rid FROM tagxref NATURAL JOIN tag"
2669
- " WHERE %s AND tagtype>0", zTagSql/*safe-for-%s*/
2674
+ "INSERT OR IGNORE INTO selected_nodes\n"
2675
+ " SELECT tagxref.rid FROM tagxref NATURAL JOIN tag\n"
2676
+ " WHERE tagtype>0\n"
2677
+ " AND %s", zTagSql/*safe-for-%s*/
26702678
);
26712679
if( zMark ){
26722680
/* If the t=release option is used with m=UUID, then also
26732681
** include the UUID check-in in the display list */
26742682
int ridMark = name_to_rid(zMark);
26752683
db_multi_exec(
26762684
"INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridMark);
26772685
}
2678
- if( P("x")!=0 ){
2679
- char *zX = fossil_strdup(P("x"));
2680
- int ii;
2681
- int ridX;
2682
- while( zX[0] ){
2683
- char c;
2684
- if( zX[0]==',' || zX[0]==' ' ){ zX++; continue; }
2685
- for(ii=1; zX[ii] && zX[ii]!=',' && zX[ii]!=' '; ii++){}
2686
- c = zX[ii];
2687
- zX[ii] = 0;
2688
- ridX = name_to_rid(zX);
2689
- db_multi_exec(
2690
- "INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridX);
2691
- zX[ii] = c;
2692
- zX += ii;
2693
- }
2694
- }
2695
- if( !related ){
2686
+ add_extra_rids("selected_nodes",P("x"));
2687
+ if( related==0 ){
26962688
blob_append_sql(&cond, " AND blob.rid IN selected_nodes");
26972689
}else{
26982690
db_multi_exec(
26992691
"CREATE TEMP TABLE related_nodes(rid INTEGER PRIMARY KEY);"
27002692
"INSERT INTO related_nodes SELECT rid FROM selected_nodes;"
@@ -2705,40 +2697,42 @@
27052697
** branch to be included in the report. These related check-ins are
27062698
** useful in helping to visualize what has happened on a quiescent
27072699
** branch that is infrequently merged with a much more activate branch.
27082700
*/
27092701
db_multi_exec(
2710
- "INSERT OR IGNORE INTO related_nodes"
2711
- " SELECT pid FROM selected_nodes CROSS JOIN plink"
2712
- " WHERE selected_nodes.rid=plink.cid;"
2702
+ "INSERT OR IGNORE INTO related_nodes\n"
2703
+ " SELECT pid FROM selected_nodes CROSS JOIN plink\n"
2704
+ " WHERE selected_nodes.rid=plink.cid;"
27132705
);
2714
- if( P("mionly")==0 ){
2706
+ if( related==1 ){
27152707
db_multi_exec(
2716
- "INSERT OR IGNORE INTO related_nodes"
2717
- " SELECT cid FROM selected_nodes CROSS JOIN plink"
2718
- " WHERE selected_nodes.rid=plink.pid;"
2708
+ "INSERT OR IGNORE INTO related_nodes\n"
2709
+ " SELECT cid FROM selected_nodes CROSS JOIN plink\n"
2710
+ " WHERE selected_nodes.rid=plink.pid;"
27192711
);
27202712
if( showCherrypicks ){
27212713
db_multi_exec(
2722
- "INSERT OR IGNORE INTO related_nodes"
2723
- " SELECT childid FROM selected_nodes CROSS JOIN cherrypick"
2724
- " WHERE selected_nodes.rid=cherrypick.parentid;"
2714
+ "INSERT OR IGNORE INTO related_nodes\n"
2715
+ " SELECT childid FROM selected_nodes CROSS JOIN cherrypick\n"
2716
+ " WHERE selected_nodes.rid=cherrypick.parentid;"
27252717
);
27262718
}
27272719
}
27282720
if( showCherrypicks ){
27292721
db_multi_exec(
2730
- "INSERT OR IGNORE INTO related_nodes"
2731
- " SELECT parentid FROM selected_nodes CROSS JOIN cherrypick"
2732
- " WHERE selected_nodes.rid=cherrypick.childid;"
2722
+ "INSERT OR IGNORE INTO related_nodes\n"
2723
+ " SELECT parentid FROM selected_nodes CROSS JOIN cherrypick\n"
2724
+ " WHERE selected_nodes.rid=cherrypick.childid;"
27332725
);
27342726
}
27352727
if( (tmFlags & TIMELINE_UNHIDE)==0 ){
27362728
db_multi_exec(
2737
- "DELETE FROM related_nodes WHERE rid IN "
2738
- " (SELECT related_nodes.rid FROM related_nodes, tagxref"
2739
- " WHERE tagid=%d AND tagtype>0 AND tagxref.rid=related_nodes.rid)",
2729
+ "DELETE FROM related_nodes\n"
2730
+ " WHERE rid IN (SELECT related_nodes.rid\n"
2731
+ " FROM related_nodes, tagxref\n"
2732
+ " WHERE tagid=%d AND tagtype>0\n"
2733
+ " AND tagxref.rid=related_nodes.rid)",
27402734
TAG_HIDDEN
27412735
);
27422736
}
27432737
}
27442738
}
@@ -2828,45 +2822,42 @@
28282822
rCirca = symbolic_name_to_mtime(zCirca, &zCirca);
28292823
blob_append_sql(&sql, "%s", blob_sql_text(&cond));
28302824
if( rAfter>0.0 ){
28312825
if( rBefore>0.0 ){
28322826
blob_append_sql(&sql,
2833
- " AND event.mtime>=%.17g AND event.mtime<=%.17g"
2827
+ " AND event.mtime>=%.17g AND event.mtime<=%.17g\n"
28342828
" ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND);
28352829
nEntry = -1;
28362830
}else{
28372831
blob_append_sql(&sql,
2838
- " AND event.mtime>=%.17g ORDER BY event.mtime ASC",
2832
+ " AND event.mtime>=%.17g\n ORDER BY event.mtime ASC",
28392833
rAfter-ONE_SECOND);
28402834
}
28412835
zCirca = 0;
28422836
url_add_parameter(&url, "c", 0);
28432837
}else if( rBefore>0.0 ){
28442838
blob_append_sql(&sql,
2845
- " AND event.mtime<=%.17g ORDER BY event.mtime DESC",
2839
+ " AND event.mtime<=%.17g\n ORDER BY event.mtime DESC",
28462840
rBefore+ONE_SECOND);
28472841
zCirca = 0;
28482842
url_add_parameter(&url, "c", 0);
28492843
}else if( rCirca>0.0 ){
28502844
Blob sql2;
28512845
blob_init(&sql2, blob_sql_text(&sql), -1);
28522846
blob_append_sql(&sql2,
2853
- " AND event.mtime>=%f ORDER BY event.mtime ASC", rCirca);
2847
+ " AND event.mtime>=%f\n ORDER BY event.mtime ASC", rCirca);
28542848
if( nEntry>0 ){
28552849
blob_append_sql(&sql2," LIMIT %d", (nEntry+1)/2);
28562850
}
2857
- if( PB("showsql") ){
2858
- @ <pre>%h(blob_sql_text(&sql2))</pre>
2859
- }
28602851
db_multi_exec("%s", blob_sql_text(&sql2));
28612852
if( nEntry>0 ){
28622853
nEntry -= db_int(0,"select count(*) from timeline");
28632854
if( nEntry<=0 ) nEntry = 1;
28642855
}
28652856
blob_reset(&sql2);
28662857
blob_append_sql(&sql,
2867
- " AND event.mtime<=%f ORDER BY event.mtime DESC",
2858
+ " AND event.mtime<=%f\n ORDER BY event.mtime DESC",
28682859
rCirca
28692860
);
28702861
if( zMark==0 ) zMark = zCirca;
28712862
}else{
28722863
blob_append_sql(&sql, " ORDER BY event.mtime DESC");
@@ -2875,11 +2866,11 @@
28752866
db_multi_exec("%s", blob_sql_text(&sql));
28762867
28772868
n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
28782869
zPlural = n==1 ? "" : "s";
28792870
if( zYearMonth ){
2880
- blob_appendf(&desc, "%d %s%s for the month beginning %h-01",
2871
+ blob_appendf(&desc, "%d %s%s for the month beginning %h",
28812872
n, zEType, zPlural, zYearMonth);
28822873
}else if( zYearWeek ){
28832874
blob_appendf(&desc, "%d %s%s for week %h beginning on %h",
28842875
n, zEType, zPlural, zYearWeek, zYearWeekStart);
28852876
}else if( zDay ){
@@ -3009,12 +3000,14 @@
30093000
style_submenu_multichoice("ms", count(azMatchStyles)/2,azMatchStyles,0);
30103001
}
30113002
}
30123003
blob_zero(&cond);
30133004
}
3014
- if( PB("showsql") ){
3015
- @ <pre>%h(blob_sql_text(&sql))</pre>
3005
+ if( showSql ){
3006
+ db_append_dml_to_blob(0);
3007
+ @ <pre>%h(blob_str(&allSql))</pre>
3008
+ blob_reset(&allSql);
30163009
}
30173010
if( search_restrict(SRCH_CKIN)!=0 ){
30183011
style_submenu_element("Search", "%R/search?y=c");
30193012
}
30203013
if( advancedMenu ){
@@ -3068,18 +3061,36 @@
30683061
if( zNewerButton ){
30693062
@ %z(chref("button","%s",zNewerButton))%h(zNewerButtonLabel)\
30703063
@ &nbsp;&uarr;</a>
30713064
}
30723065
cgi_check_for_malice();
3073
- www_print_timeline(&q, tmFlags, zThisUser, zThisTag, zBrName,
3074
- selectedRid, secondaryRid, 0);
3066
+ {
3067
+ Matcher *pLeftBranch;
3068
+ const char *zPattern = P("sl");
3069
+ if( zPattern!=0 ){
3070
+ MatchStyle ms;
3071
+ if( zMatchStyle!=0 ){
3072
+ ms = matchStyle;
3073
+ }else{
3074
+ ms = strpbrk(zPattern,"*[?")!=0 ? MS_GLOB : MS_BRLIST;
3075
+ }
3076
+ pLeftBranch = match_create(ms,zPattern);
3077
+ }else{
3078
+ pLeftBranch = match_create(matchStyle, zBrName?zBrName:zTagName);
3079
+ }
3080
+ www_print_timeline(&q, tmFlags, zThisUser, zThisTag, pLeftBranch,
3081
+ selectedRid, secondaryRid, 0);
3082
+ match_free(pLeftBranch);
3083
+ }
30753084
db_finalize(&q);
30763085
if( zOlderButton ){
30773086
@ %z(chref("button","%s",zOlderButton))%h(zOlderButtonLabel)\
30783087
@ &nbsp;&darr;</a>
30793088
}
30803089
document_emit_js(/*handles pikchrs rendered above*/);
3090
+ blob_reset(&sql);
3091
+ blob_reset(&desc);
30813092
style_finish_page();
30823093
}
30833094
30843095
/*
30853096
** Translate a timeline entry into the printable format by
@@ -3744,10 +3755,11 @@
37443755
const char *zToday;
37453756
char *zStartOfProject;
37463757
int i;
37473758
Stmt q;
37483759
char *z;
3760
+ int bZulu = 0;
37493761
37503762
login_check_credentials();
37513763
if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum) ){
37523764
login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
37533765
return;
@@ -3754,11 +3766,11 @@
37543766
}
37553767
style_set_current_feature("timeline");
37563768
style_header("Today In History");
37573769
zToday = (char*)P("today");
37583770
if( zToday ){
3759
- zToday = timeline_expand_datetime(zToday);
3771
+ zToday = timeline_expand_datetime(zToday, &bZulu);
37603772
if( !fossil_isdate(zToday) ) zToday = 0;
37613773
}
37623774
if( zToday==0 ){
37633775
zToday = db_text(0, "SELECT date('now',toLocal())");
37643776
}
37653777
--- src/timeline.c
+++ src/timeline.c
@@ -191,11 +191,11 @@
191 void www_print_timeline(
192 Stmt *pQuery, /* Query to implement the timeline */
193 int tmFlags, /* Flags controlling display behavior */
194 const char *zThisUser, /* Suppress links to this user */
195 const char *zThisTag, /* Suppress links to this tag */
196 const char *zLeftBranch, /* Strive to put this branch on the left margin */
197 int selectedRid, /* Highlight the line with this RID value or zero */
198 int secondRid, /* Secondary highlight (or zero) */
199 void (*xExtra)(int) /* Routine to call on each line of display */
200 ){
201 int mxWikiLen;
@@ -813,11 +813,11 @@
813 }
814 if( pendingEndTr ){
815 @ </td></tr>
816 }
817 if( pGraph ){
818 graph_finish(pGraph, zLeftBranch, tmFlags);
819 if( pGraph->nErr ){
820 graph_free(pGraph);
821 pGraph = 0;
822 }else{
823 @ <tr class="timelineBottom" id="btm-%d(iTableId)">\
@@ -1290,12 +1290,13 @@
1290 const char *zChng, /* The filename GLOB list */
1291 Blob *pSql /* The SELECT statement under construction */
1292 ){
1293 if( zChng==0 || zChng[0]==0 ) return;
1294 blob_append_sql(pSql," AND event.objid IN ("
1295 "SELECT mlink.mid FROM mlink, filename"
1296 " WHERE mlink.fnid=filename.fnid AND %s)",
 
1297 glob_expr("filename.name", mprintf("\"%s\"", zChng)));
1298 }
1299 static void addFileGlobDescription(
1300 const char *zChng, /* The filename GLOB list */
1301 Blob *pDescription /* Result description */
@@ -1303,274 +1304,86 @@
1303 if( zChng==0 || zChng[0]==0 ) return;
1304 blob_appendf(pDescription, " that include changes to files matching '%h'",
1305 zChng);
1306 }
1307
1308 /*
1309 ** Tag match expression type code.
1310 */
1311 typedef enum {
1312 MS_EXACT, /* Matches a single tag by exact string comparison. */
1313 MS_GLOB, /* Matches tags against a list of GLOB patterns. */
1314 MS_LIKE, /* Matches tags against a list of LIKE patterns. */
1315 MS_REGEXP, /* Matches tags against a list of regular expressions. */
1316 MS_BRLIST, /* Same as REGEXP, except the regular expression is a list
1317 ** of branch names */
1318 } MatchStyle;
1319
1320 /*
1321 ** Quote a tag string by surrounding it with double quotes and preceding
1322 ** internal double quotes and backslashes with backslashes.
1323 */
1324 static const char *tagQuote(
1325 int len, /* Maximum length of zTag, or negative for unlimited */
1326 const char *zTag /* Tag string */
1327 ){
1328 Blob blob = BLOB_INITIALIZER;
1329 int i, j;
1330 blob_zero(&blob);
1331 blob_append(&blob, "\"", 1);
1332 for( i=j=0; zTag[j] && (len<0 || j<len); ++j ){
1333 if( zTag[j]=='\"' || zTag[j]=='\\' ){
1334 if( j>i ){
1335 blob_append(&blob, zTag+i, j-i);
1336 }
1337 blob_append(&blob, "\\", 1);
1338 i = j;
1339 }
1340 }
1341 if( j>i ){
1342 blob_append(&blob, zTag+i, j-i);
1343 }
1344 blob_append(&blob, "\"", 1);
1345 return blob_str(&blob);
1346 }
1347
1348 /*
1349 ** Construct the tag match SQL expression.
1350 **
1351 ** This function is adapted from glob_expr() to support the MS_EXACT, MS_GLOB,
1352 ** MS_LIKE, MS_REGEXP, and MS_BRLIST match styles.
1353 **
1354 ** For MS_EXACT, the returned expression
1355 ** checks for integer match against the tag ID which is looked up directly by
1356 ** this function. For the other modes, the returned SQL expression performs
1357 ** string comparisons against the tag names, so it is necessary to join against
1358 ** the tag table to access the "tagname" column.
1359 **
1360 ** Each pattern is adjusted to to start with "sym-" and be anchored at end.
1361 **
1362 ** In MS_REGEXP mode, backslash can be used to protect delimiter characters.
1363 ** The backslashes are not removed from the regular expression.
1364 **
1365 ** In addition to assembling and returning an SQL expression, this function
1366 ** makes an English-language description of the patterns being matched, suitable
1367 ** for display in the web interface.
1368 **
1369 ** If any errors arise during processing, *zError is set to an error message.
1370 ** Otherwise it is set to NULL.
1371 */
1372 static const char *tagMatchExpression(
1373 MatchStyle matchStyle, /* Match style code */
1374 const char *zTag, /* Tag name, match pattern, or pattern list */
1375 const char **zDesc, /* Output expression description string */
1376 const char **zError /* Output error string */
1377 ){
1378 Blob expr = BLOB_INITIALIZER; /* SQL expression string assembly buffer */
1379 Blob desc = BLOB_INITIALIZER; /* English description of match patterns */
1380 Blob err = BLOB_INITIALIZER; /* Error text assembly buffer */
1381 const char *zStart; /* Text at start of expression */
1382 const char *zDelimiter; /* Text between expression terms */
1383 const char *zEnd; /* Text at end of expression */
1384 const char *zPrefix; /* Text before each match pattern */
1385 const char *zSuffix; /* Text after each match pattern */
1386 const char *zIntro; /* Text introducing pattern description */
1387 const char *zPattern = 0; /* Previous quoted pattern */
1388 const char *zFail = 0; /* Current failure message or NULL if okay */
1389 const char *zOr = " or "; /* Text before final quoted pattern */
1390 char cDel; /* Input delimiter character */
1391 int i; /* Input match pattern length counter */
1392
1393 /* Optimize exact matches by looking up the ID in advance to create a simple
1394 * numeric comparison. Bypass the remainder of this function. */
1395 if( matchStyle==MS_EXACT ){
1396 *zDesc = tagQuote(-1, zTag);
1397 return mprintf("(tagid=%d)", db_int(-1,
1398 "SELECT tagid FROM tag WHERE tagname='sym-%q'", zTag));
1399 }
1400
1401 /* Decide pattern prefix and suffix strings according to match style. */
1402 if( matchStyle==MS_GLOB ){
1403 zStart = "(";
1404 zDelimiter = " OR ";
1405 zEnd = ")";
1406 zPrefix = "tagname GLOB 'sym-";
1407 zSuffix = "'";
1408 zIntro = "glob pattern ";
1409 }else if( matchStyle==MS_LIKE ){
1410 zStart = "(";
1411 zDelimiter = " OR ";
1412 zEnd = ")";
1413 zPrefix = "tagname LIKE 'sym-";
1414 zSuffix = "'";
1415 zIntro = "SQL LIKE pattern ";
1416 }else if( matchStyle==MS_REGEXP ){
1417 zStart = "(tagname REGEXP '^sym-(";
1418 zDelimiter = "|";
1419 zEnd = ")$')";
1420 zPrefix = "";
1421 zSuffix = "";
1422 zIntro = "regular expression ";
1423 }else/* if( matchStyle==MS_BRLIST )*/{
1424 zStart = "tagname IN ('sym-";
1425 zDelimiter = "','sym-";
1426 zEnd = "')";
1427 zPrefix = "";
1428 zSuffix = "";
1429 zIntro = "";
1430 }
1431
1432 /* Convert the list of matches into an SQL expression and text description. */
1433 blob_zero(&expr);
1434 blob_zero(&desc);
1435 blob_zero(&err);
1436 while( 1 ){
1437 /* Skip leading delimiters. */
1438 for( ; fossil_isspace(*zTag) || *zTag==','; ++zTag );
1439
1440 /* Next non-delimiter character determines quoting. */
1441 if( !*zTag ){
1442 /* Terminate loop at end of string. */
1443 break;
1444 }else if( *zTag=='\'' || *zTag=='"' ){
1445 /* If word is quoted, prepare to stop at end quote. */
1446 cDel = *zTag;
1447 ++zTag;
1448 }else{
1449 /* If word is not quoted, prepare to stop at delimiter. */
1450 cDel = ',';
1451 }
1452
1453 /* Find the next delimiter character or end of string. */
1454 for( i=0; zTag[i] && zTag[i]!=cDel; ++i ){
1455 /* If delimiter is comma, also recognize spaces as delimiters. */
1456 if( cDel==',' && fossil_isspace(zTag[i]) ){
1457 break;
1458 }
1459
1460 /* In regexp mode, ignore delimiters following backslashes. */
1461 if( matchStyle==MS_REGEXP && zTag[i]=='\\' && zTag[i+1] ){
1462 ++i;
1463 }
1464 }
1465
1466 /* Check for regular expression syntax errors. */
1467 if( matchStyle==MS_REGEXP ){
1468 ReCompiled *regexp;
1469 char *zTagDup = fossil_strndup(zTag, i);
1470 zFail = re_compile(&regexp, zTagDup, 0);
1471 re_free(regexp);
1472 fossil_free(zTagDup);
1473 }
1474
1475 /* Process success and error results. */
1476 if( !zFail ){
1477 /* Incorporate the match word into the output expression. %q is used to
1478 * protect against SQL injection attacks by replacing ' with ''. */
1479 blob_appendf(&expr, "%s%s%#q%s", blob_size(&expr) ? zDelimiter : zStart,
1480 zPrefix, i, zTag, zSuffix);
1481
1482 /* Build up the description string. */
1483 if( !blob_size(&desc) ){
1484 /* First tag: start with intro followed by first quoted tag. */
1485 blob_append(&desc, zIntro, -1);
1486 blob_append(&desc, tagQuote(i, zTag), -1);
1487 }else{
1488 if( zPattern ){
1489 /* Third and subsequent tags: append comma then previous tag. */
1490 blob_append(&desc, ", ", 2);
1491 blob_append(&desc, zPattern, -1);
1492 zOr = ", or ";
1493 }
1494
1495 /* Second and subsequent tags: store quoted tag for next iteration. */
1496 zPattern = tagQuote(i, zTag);
1497 }
1498 }else{
1499 /* On error, skip the match word and build up the error message buffer. */
1500 if( !blob_size(&err) ){
1501 blob_append(&err, "Error: ", 7);
1502 }else{
1503 blob_append(&err, ", ", 2);
1504 }
1505 blob_appendf(&err, "(%s%s: %s)", zIntro, tagQuote(i, zTag), zFail);
1506 }
1507
1508 /* Advance past all consumed input characters. */
1509 zTag += i;
1510 if( cDel!=',' && *zTag==cDel ){
1511 ++zTag;
1512 }
1513 }
1514
1515 /* Finalize and extract the pattern description. */
1516 if( zPattern ){
1517 blob_append(&desc, zOr, -1);
1518 blob_append(&desc, zPattern, -1);
1519 }
1520 *zDesc = blob_str(&desc);
1521
1522 /* Finalize and extract the error text. */
1523 *zError = blob_size(&err) ? blob_str(&err) : 0;
1524
1525 /* Finalize and extract the SQL expression. */
1526 if( blob_size(&expr) ){
1527 blob_append(&expr, zEnd, -1);
1528 return blob_str(&expr);
1529 }
1530
1531 /* If execution reaches this point, the pattern was empty. Return NULL. */
1532 return 0;
1533 }
1534
1535 /*
1536 ** Similar to fossil_expand_datetime()
1537 **
1538 ** Add missing "-" characters into a date/time. Examples:
1539 **
1540 ** 20190419 => 2019-04-19
1541 ** 201904 => 2019-04
1542 */
1543 const char *timeline_expand_datetime(const char *zIn){
1544 static char zEDate[20];
1545 static const char aPunct[] = { 0, 0, '-', '-', ' ', ':', ':' };
1546 int n = (int)strlen(zIn);
1547 int i, j;
1548
1549 /* Only three forms allowed:
 
1550 ** (1) YYYYMMDD
1551 ** (2) YYYYMM
1552 ** (3) YYYYWW
1553 */
 
 
 
 
 
 
1554 if( n!=8 && n!=6 ) return zIn;
1555
1556 /* Every character must be a digit */
1557 for(i=0; fossil_isdigit(zIn[i]); i++){}
1558 if( i!=n ) return zIn;
1559
1560 /* Expand the date */
1561 for(i=j=0; zIn[i]; i++){
1562 if( i>=4 && (i%2)==0 ){
1563 zEDate[j++] = aPunct[i/2];
1564 }
1565 zEDate[j++] = zIn[i];
1566 }
1567 zEDate[j] = 0;
1568
1569 /* It looks like this may be a date. Return it with punctuation added. */
1570 return zEDate;
1571 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1572
1573 /*
1574 ** Find the first check-in encountered with a particular tag
1575 ** when moving either forwards are backwards in time from a
1576 ** particular starting point (iFrom). Return the rid of that
@@ -1591,10 +1404,11 @@
1591 tagId = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zEnd);
1592 if( tagId==0 ){
1593 endId = symbolic_name_to_rid(zEnd, "ci");
1594 if( endId==0 ) return 0;
1595 }
 
1596 if( bForward ){
1597 if( tagId ){
1598 db_prepare(&q,
1599 "WITH RECURSIVE dx(id,mtime) AS ("
1600 " SELECT %d, event.mtime FROM event WHERE objid=%d"
@@ -1658,12 +1472,54 @@
1658 }
1659 if( db_step(&q)==SQLITE_ROW ){
1660 ans = db_column_int(&q, 0);
1661 }
1662 db_finalize(&q);
 
1663 return ans;
1664 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1665
1666 /*
1667 ** COMMAND: test-endpoint
1668 **
1669 ** Usage: fossil test-endpoint BASE TAG ?OPTIONS?
@@ -1698,21 +1554,21 @@
1698 /*
1699 ** WEBPAGE: timeline
1700 **
1701 ** Query parameters:
1702 **
1703 ** a=TIMEORTAG Show events after TIMEORTAG
1704 ** b=TIMEORTAG Show events before TIMEORTAG
1705 ** c=TIMEORTAG Show events that happen "circa" TIMEORTAG
1706 ** cf=FILEHASH Show events around the time of the first use of
1707 ** the file with FILEHASH
1708 ** m=TIMEORTAG Highlight the event at TIMEORTAG, or the closest available
1709 ** event if TIMEORTAG is not part of the timeline. If
1710 ** the t= or r= is used, the m event is added to the timeline
1711 ** if it isn't there already.
1712 ** x=HASHLIST Show all check-ins in the comma-separated HASHLIST
1713 ** in addition to check-ins specified by t= or r=
1714 ** sel1=TIMEORTAG Highlight the check-in at TIMEORTAG if it is part of
1715 ** the timeline. Similar to m= except TIMEORTAG must
1716 ** match a check-in that is already in the timeline.
1717 ** sel2=TIMEORTAG Like sel1= but use the secondary highlight.
1718 ** n=COUNT Maximum number of events. "all" for no limit
@@ -1732,63 +1588,71 @@
1732 ** from=CX ... shortest path from CX back to CHECKIN
1733 ** ft=CHECKIN "Forward To": Show decendents forward to CHECKIN
1734 ** d=CX ... from CX up to the time of CHECKIN
1735 ** from=CX ... shortest path from CX up to CHECKIN
1736 ** t=TAG Show only check-ins with the given TAG
1737 ** r=TAG Show check-ins related to TAG, equivalent to t=TAG&rel
1738 ** tl=TAGLIST Shorthand for t=TAGLIST&ms=brlist
1739 ** rl=TAGLIST Shorthand for r=TAGLIST&ms=brlist
 
 
1740 ** rel Show related check-ins as well as those matching t=TAG
1741 ** mionly Limit rel to show ancestors but not descendants
1742 ** nowiki Do not show wiki associated with branch or tag
1743 ** ms=MATCHSTYLE Set tag match style to EXACT, GLOB, LIKE, REGEXP
 
1744 ** u=USER Only show items associated with USER
1745 ** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', or 'all'.
1746 ** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar",
1747 ** x: "Classic".
1748 ** advm Use the "Advanced" or "Busy" menu design.
1749 ** ng No Graph.
1750 ** ncp Omit cherrypick merges
1751 ** nd Do not highlight the focus check-in
1752 ** nsm Omit the submenu
1753 ** nc Omit all graph colors other than highlights
1754 ** v Show details of files changed
1755 ** vfx Show complete text of forum messages
1756 ** f=CHECKIN Show family (immediate parents and children) of CHECKIN
1757 ** from=CHECKIN Path from...
1758 ** to=CHECKIN ... to this
1759 ** to2=CHECKIN ... backup name if to= doesn't resolve
1760 ** shortest ... show only the shortest path
1761 ** rel ... also show related checkins
1762 ** bt=PRIOR ... path from CHECKIN back to PRIOR
1763 ** ft=LATER ... path from CHECKIN forward to LATER
 
 
 
1764 ** uf=FILE_HASH Show only check-ins that contain the given file version
1765 ** All qualifying check-ins are shown unless there is
1766 ** also an n= or n1= query parameter.
1767 ** chng=GLOBLIST Show only check-ins that involve changes to a file whose
1768 ** name matches one of the comma-separate GLOBLIST
1769 ** brbg Background color determined by branch name
1770 ** ubg Background color determined by user
1771 ** deltabg Background color red for delta manifests or green
1772 ** for baseline manifests
1773 ** namechng Show only check-ins that have filename changes
1774 ** forks Show only forks and their children
1775 ** cherrypicks Show all cherrypicks
1776 ** ym=YYYY-MM Show only events for the given year/month
1777 ** yw=YYYY-WW Show only events for the given week of the given year
1778 ** yw=YYYY-MM-DD Show events for the week that includes the given day
1779 ** ymd=YYYY-MM-DD Show only events on the given day. The use "ymd=now"
1780 ** to see all changes for the current week.
 
 
1781 ** year=YYYY Show only events on the given year. The use "year=0"
1782 ** to see all changes for the current year.
1783 ** days=N Show events over the previous N days
1784 ** datefmt=N Override the date format: 0=HH:MM, 1=HH:MM:SS,
1785 ** 2=YYYY-MM-DD HH:MM:SS, 3=YYMMDD HH:MM, and 4 means "off".
1786 ** bisect Show the check-ins that are in the current bisect
1787 ** oldestfirst Show events oldest first.
1788 ** showid Show RIDs
1789 ** showsql Show the SQL text
1790 **
1791 ** p= and d= can appear individually or together. If either p= or d=
1792 ** appear, then u=, y=, a=, and b= are ignored.
1793 **
1794 ** If both a= and b= appear then both upper and lower bounds are honored.
@@ -1862,13 +1726,20 @@
1862 int advancedMenu = 0; /* Use the advanced menu design */
1863 char *zPlural; /* Ending for plural forms */
1864 int showCherrypicks = 1; /* True to show cherrypick merges */
1865 int haveParameterN; /* True if n= query parameter present */
1866 int from_to_mode = 0; /* 0: from,to. 1: from,ft 2: from,bt */
 
 
1867
 
1868 url_initialize(&url, "timeline");
1869 cgi_query_parameters_to_url(&url);
 
 
 
 
1870
1871 (void)P_NoBot("ss")
1872 /* "ss" is processed via the udc but at least one spider likes to
1873 ** try to SQL inject via this argument, so let's catch that. */;
1874
@@ -1943,11 +1814,10 @@
1943 */
1944 pd_rid = name_choice("dp","dp2",&zDPName);
1945 if( pd_rid ){
1946 p_rid = d_rid = pd_rid;
1947 }
1948 login_check_credentials();
1949 if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum)
1950 || (bisectLocal && !g.perm.Setup)
1951 ){
1952 login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
1953 return;
@@ -1984,48 +1854,52 @@
1984
1985 /* Check for tl=TAGLIST and rl=TAGLIST which are abbreviations for
1986 ** t=TAGLIST&ms=brlist and r=TAGLIST&ms=brlist repectively. */
1987 if( zBrName==0 && zTagName==0 ){
1988 const char *z;
 
1989 if( (z = P("tl"))!=0 ){
1990 zTagName = z;
1991 zMatchStyle = "brlist";
 
 
 
 
 
1992 }
1993 if( (z = P("rl"))!=0 ){
1994 zBrName = z;
1995 zMatchStyle = "brlist";
 
 
 
 
 
 
1996 }
1997 }
1998
1999 /* Convert r=TAG to t=TAG&rel in order to populate the UI style widgets. */
2000 if( zBrName && !related ){
2001 cgi_delete_query_parameter("r");
2002 cgi_set_query_parameter("t", zBrName); (void)P("t");
2003 cgi_set_query_parameter("rel", "1");
2004 zTagName = zBrName;
2005 related = 1;
2006 zType = "ci";
2007 }
2008
2009 /* Ignore empty tag query strings. */
2010 if( zTagName && !*zTagName ){
2011 zTagName = 0;
2012 }
2013
2014 /* Finish preliminary processing of tag match queries. */
 
2015 if( zTagName ){
2016 zType = "ci";
2017 /* Interpet the tag style string. */
2018 if( fossil_stricmp(zMatchStyle, "glob")==0 ){
2019 matchStyle = MS_GLOB;
2020 }else if( fossil_stricmp(zMatchStyle, "like")==0 ){
2021 matchStyle = MS_LIKE;
2022 }else if( fossil_stricmp(zMatchStyle, "regexp")==0 ){
2023 matchStyle = MS_REGEXP;
2024 }else if( fossil_stricmp(zMatchStyle, "brlist")==0 ){
2025 matchStyle = MS_BRLIST;
2026 }else{
2027 /* For exact maching, inhibit links to the selected tag. */
2028 zThisTag = zTagName;
2029 Th_Store("current_checkin", zTagName);
2030 }
2031
@@ -2033,11 +1907,11 @@
2033 if( advancedMenu ){
2034 style_submenu_checkbox("rel", "Related", 0, 0);
2035 }
2036
2037 /* Construct the tag match expression. */
2038 zTagSql = tagMatchExpression(matchStyle, zTagName, &zMatchDesc, &zError);
2039 }
2040
2041 if( zMark && zMark[0]==0 ){
2042 if( zAfter ) zMark = zAfter;
2043 if( zBefore ) zMark = zBefore;
@@ -2081,10 +1955,11 @@
2081 }
2082 if( PB("nc") ){
2083 tmFlags &= ~(TIMELINE_DELTA|TIMELINE_BRCOLOR|TIMELINE_UCOLOR);
2084 tmFlags |= TIMELINE_NOCOLOR;
2085 }
 
2086 if( zUses!=0 ){
2087 int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses);
2088 if( ufid ){
2089 zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
2090 db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
@@ -2098,42 +1973,42 @@
2098 }
2099 if( renameOnly ){
2100 db_multi_exec(
2101 "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);"
2102 "INSERT OR IGNORE INTO rnfile"
2103 " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;"
2104 );
2105 disableY = 1;
2106 }
2107 if( forkOnly ){
2108 db_multi_exec(
2109 "CREATE TEMP TABLE rnfork(rid INTEGER PRIMARY KEY);\n"
2110 "INSERT OR IGNORE INTO rnfork(rid)\n"
2111 " SELECT pid FROM plink\n"
2112 " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
2113 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
2114 " GROUP BY pid"
2115 " HAVING count(*)>1;\n"
2116 "INSERT OR IGNORE INTO rnfork(rid)"
2117 " SELECT cid FROM plink\n"
2118 " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
2119 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
2120 " GROUP BY cid"
2121 " HAVING count(*)>1;\n",
2122 TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
2123 );
2124 db_multi_exec(
2125 "INSERT OR IGNORE INTO rnfork(rid)\n"
2126 " SELECT cid FROM plink\n"
2127 " WHERE pid IN rnfork"
2128 " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
2129 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
2130 " UNION "
2131 " SELECT pid FROM plink\n"
2132 " WHERE cid IN rnfork"
2133 " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)=="
2134 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n",
2135 TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
2136 );
2137 tmFlags |= TIMELINE_UNHIDE;
2138 zType = "ci";
2139 disableY = 1;
@@ -2206,77 +2081,114 @@
2206 PathNode *p = 0;
2207 const char *zFrom = 0;
2208 const char *zTo = 0;
2209 Blob ins;
2210 int nNodeOnPath = 0;
 
 
2211
2212 if( from_rid && to_rid ){
2213 if( from_to_mode==0 ){
2214 p = path_shortest(from_rid, to_rid, noMerge, 0, 0);
2215 }else if( from_to_mode==1 ){
2216 p = path_shortest(from_rid, to_rid, 0, 1, 0);
 
 
2217 }else{
2218 p = path_shortest(to_rid, from_rid, 0, 1, 0);
 
 
2219 }
2220 zFrom = P("from");
2221 zTo = zTo2 ? zTo2 : P("to");
2222 }else{
2223 if( path_common_ancestor(me_rid, you_rid) ){
 
2224 p = path_first();
2225 }
2226 zFrom = P("me");
2227 zTo = P("you");
 
 
 
 
 
 
 
 
 
2228 }
2229 blob_init(&ins, 0, 0);
2230 db_multi_exec(
2231 "CREATE TABLE IF NOT EXISTS temp.pathnode(x INTEGER PRIMARY KEY);"
2232 );
2233 if( p ){
 
2234 blob_init(&ins, 0, 0);
2235 blob_append_sql(&ins, "INSERT INTO pathnode(x) VALUES(%d)", p->rid);
2236 p = p->u.pTo;
2237 while( p ){
2238 blob_append_sql(&ins, ",(%d)", p->rid);
 
 
 
 
 
 
2239 p = p->u.pTo;
2240 }
2241 }
2242 path_reset();
2243 db_multi_exec("%s", blob_str(&ins)/*safe-for-%s*/);
2244 blob_reset(&ins);
2245 if( related || P("mionly") ){
2246 db_multi_exec(
2247 "CREATE TABLE IF NOT EXISTS temp.related(x INTEGER PRIMARY KEY);"
2248 "INSERT OR IGNORE INTO related(x)"
2249 " SELECT pid FROM plink WHERE cid IN pathnode AND NOT isprim;"
2250 );
2251 if( P("mionly")==0 ){
2252 db_multi_exec(
2253 "INSERT OR IGNORE INTO related(x)"
2254 " SELECT cid FROM plink WHERE pid IN pathnode;"
2255 );
2256 }
2257 if( showCherrypicks ){
2258 db_multi_exec(
2259 "INSERT OR IGNORE INTO related(x)"
2260 " SELECT parentid FROM cherrypick WHERE childid IN pathnode;"
2261 );
2262 if( P("mionly")==0 ){
2263 db_multi_exec(
2264 "INSERT OR IGNORE INTO related(x)"
2265 " SELECT childid FROM cherrypick WHERE parentid IN pathnode;"
2266 );
2267 }
 
 
 
 
 
 
 
 
 
 
 
 
2268 }
2269 db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related");
2270 }
 
2271 blob_append_sql(&sql, " AND event.objid IN pathnode");
2272 if( zChng && zChng[0] ){
2273 db_multi_exec(
2274 "DELETE FROM pathnode "
2275 " WHERE NOT EXISTS(SELECT 1 FROM mlink, filename"
2276 " WHERE mlink.mid=x"
2277 " AND mlink.fnid=filename.fnid AND %s)",
 
2278 glob_expr("filename.name", zChng)
2279 );
2280 }
2281 tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
2282 db_multi_exec("%s", blob_sql_text(&sql));
@@ -2330,18 +2242,18 @@
2330 if( !haveParameterN ) nEntry = 10;
2331 }
2332 db_multi_exec(
2333 "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
2334 );
 
2335 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d",
2336 p_rid ? p_rid : d_rid);
2337 zCiName = zDPName;
2338 if( zCiName==0 ) zCiName = zUuid;
2339 blob_append_sql(&sql, " AND event.objid IN ok");
2340 nd = 0;
2341 if( d_rid ){
2342 Stmt s;
2343 double rStopTime = 9e99;
2344 zFwdTo = P("ft");
2345 if( zFwdTo ){
2346 double rStartDate = db_double(0.0,
2347 "SELECT mtime FROM event WHERE objid=%d", d_rid);
@@ -2353,27 +2265,25 @@
2353 if( !haveParameterN ) nEntry = 0;
2354 rStopTime = db_double(9e99,
2355 "SELECT mtime FROM event WHERE objid=%d", ridFwdTo);
2356 }
2357 }
2358 db_prepare(&s,
2359 "WITH RECURSIVE"
2360 " dx(rid,mtime) AS ("
2361 " SELECT %d, 0"
2362 " UNION"
2363 " SELECT plink.cid, plink.mtime FROM dx, plink"
2364 " WHERE plink.pid=dx.rid"
2365 " AND (:stop>=8e99 OR plink.mtime<=:stop)"
2366 " ORDER BY 2"
2367 " )"
 
 
2368 "INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d",
2369 d_rid, nEntry<=0 ? -1 : nEntry+1
2370 );
2371 db_bind_double(&s, ":stop", rStopTime);
2372 db_step(&s);
2373 db_finalize(&s);
2374 /* compute_descendants(d_rid, nEntry==0 ? 0 : nEntry+1); */
2375 nd = db_int(0, "SELECT count(*)-1 FROM ok");
2376 if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql));
2377 if( nd>0 || p_rid==0 ){
2378 blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s");
2379 }
@@ -2482,67 +2392,76 @@
2482 if( zChng && *zChng ){
2483 addFileGlobExclusion(zChng, &cond);
2484 tmFlags |= TIMELINE_XMERGE;
2485 }
2486 if( zUses ){
2487 blob_append_sql(&cond, " AND event.objid IN usesfile ");
2488 }
2489 if( renameOnly ){
2490 blob_append_sql(&cond, " AND event.objid IN rnfile ");
2491 }
2492 if( forkOnly ){
2493 blob_append_sql(&cond, " AND event.objid IN rnfork ");
2494 }
2495 if( cpOnly && showCherrypicks ){
2496 db_multi_exec(
2497 "CREATE TEMP TABLE IF NOT EXISTS cpnodes(rid INTEGER PRIMARY KEY);"
2498 "INSERT OR IGNORE INTO cpnodes SELECT childid FROM cherrypick;"
2499 "INSERT OR IGNORE INTO cpnodes SELECT parentid FROM cherrypick;"
2500 );
2501 blob_append_sql(&cond, " AND event.objid IN cpnodes ");
2502 }
2503 if( bisectLocal || zBisect!=0 ){
2504 blob_append_sql(&cond, " AND event.objid IN (SELECT rid FROM bilog) ");
2505 }
2506 if( zYearMonth ){
2507 char *zNext;
2508 zYearMonth = timeline_expand_datetime(zYearMonth);
2509 if( strlen(zYearMonth)>7 ){
2510 zYearMonth = mprintf("%.7s", zYearMonth);
2511 }
2512 if( db_int(0,"SELECT julianday('%q-01') IS NULL", zYearMonth) ){
2513 zYearMonth = db_text(0, "SELECT strftime('%%Y-%%m','now');");
2514 }
2515 zNext = db_text(0, "SELECT strftime('%%Y-%%m','%q-01','+1 month');",
2516 zYearMonth);
2517 if( db_int(0,
2518 "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2519 " WHERE blob.rid=event.objid AND mtime>=julianday('%q-01')%s)",
2520 zNext, blob_sql_text(&cond))
2521 ){
2522 zNewerButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0));
2523 zNewerButtonLabel = "Following month";
2524 }
2525 fossil_free(zNext);
2526 zNext = db_text(0, "SELECT strftime('%%Y-%%m','%q-01','-1 month');",
2527 zYearMonth);
2528 if( db_int(0,
2529 "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2530 " WHERE blob.rid=event.objid AND mtime<julianday('%q-01')%s)",
2531 zYearMonth, blob_sql_text(&cond))
2532 ){
2533 zOlderButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0));
2534 zOlderButtonLabel = "Previous month";
2535 }
2536 fossil_free(zNext);
2537 blob_append_sql(&cond, " AND %Q=strftime('%%Y-%%m',event.mtime) ",
2538 zYearMonth);
2539 nEntry = -1;
 
 
 
 
 
 
 
2540 }
2541 else if( zYearWeek ){
2542 char *z, *zNext;
2543 zYearWeek = timeline_expand_datetime(zYearWeek);
 
 
2544 z = db_text(0, "SELECT strftime('%%Y-%%W',%Q)", zYearWeek);
2545 if( z && z[0] ){
2546 zYearWeekStart = db_text(0, "SELECT date(%Q,'-6 days','weekday 1')",
2547 zYearWeek);
2548 zYearWeek = z;
@@ -2559,64 +2478,152 @@
2559 "SELECT date('now','-6 days','weekday 1');");
2560 zYearWeek = db_text(0,
2561 "SELECT strftime('%%Y-%%W','now','-6 days','weekday 1')");
2562 }
2563 }
2564 zNext = db_text(0, "SELECT date(%Q,'+7 day');", zYearWeekStart);
2565 if( db_int(0,
2566 "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2567 " WHERE blob.rid=event.objid AND mtime>=julianday(%Q)%s)",
2568 zNext, blob_sql_text(&cond))
2569 ){
2570 zNewerButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0));
2571 zNewerButtonLabel = "Following week";
2572 }
2573 fossil_free(zNext);
2574 zNext = db_text(0, "SELECT date(%Q,'-7 days');", zYearWeekStart);
2575 if( db_int(0,
2576 "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2577 " WHERE blob.rid=event.objid AND mtime<julianday(%Q)%s)",
2578 zYearWeekStart, blob_sql_text(&cond))
2579 ){
2580 zOlderButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0));
2581 zOlderButtonLabel = "Previous week";
2582 }
2583 fossil_free(zNext);
2584 blob_append_sql(&cond, " AND %Q=strftime('%%Y-%%W',event.mtime) ",
2585 zYearWeek);
2586 nEntry = -1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2587 }
2588 else if( zDay ){
2589 char *zNext;
2590 zDay = timeline_expand_datetime(zDay);
 
 
 
 
2591 zDay = db_text(0, "SELECT date(%Q)", zDay);
2592 if( zDay==0 || zDay[0]==0 ){
2593 zDay = db_text(0, "SELECT date('now')");
2594 }
2595 zNext = db_text(0, "SELECT date(%Q,'+1 day');", zDay);
2596 if( db_int(0,
2597 "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2598 " WHERE blob.rid=event.objid AND mtime>=julianday(%Q)%s)",
2599 zNext, blob_sql_text(&cond))
2600 ){
2601 zNewerButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
2602 zNewerButtonLabel = "Following day";
2603 }
2604 fossil_free(zNext);
2605 zNext = db_text(0, "SELECT date(%Q,'-1 day');", zDay);
2606 if( db_int(0,
2607 "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2608 " WHERE blob.rid=event.objid AND mtime<julianday(%Q)%s)",
2609 zDay, blob_sql_text(&cond))
2610 ){
2611 zOlderButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
2612 zOlderButtonLabel = "Previous day";
2613 }
2614 fossil_free(zNext);
2615 blob_append_sql(&cond, " AND %Q=date(event.mtime) ",
2616 zDay);
2617 nEntry = -1;
 
 
 
 
 
 
 
 
 
 
2618 }
2619 else if( zNDays ){
2620 nDays = atoi(zNDays);
2621 if( nDays<1 ) nDays = 1;
2622 blob_append_sql(&cond, " AND event.mtime>=julianday('now','-%d days') ",
@@ -2662,39 +2669,24 @@
2662 nEntry = -1;
2663 }
2664 if( zTagSql ){
2665 db_multi_exec(
2666 "CREATE TEMP TABLE selected_nodes(rid INTEGER PRIMARY KEY);"
2667 "INSERT OR IGNORE INTO selected_nodes"
2668 " SELECT tagxref.rid FROM tagxref NATURAL JOIN tag"
2669 " WHERE %s AND tagtype>0", zTagSql/*safe-for-%s*/
 
2670 );
2671 if( zMark ){
2672 /* If the t=release option is used with m=UUID, then also
2673 ** include the UUID check-in in the display list */
2674 int ridMark = name_to_rid(zMark);
2675 db_multi_exec(
2676 "INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridMark);
2677 }
2678 if( P("x")!=0 ){
2679 char *zX = fossil_strdup(P("x"));
2680 int ii;
2681 int ridX;
2682 while( zX[0] ){
2683 char c;
2684 if( zX[0]==',' || zX[0]==' ' ){ zX++; continue; }
2685 for(ii=1; zX[ii] && zX[ii]!=',' && zX[ii]!=' '; ii++){}
2686 c = zX[ii];
2687 zX[ii] = 0;
2688 ridX = name_to_rid(zX);
2689 db_multi_exec(
2690 "INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridX);
2691 zX[ii] = c;
2692 zX += ii;
2693 }
2694 }
2695 if( !related ){
2696 blob_append_sql(&cond, " AND blob.rid IN selected_nodes");
2697 }else{
2698 db_multi_exec(
2699 "CREATE TEMP TABLE related_nodes(rid INTEGER PRIMARY KEY);"
2700 "INSERT INTO related_nodes SELECT rid FROM selected_nodes;"
@@ -2705,40 +2697,42 @@
2705 ** branch to be included in the report. These related check-ins are
2706 ** useful in helping to visualize what has happened on a quiescent
2707 ** branch that is infrequently merged with a much more activate branch.
2708 */
2709 db_multi_exec(
2710 "INSERT OR IGNORE INTO related_nodes"
2711 " SELECT pid FROM selected_nodes CROSS JOIN plink"
2712 " WHERE selected_nodes.rid=plink.cid;"
2713 );
2714 if( P("mionly")==0 ){
2715 db_multi_exec(
2716 "INSERT OR IGNORE INTO related_nodes"
2717 " SELECT cid FROM selected_nodes CROSS JOIN plink"
2718 " WHERE selected_nodes.rid=plink.pid;"
2719 );
2720 if( showCherrypicks ){
2721 db_multi_exec(
2722 "INSERT OR IGNORE INTO related_nodes"
2723 " SELECT childid FROM selected_nodes CROSS JOIN cherrypick"
2724 " WHERE selected_nodes.rid=cherrypick.parentid;"
2725 );
2726 }
2727 }
2728 if( showCherrypicks ){
2729 db_multi_exec(
2730 "INSERT OR IGNORE INTO related_nodes"
2731 " SELECT parentid FROM selected_nodes CROSS JOIN cherrypick"
2732 " WHERE selected_nodes.rid=cherrypick.childid;"
2733 );
2734 }
2735 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
2736 db_multi_exec(
2737 "DELETE FROM related_nodes WHERE rid IN "
2738 " (SELECT related_nodes.rid FROM related_nodes, tagxref"
2739 " WHERE tagid=%d AND tagtype>0 AND tagxref.rid=related_nodes.rid)",
 
 
2740 TAG_HIDDEN
2741 );
2742 }
2743 }
2744 }
@@ -2828,45 +2822,42 @@
2828 rCirca = symbolic_name_to_mtime(zCirca, &zCirca);
2829 blob_append_sql(&sql, "%s", blob_sql_text(&cond));
2830 if( rAfter>0.0 ){
2831 if( rBefore>0.0 ){
2832 blob_append_sql(&sql,
2833 " AND event.mtime>=%.17g AND event.mtime<=%.17g"
2834 " ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND);
2835 nEntry = -1;
2836 }else{
2837 blob_append_sql(&sql,
2838 " AND event.mtime>=%.17g ORDER BY event.mtime ASC",
2839 rAfter-ONE_SECOND);
2840 }
2841 zCirca = 0;
2842 url_add_parameter(&url, "c", 0);
2843 }else if( rBefore>0.0 ){
2844 blob_append_sql(&sql,
2845 " AND event.mtime<=%.17g ORDER BY event.mtime DESC",
2846 rBefore+ONE_SECOND);
2847 zCirca = 0;
2848 url_add_parameter(&url, "c", 0);
2849 }else if( rCirca>0.0 ){
2850 Blob sql2;
2851 blob_init(&sql2, blob_sql_text(&sql), -1);
2852 blob_append_sql(&sql2,
2853 " AND event.mtime>=%f ORDER BY event.mtime ASC", rCirca);
2854 if( nEntry>0 ){
2855 blob_append_sql(&sql2," LIMIT %d", (nEntry+1)/2);
2856 }
2857 if( PB("showsql") ){
2858 @ <pre>%h(blob_sql_text(&sql2))</pre>
2859 }
2860 db_multi_exec("%s", blob_sql_text(&sql2));
2861 if( nEntry>0 ){
2862 nEntry -= db_int(0,"select count(*) from timeline");
2863 if( nEntry<=0 ) nEntry = 1;
2864 }
2865 blob_reset(&sql2);
2866 blob_append_sql(&sql,
2867 " AND event.mtime<=%f ORDER BY event.mtime DESC",
2868 rCirca
2869 );
2870 if( zMark==0 ) zMark = zCirca;
2871 }else{
2872 blob_append_sql(&sql, " ORDER BY event.mtime DESC");
@@ -2875,11 +2866,11 @@
2875 db_multi_exec("%s", blob_sql_text(&sql));
2876
2877 n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
2878 zPlural = n==1 ? "" : "s";
2879 if( zYearMonth ){
2880 blob_appendf(&desc, "%d %s%s for the month beginning %h-01",
2881 n, zEType, zPlural, zYearMonth);
2882 }else if( zYearWeek ){
2883 blob_appendf(&desc, "%d %s%s for week %h beginning on %h",
2884 n, zEType, zPlural, zYearWeek, zYearWeekStart);
2885 }else if( zDay ){
@@ -3009,12 +3000,14 @@
3009 style_submenu_multichoice("ms", count(azMatchStyles)/2,azMatchStyles,0);
3010 }
3011 }
3012 blob_zero(&cond);
3013 }
3014 if( PB("showsql") ){
3015 @ <pre>%h(blob_sql_text(&sql))</pre>
 
 
3016 }
3017 if( search_restrict(SRCH_CKIN)!=0 ){
3018 style_submenu_element("Search", "%R/search?y=c");
3019 }
3020 if( advancedMenu ){
@@ -3068,18 +3061,36 @@
3068 if( zNewerButton ){
3069 @ %z(chref("button","%s",zNewerButton))%h(zNewerButtonLabel)\
3070 @ &nbsp;&uarr;</a>
3071 }
3072 cgi_check_for_malice();
3073 www_print_timeline(&q, tmFlags, zThisUser, zThisTag, zBrName,
3074 selectedRid, secondaryRid, 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3075 db_finalize(&q);
3076 if( zOlderButton ){
3077 @ %z(chref("button","%s",zOlderButton))%h(zOlderButtonLabel)\
3078 @ &nbsp;&darr;</a>
3079 }
3080 document_emit_js(/*handles pikchrs rendered above*/);
 
 
3081 style_finish_page();
3082 }
3083
3084 /*
3085 ** Translate a timeline entry into the printable format by
@@ -3744,10 +3755,11 @@
3744 const char *zToday;
3745 char *zStartOfProject;
3746 int i;
3747 Stmt q;
3748 char *z;
 
3749
3750 login_check_credentials();
3751 if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum) ){
3752 login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
3753 return;
@@ -3754,11 +3766,11 @@
3754 }
3755 style_set_current_feature("timeline");
3756 style_header("Today In History");
3757 zToday = (char*)P("today");
3758 if( zToday ){
3759 zToday = timeline_expand_datetime(zToday);
3760 if( !fossil_isdate(zToday) ) zToday = 0;
3761 }
3762 if( zToday==0 ){
3763 zToday = db_text(0, "SELECT date('now',toLocal())");
3764 }
3765
--- src/timeline.c
+++ src/timeline.c
@@ -191,11 +191,11 @@
191 void www_print_timeline(
192 Stmt *pQuery, /* Query to implement the timeline */
193 int tmFlags, /* Flags controlling display behavior */
194 const char *zThisUser, /* Suppress links to this user */
195 const char *zThisTag, /* Suppress links to this tag */
196 Matcher *pLeftBranch, /* Comparison function to use for zLeftBranch */
197 int selectedRid, /* Highlight the line with this RID value or zero */
198 int secondRid, /* Secondary highlight (or zero) */
199 void (*xExtra)(int) /* Routine to call on each line of display */
200 ){
201 int mxWikiLen;
@@ -813,11 +813,11 @@
813 }
814 if( pendingEndTr ){
815 @ </td></tr>
816 }
817 if( pGraph ){
818 graph_finish(pGraph, pLeftBranch, tmFlags);
819 if( pGraph->nErr ){
820 graph_free(pGraph);
821 pGraph = 0;
822 }else{
823 @ <tr class="timelineBottom" id="btm-%d(iTableId)">\
@@ -1290,12 +1290,13 @@
1290 const char *zChng, /* The filename GLOB list */
1291 Blob *pSql /* The SELECT statement under construction */
1292 ){
1293 if( zChng==0 || zChng[0]==0 ) return;
1294 blob_append_sql(pSql," AND event.objid IN ("
1295 "SELECT mlink.mid FROM mlink, filename\n"
1296 " WHERE mlink.fnid=filename.fnid\n"
1297 " AND %s)",
1298 glob_expr("filename.name", mprintf("\"%s\"", zChng)));
1299 }
1300 static void addFileGlobDescription(
1301 const char *zChng, /* The filename GLOB list */
1302 Blob *pDescription /* Result description */
@@ -1303,274 +1304,86 @@
1304 if( zChng==0 || zChng[0]==0 ) return;
1305 blob_appendf(pDescription, " that include changes to files matching '%h'",
1306 zChng);
1307 }
1308
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1309 /*
1310 ** Similar to fossil_expand_datetime()
1311 **
1312 ** Add missing "-" characters into a date/time. Examples:
1313 **
1314 ** 20190419 => 2019-04-19
1315 ** 201904 => 2019-04
1316 */
1317 const char *timeline_expand_datetime(const char *zIn, int *pbZulu){
1318 static char zEDate[16];
 
1319 int n = (int)strlen(zIn);
1320 int i, j;
1321
1322 /* These forms are recognized:
1323 **
1324 ** (1) YYYYMMDD
1325 ** (2) YYYYMM
1326 ** (3) YYYYWW
1327 */
1328 if( n && (zIn[n-1]=='Z' || zIn[n-1]=='z') ){
1329 n--;
1330 if( pbZulu ) *pbZulu = 1;
1331 }else{
1332 if( pbZulu ) *pbZulu = 0;
1333 }
1334 if( n!=8 && n!=6 ) return zIn;
1335
1336 /* Every character must be a digit */
1337 for(i=0; i<n && fossil_isdigit(zIn[i]); i++){}
1338 if( i!=n ) return zIn;
1339
1340 /* Expand the date */
1341 for(i=j=0; i<n; i++){
1342 if( j==4 || j==7 ) zEDate[j++] = '-';
 
 
1343 zEDate[j++] = zIn[i];
1344 }
1345 zEDate[j] = 0;
1346
1347 /* It looks like this may be a date. Return it with punctuation added. */
1348 return zEDate;
1349 }
1350
1351 /*
1352 ** Check to see if the argument is a date-span for the ymd= query
1353 ** parameter. A valid date-span is of the form:
1354 **
1355 ** 0123456789 123456 <-- index
1356 ** YYYYMMDD-YYYYMMDD
1357 **
1358 ** with an optional "Z" timeline modifier at the end. Return true if
1359 ** the input is a valid date space and false if not.
1360 */
1361 static int timeline_is_datespan(const char *zDay){
1362 size_t n = strlen(zDay);
1363 int i, d, m;
1364
1365 if( n<17 || n>18 ) return 0;
1366 if( n==18 ){
1367 if( zDay[17]!='Z' && zDay[17]!='z' ) return 0;
1368 n--;
1369 }
1370 if( zDay[8]!='-' ) return 0;
1371 for(i=0; i<17 && (fossil_isdigit(zDay[i]) || i==8); i++){}
1372 if( i!=17 ) return 0;
1373 i = atoi(zDay);
1374 d = i%100;
1375 if( d<1 || d>31 ) return 0;
1376 m = (i/100)%100;
1377 if( m<1 || m>12 ) return 0;
1378 i = atoi(zDay+9);
1379 d = i%100;
1380 if( d<1 || d>31 ) return 0;
1381 m = (i/100)%100;
1382 if( m<1 || m>12 ) return 0;
1383 return 1;
1384 }
1385
1386 /*
1387 ** Find the first check-in encountered with a particular tag
1388 ** when moving either forwards are backwards in time from a
1389 ** particular starting point (iFrom). Return the rid of that
@@ -1591,10 +1404,11 @@
1404 tagId = db_int(0, "SELECT tagid FROM tag WHERE tagname='sym-%q'", zEnd);
1405 if( tagId==0 ){
1406 endId = symbolic_name_to_rid(zEnd, "ci");
1407 if( endId==0 ) return 0;
1408 }
1409 db_pause_dml_log();
1410 if( bForward ){
1411 if( tagId ){
1412 db_prepare(&q,
1413 "WITH RECURSIVE dx(id,mtime) AS ("
1414 " SELECT %d, event.mtime FROM event WHERE objid=%d"
@@ -1658,12 +1472,54 @@
1472 }
1473 if( db_step(&q)==SQLITE_ROW ){
1474 ans = db_column_int(&q, 0);
1475 }
1476 db_finalize(&q);
1477 db_unpause_dml_log();
1478 return ans;
1479 }
1480
1481 /*
1482 ** Add to the (temp) table zTab, RID values for every check-in
1483 ** identifier found on the zExtra string. Check-in names can be separated
1484 ** by commas or by whitespace.
1485 */
1486 static void add_extra_rids(const char *zTab, const char *zExtra){
1487 int ii;
1488 int rid;
1489 int cnt;
1490 Blob sql;
1491 char *zX;
1492 char *zToDel;
1493 if( zExtra==0 ) return;
1494 cnt = 0;
1495 blob_init(&sql, 0, 0);
1496 zX = zToDel = fossil_strdup(zExtra);
1497 blob_append_sql(&sql, "INSERT OR IGNORE INTO \"%w\" VALUES", zTab);
1498 while( zX[0] ){
1499 char c;
1500 if( zX[0]==',' || zX[0]==' ' ){ zX++; continue; }
1501 for(ii=1; zX[ii] && zX[ii]!=',' && zX[ii]!=' '; ii++){}
1502 c = zX[ii];
1503 zX[ii] = 0;
1504 rid = name_to_rid(zX);
1505 if( rid>0 ){
1506 if( (cnt%10)==4 ){
1507 blob_append_sql(&sql,",\n ");
1508 }else if( cnt>0 ){
1509 blob_append_sql(&sql,",");
1510 }
1511 blob_append_sql(&sql, "(%d)", rid);
1512 cnt++;
1513 }
1514 zX[ii] = c;
1515 zX += ii;
1516 }
1517 if( cnt ) db_exec_sql(blob_sql_text(&sql));
1518 blob_reset(&sql);
1519 fossil_free(zToDel);
1520 }
1521
1522 /*
1523 ** COMMAND: test-endpoint
1524 **
1525 ** Usage: fossil test-endpoint BASE TAG ?OPTIONS?
@@ -1698,21 +1554,21 @@
1554 /*
1555 ** WEBPAGE: timeline
1556 **
1557 ** Query parameters:
1558 **
1559 ** a=TIMEORTAG Show events after TIMEORTAG.
1560 ** b=TIMEORTAG Show events before TIMEORTAG.
1561 ** c=TIMEORTAG Show events that happen "circa" TIMEORTAG
1562 ** cf=FILEHASH Show events around the time of the first use of
1563 ** the file with FILEHASH.
1564 ** m=TIMEORTAG Highlight the event at TIMEORTAG, or the closest available
1565 ** event if TIMEORTAG is not part of the timeline. If
1566 ** the t= or r= is used, the m event is added to the timeline
1567 ** if it isn't there already.
1568 ** x=LIST Show check-ins in the comma- or space-separated LIST
1569 ** in addition to check-ins specified by other parameters.
1570 ** sel1=TIMEORTAG Highlight the check-in at TIMEORTAG if it is part of
1571 ** the timeline. Similar to m= except TIMEORTAG must
1572 ** match a check-in that is already in the timeline.
1573 ** sel2=TIMEORTAG Like sel1= but use the secondary highlight.
1574 ** n=COUNT Maximum number of events. "all" for no limit
@@ -1732,63 +1588,71 @@
1588 ** from=CX ... shortest path from CX back to CHECKIN
1589 ** ft=CHECKIN "Forward To": Show decendents forward to CHECKIN
1590 ** d=CX ... from CX up to the time of CHECKIN
1591 ** from=CX ... shortest path from CX up to CHECKIN
1592 ** t=TAG Show only check-ins with the given TAG
1593 ** r=TAG Same as 't=TAG&rel'. Mnemonic: "Related"
1594 ** tl=TAGLIST Same as 't=TAGLIST&ms=brlist'. Mnemonic: "Tag List"
1595 ** rl=TAGLIST Same as 'r=TAGLIST&ms=brlist'. Mnemonic: "Related List"
1596 ** ml=TAGLIST Same as 'tl=TAGLIST&mionly'. Mnemonic: "Merge-in List"
1597 ** sl=TAGLIST "Sort List". Draw TAGLIST branches ordered left to right.
1598 ** rel Show related check-ins as well as those matching t=TAG
1599 ** mionly Show related parents but not related children.
1600 ** nowiki Do not show wiki associated with branch or tag
1601 ** ms=MATCHSTYLE Set tag name match algorithm. One of "exact", "glob",
1602 ** "like", or "regexp".
1603 ** u=USER Only show items associated with USER
1604 ** y=TYPE 'ci', 'w', 't', 'n', 'e', 'f', or 'all'.
1605 ** ss=VIEWSTYLE c: "Compact", v: "Verbose", m: "Modern", j: "Columnar",
1606 * x: "Classic".
1607 ** advm Use the "Advanced" or "Busy" menu design.
1608 ** ng No Graph.
1609 ** ncp Omit cherrypick merges
1610 ** nd Do not highlight the focus check-in
1611 ** nsm Omit the submenu
1612 ** nc Omit all graph colors other than highlights
1613 ** v Show details of files changed
1614 ** vfx Show complete text of forum messages
1615 ** f=CHECKIN Family (immediate parents and children) of CHECKIN
1616 ** from=CHECKIN Path through common ancestor from...
1617 ** to=CHECKIN ... to this
1618 ** to2=CHECKIN ... backup name if to= doesn't resolve
1619 ** shortest ... show only the shortest path
1620 ** rel ... also show related checkins
1621 ** bt=PRIOR ... path from CHECKIN back to PRIOR
1622 ** ft=LATER ... path from CHECKIN forward to LATER
1623 ** me=CHECKIN Most direct path from...
1624 ** you=CHECKIN ... to this
1625 ** rel ... also show related checkins
1626 ** uf=FILE_HASH Show only check-ins that contain the given file version
1627 ** All qualifying check-ins are shown unless there is
1628 ** also an n= or n1= query parameter.
1629 ** chng=GLOBLIST Show only check-ins that involve changes to a file whose
1630 ** name matches one of the comma-separate GLOBLIST
1631 ** brbg Background color determined by branch name
1632 ** ubg Background color determined by user
1633 ** deltabg Background color red for delta manifests or green
1634 ** for baseline manifests
1635 ** namechng Show only check-ins that have filename changes
1636 ** forks Show only forks and their children
1637 ** cherrypicks Show all cherrypicks
1638 ** ym=YYYYMM Show only events for the given year/month
1639 ** yw=YYYYWW Show only events for the given week of the given year
1640 ** yw=YYYYMMDD Show events for the week that includes the given day
1641 ** ymd=YYYYMMDD Show only events on the given day. The use "ymd=now"
1642 ** to see all changes for the current week. Add "z" at end
1643 ** to divide days at UTC instead of localtime days.
1644 ** Use ymd=YYYYMMDD-YYYYMMDD (with optional "z") for a range.
1645 ** year=YYYY Show only events on the given year. The use "year=0"
1646 ** to see all changes for the current year.
1647 ** days=N Show events over the previous N days
1648 ** datefmt=N Override the date format: 0=HH:MM, 1=HH:MM:SS,
1649 ** 2=YYYY-MM-DD HH:MM:SS, 3=YYMMDD HH:MM, and 4 means "off".
1650 ** bisect Show the check-ins that are in the current bisect
1651 ** oldestfirst Show events oldest first.
1652 ** showid Show RIDs
1653 ** showsql Show the SQL used to generate the report
1654 **
1655 ** p= and d= can appear individually or together. If either p= or d=
1656 ** appear, then u=, y=, a=, and b= are ignored.
1657 **
1658 ** If both a= and b= appear then both upper and lower bounds are honored.
@@ -1862,13 +1726,20 @@
1726 int advancedMenu = 0; /* Use the advanced menu design */
1727 char *zPlural; /* Ending for plural forms */
1728 int showCherrypicks = 1; /* True to show cherrypick merges */
1729 int haveParameterN; /* True if n= query parameter present */
1730 int from_to_mode = 0; /* 0: from,to. 1: from,ft 2: from,bt */
1731 int showSql = PB("showsql"); /* True to show the SQL */
1732 Blob allSql; /* Copy of all SQL text */
1733
1734 login_check_credentials();
1735 url_initialize(&url, "timeline");
1736 cgi_query_parameters_to_url(&url);
1737 blob_init(&allSql, 0, 0);
1738
1739 /* The "mionly" query parameter is like "rel", but shows merge-ins only */
1740 if( P("mionly")!=0 ) related = 2;
1741
1742 (void)P_NoBot("ss")
1743 /* "ss" is processed via the udc but at least one spider likes to
1744 ** try to SQL inject via this argument, so let's catch that. */;
1745
@@ -1943,11 +1814,10 @@
1814 */
1815 pd_rid = name_choice("dp","dp2",&zDPName);
1816 if( pd_rid ){
1817 p_rid = d_rid = pd_rid;
1818 }
 
1819 if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum)
1820 || (bisectLocal && !g.perm.Setup)
1821 ){
1822 login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
1823 return;
@@ -1984,48 +1854,52 @@
1854
1855 /* Check for tl=TAGLIST and rl=TAGLIST which are abbreviations for
1856 ** t=TAGLIST&ms=brlist and r=TAGLIST&ms=brlist repectively. */
1857 if( zBrName==0 && zTagName==0 ){
1858 const char *z;
1859 const char *zPattern = 0;
1860 if( (z = P("tl"))!=0 ){
1861 zPattern = zTagName = z;
1862 }else if( (z = P("rl"))!=0 ){
1863 zPattern = zBrName = z;
1864 if( related==0 ) related = 1;
1865 }else if( (z = P("ml"))!=0 ){
1866 zPattern = zBrName = z;
1867 if( related==0 ) related = 2;
1868 }
1869 if( zPattern!=0 && zMatchStyle==0 ){
1870 /* If there was no ms= query parameter, set the match style to
1871 ** "glob" if the pattern appears to contain GLOB character, or
1872 ** "brlist" if it does not. */
1873 if( strpbrk(zPattern,"*[?") ){
1874 zMatchStyle = "glob";
1875 }else{
1876 zMatchStyle = "brlist";
1877 }
1878 }
1879 }
1880
1881 /* Convert r=TAG to t=TAG&rel in order to populate the UI style widgets. */
1882 if( zBrName ){
1883 cgi_delete_query_parameter("r");
1884 cgi_set_query_parameter("t", zBrName); (void)P("t");
1885 cgi_set_query_parameter("rel", "1");
1886 zTagName = zBrName;
1887 if( related==0 ) related = 1;
1888 zType = "ci";
1889 }
1890
1891 /* Ignore empty tag query strings. */
1892 if( zTagName && !*zTagName ){
1893 zTagName = 0;
1894 }
1895
1896 /* Finish preliminary processing of tag match queries. */
1897 matchStyle = match_style(zMatchStyle, MS_EXACT);
1898 if( zTagName ){
1899 zType = "ci";
1900 if( matchStyle==MS_EXACT ){
 
 
 
 
 
 
 
 
 
1901 /* For exact maching, inhibit links to the selected tag. */
1902 zThisTag = zTagName;
1903 Th_Store("current_checkin", zTagName);
1904 }
1905
@@ -2033,11 +1907,11 @@
1907 if( advancedMenu ){
1908 style_submenu_checkbox("rel", "Related", 0, 0);
1909 }
1910
1911 /* Construct the tag match expression. */
1912 zTagSql = match_tag_sqlexpr(matchStyle, zTagName, &zMatchDesc, &zError);
1913 }
1914
1915 if( zMark && zMark[0]==0 ){
1916 if( zAfter ) zMark = zAfter;
1917 if( zBefore ) zMark = zBefore;
@@ -2081,10 +1955,11 @@
1955 }
1956 if( PB("nc") ){
1957 tmFlags &= ~(TIMELINE_DELTA|TIMELINE_BRCOLOR|TIMELINE_UCOLOR);
1958 tmFlags |= TIMELINE_NOCOLOR;
1959 }
1960 if( showSql ) db_append_dml_to_blob(&allSql);
1961 if( zUses!=0 ){
1962 int ufid = db_int(0, "SELECT rid FROM blob WHERE uuid GLOB '%q*'", zUses);
1963 if( ufid ){
1964 zUses = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", ufid);
1965 db_multi_exec("CREATE TEMP TABLE usesfile(rid INTEGER PRIMARY KEY)");
@@ -2098,42 +1973,42 @@
1973 }
1974 if( renameOnly ){
1975 db_multi_exec(
1976 "CREATE TEMP TABLE rnfile(rid INTEGER PRIMARY KEY);"
1977 "INSERT OR IGNORE INTO rnfile"
1978 " SELECT mid FROM mlink WHERE pfnid>0 AND pfnid!=fnid;"
1979 );
1980 disableY = 1;
1981 }
1982 if( forkOnly ){
1983 db_multi_exec(
1984 "CREATE TEMP TABLE rnfork(rid INTEGER PRIMARY KEY);\n"
1985 "INSERT OR IGNORE INTO rnfork(rid)\n"
1986 " SELECT pid FROM plink\n"
1987 " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n"
1988 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
1989 " GROUP BY pid\n"
1990 " HAVING count(*)>1;\n"
1991 "INSERT OR IGNORE INTO rnfork(rid)\n"
1992 " SELECT cid FROM plink\n"
1993 " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n"
1994 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
1995 " GROUP BY cid\n"
1996 " HAVING count(*)>1;\n",
1997 TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
1998 );
1999 db_multi_exec(
2000 "INSERT OR IGNORE INTO rnfork(rid)\n"
2001 " SELECT cid FROM plink\n"
2002 " WHERE pid IN rnfork\n"
2003 " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n"
2004 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n"
2005 " UNION\n"
2006 " SELECT pid FROM plink\n"
2007 " WHERE cid IN rnfork\n"
2008 " AND (SELECT value FROM tagxref WHERE tagid=%d AND rid=cid)==\n"
2009 " (SELECT value FROM tagxref WHERE tagid=%d AND rid=pid)\n",
2010 TAG_BRANCH, TAG_BRANCH, TAG_BRANCH, TAG_BRANCH
2011 );
2012 tmFlags |= TIMELINE_UNHIDE;
2013 zType = "ci";
2014 disableY = 1;
@@ -2206,77 +2081,114 @@
2081 PathNode *p = 0;
2082 const char *zFrom = 0;
2083 const char *zTo = 0;
2084 Blob ins;
2085 int nNodeOnPath = 0;
2086 int commonAncs = 0; /* Common ancestors of me_rid and you_rid. */
2087 int earlierRid = 0, laterRid = 0;
2088
2089 if( from_rid && to_rid ){
2090 if( from_to_mode==0 ){
2091 p = path_shortest(from_rid, to_rid, noMerge, 0, 0);
2092 }else if( from_to_mode==1 ){
2093 p = path_shortest(from_rid, to_rid, 0, 1, 0);
2094 earlierRid = commonAncs = from_rid;
2095 laterRid = to_rid;
2096 }else{
2097 p = path_shortest(to_rid, from_rid, 0, 1, 0);
2098 earlierRid = commonAncs = to_rid;
2099 laterRid = from_rid;
2100 }
2101 zFrom = P("from");
2102 zTo = zTo2 ? zTo2 : P("to");
2103 }else{
2104 commonAncs = path_common_ancestor(me_rid, you_rid);
2105 if( commonAncs!=0 ){
2106 p = path_first();
2107 }
2108 if( commonAncs==you_rid ){
2109 zFrom = P("you");
2110 zTo = P("me");
2111 earlierRid = you_rid;
2112 laterRid = me_rid;
2113 }else{
2114 zFrom = P("me");
2115 zTo = P("you");
2116 earlierRid = me_rid;
2117 laterRid = you_rid;
2118 }
2119 }
2120 blob_init(&ins, 0, 0);
2121 db_multi_exec(
2122 "CREATE TEMP TABLE IF NOT EXISTS pathnode(x INTEGER PRIMARY KEY);"
2123 );
2124 if( p ){
2125 int cnt = 4;
2126 blob_init(&ins, 0, 0);
2127 blob_append_sql(&ins, "INSERT INTO pathnode(x) VALUES(%d)", p->rid);
2128 p = p->u.pTo;
2129 while( p ){
2130 if( cnt==8 ){
2131 blob_append_sql(&ins, ",\n (%d)", p->rid);
2132 cnt = 0;
2133 }else{
2134 cnt++;
2135 blob_append_sql(&ins, ",(%d)", p->rid);
2136 }
2137 p = p->u.pTo;
2138 }
2139 }
2140 path_reset();
2141 db_multi_exec("%s", blob_str(&ins)/*safe-for-%s*/);
2142 blob_reset(&ins);
2143 if( related ){
2144 db_multi_exec(
2145 "CREATE TEMP TABLE IF NOT EXISTS related(x INTEGER PRIMARY KEY);"
2146 "INSERT OR IGNORE INTO related(x)"
2147 " SELECT pid FROM plink WHERE cid IN pathnode AND NOT isprim;"
2148 );
2149 if( related==1 ){
2150 db_multi_exec(
2151 "INSERT OR IGNORE INTO related(x)"
2152 " SELECT cid FROM plink WHERE pid IN pathnode;"
2153 );
2154 }
2155 if( showCherrypicks ){
2156 db_multi_exec(
2157 "INSERT OR IGNORE INTO related(x)"
2158 " SELECT parentid FROM cherrypick WHERE childid IN pathnode;"
2159 );
2160 if( related==1 ){
2161 db_multi_exec(
2162 "INSERT OR IGNORE INTO related(x)"
2163 " SELECT childid FROM cherrypick WHERE parentid IN pathnode;"
2164 );
2165 }
2166 }
2167 if( earlierRid && laterRid && commonAncs==earlierRid ){
2168 /* On a query with me=XXX, you=YYY, and rel, omit all nodes that
2169 ** are not ancestors of either XXX or YYY, as those nodes tend to
2170 ** be extraneous */
2171 db_multi_exec(
2172 "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
2173 );
2174 compute_ancestors(laterRid, 0, 0, earlierRid);
2175 db_multi_exec(
2176 "DELETE FROM related WHERE x NOT IN ok;"
2177 );
2178 }
2179 db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related");
2180 }
2181 add_extra_rids("pathnode",P("x"));
2182 blob_append_sql(&sql, " AND event.objid IN pathnode");
2183 if( zChng && zChng[0] ){
2184 db_multi_exec(
2185 "DELETE FROM pathnode\n"
2186 " WHERE NOT EXISTS(SELECT 1 FROM mlink, filename\n"
2187 " WHERE mlink.mid=x\n"
2188 " AND mlink.fnid=filename.fnid\n"
2189 " AND %s)",
2190 glob_expr("filename.name", zChng)
2191 );
2192 }
2193 tmFlags |= TIMELINE_XMERGE | TIMELINE_FILLGAPS;
2194 db_multi_exec("%s", blob_sql_text(&sql));
@@ -2330,18 +2242,18 @@
2242 if( !haveParameterN ) nEntry = 10;
2243 }
2244 db_multi_exec(
2245 "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
2246 );
2247 add_extra_rids("ok", P("x"));
2248 zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d",
2249 p_rid ? p_rid : d_rid);
2250 zCiName = zDPName;
2251 if( zCiName==0 ) zCiName = zUuid;
2252 blob_append_sql(&sql, " AND event.objid IN ok");
2253 nd = 0;
2254 if( d_rid ){
 
2255 double rStopTime = 9e99;
2256 zFwdTo = P("ft");
2257 if( zFwdTo ){
2258 double rStartDate = db_double(0.0,
2259 "SELECT mtime FROM event WHERE objid=%d", d_rid);
@@ -2353,27 +2265,25 @@
2265 if( !haveParameterN ) nEntry = 0;
2266 rStopTime = db_double(9e99,
2267 "SELECT mtime FROM event WHERE objid=%d", ridFwdTo);
2268 }
2269 }
2270 if( rStopTime<9e99 ){
2271 rStopTime += 5.8e-6; /* Round up by 1/2 second */
2272 }
2273 db_multi_exec(
2274 "WITH RECURSIVE dx(rid,mtime) AS (\n"
2275 " SELECT %d, 0\n"
2276 " UNION\n"
2277 " SELECT plink.cid, plink.mtime FROM dx, plink\n"
2278 " WHERE plink.pid=dx.rid\n"
2279 " AND plink.mtime<=%.*g\n"
2280 " ORDER BY 2\n"
2281 ")\n"
2282 "INSERT OR IGNORE INTO ok SELECT rid FROM dx LIMIT %d",
2283 d_rid, rStopTime<8e99 ? 17 : 2, rStopTime, nEntry<=0 ? -1 : nEntry+1
2284 );
 
 
 
 
2285 nd = db_int(0, "SELECT count(*)-1 FROM ok");
2286 if( nd>=0 ) db_multi_exec("%s", blob_sql_text(&sql));
2287 if( nd>0 || p_rid==0 ){
2288 blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s");
2289 }
@@ -2482,67 +2392,76 @@
2392 if( zChng && *zChng ){
2393 addFileGlobExclusion(zChng, &cond);
2394 tmFlags |= TIMELINE_XMERGE;
2395 }
2396 if( zUses ){
2397 blob_append_sql(&cond, " AND event.objid IN usesfile\n");
2398 }
2399 if( renameOnly ){
2400 blob_append_sql(&cond, " AND event.objid IN rnfile\n");
2401 }
2402 if( forkOnly ){
2403 blob_append_sql(&cond, " AND event.objid IN rnfork\n");
2404 }
2405 if( cpOnly && showCherrypicks ){
2406 db_multi_exec(
2407 "CREATE TEMP TABLE IF NOT EXISTS cpnodes(rid INTEGER PRIMARY KEY);"
2408 "INSERT OR IGNORE INTO cpnodes SELECT childid FROM cherrypick;"
2409 "INSERT OR IGNORE INTO cpnodes SELECT parentid FROM cherrypick;"
2410 );
2411 blob_append_sql(&cond, " AND event.objid IN cpnodes\n");
2412 }
2413 if( bisectLocal || zBisect!=0 ){
2414 blob_append_sql(&cond, " AND event.objid IN (SELECT rid FROM bilog)\n");
2415 }
2416 if( zYearMonth ){
2417 char *zNext;
2418 int bZulu = 0;
2419 const char *zTZMod;
2420 zYearMonth = timeline_expand_datetime(zYearMonth, &bZulu);
2421 zYearMonth = mprintf("%.7s", zYearMonth);
2422 if( db_int(0,"SELECT julianday('%q-01') IS NULL", zYearMonth) ){
2423 zYearMonth = db_text(0, "SELECT strftime('%%Y-%%m','now');");
2424 }
2425 zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00";
2426 if( db_int(0,
2427 "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2428 " WHERE blob.rid=event.objid"
2429 " AND mtime>=julianday('%q-01',%Q)%s)",
2430 zYearMonth, zTZMod, blob_sql_text(&cond))
2431 ){
2432 zNext = db_text(0, "SELECT strftime('%%Y%%m%q','%q-01','+1 month');",
2433 &"Z"[!bZulu], zYearMonth);
2434 zNewerButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0));
2435 zNewerButtonLabel = "Following month";
2436 fossil_free(zNext);
2437 }
2438 if( db_int(0,
2439 "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2440 " WHERE blob.rid=event.objid"
2441 " AND mtime<julianday('%q-01',%Q)%s)",
2442 zYearMonth, zTZMod, blob_sql_text(&cond))
2443 ){
2444 zNext = db_text(0, "SELECT strftime('%%Y%%m%q','%q-01','-1 month');",
2445 &"Z"[!bZulu], zYearMonth);
2446 zOlderButton = fossil_strdup(url_render(&url, "ym", zNext, 0, 0));
2447 zOlderButtonLabel = "Previous month";
2448 fossil_free(zNext);
2449 }
2450 blob_append_sql(&cond,
2451 " AND event.mtime>=julianday('%q-01',%Q)"
2452 " AND event.mtime<julianday('%q-01',%Q,'+1 month')\n",
2453 zYearMonth, zTZMod, zYearMonth, zTZMod);
2454 nEntry = -1;
2455 /* Adjust the zYearMonth for the title */
2456 zYearMonth = mprintf("%z-01%s", zYearMonth, &"Z"[!bZulu]);
2457 }
2458 else if( zYearWeek ){
2459 char *z, *zNext;
2460 int bZulu = 0;
2461 const char *zTZMod;
2462 zYearWeek = timeline_expand_datetime(zYearWeek, &bZulu);
2463 z = db_text(0, "SELECT strftime('%%Y-%%W',%Q)", zYearWeek);
2464 if( z && z[0] ){
2465 zYearWeekStart = db_text(0, "SELECT date(%Q,'-6 days','weekday 1')",
2466 zYearWeek);
2467 zYearWeek = z;
@@ -2559,64 +2478,152 @@
2478 "SELECT date('now','-6 days','weekday 1');");
2479 zYearWeek = db_text(0,
2480 "SELECT strftime('%%Y-%%W','now','-6 days','weekday 1')");
2481 }
2482 }
2483 zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00";
2484 if( db_int(0,
2485 "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2486 " WHERE blob.rid=event.objid"
2487 " AND mtime>=julianday(%Q,%Q)%s)",
2488 zYearWeekStart, zTZMod, blob_sql_text(&cond))
2489 ){
2490 zNext = db_text(0, "SELECT strftime('%%Y%%W%q',%Q,'+7 day');",
2491 &"Z"[!bZulu], zYearWeekStart);
2492 zNewerButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0));
2493 zNewerButtonLabel = "Following week";
2494 fossil_free(zNext);
2495 }
2496 if( db_int(0,
2497 "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2498 " WHERE blob.rid=event.objid"
2499 " AND mtime<julianday(%Q,%Q)%s)",
2500 zYearWeekStart, zTZMod, blob_sql_text(&cond))
2501 ){
2502 zNext = db_text(0, "SELECT strftime('%%Y%%W%q',%Q,'-7 days');",
2503 &"Z"[!bZulu], zYearWeekStart);
2504 zOlderButton = fossil_strdup(url_render(&url, "yw", zNext, 0, 0));
2505 zOlderButtonLabel = "Previous week";
2506 fossil_free(zNext);
2507 }
2508 blob_append_sql(&cond,
2509 " AND event.mtime>=julianday(%Q,%Q)"
2510 " AND event.mtime<julianday(%Q,%Q,'+7 days')\n",
2511 zYearWeekStart, zTZMod, zYearWeekStart, zTZMod);
2512 nEntry = -1;
2513 if( fossil_ui_localtime() && bZulu ){
2514 zYearWeekStart = mprintf("%zZ", zYearWeekStart);
2515 }
2516 }
2517 else if( zDay && timeline_is_datespan(zDay) ){
2518 char *zNext;
2519 char *zStart, *zEnd;
2520 int nDay;
2521 int bZulu = 0;
2522 const char *zTZMod;
2523 zEnd = db_text(0, "SELECT date(%Q)",
2524 timeline_expand_datetime(zDay+9, &bZulu));
2525 zStart = db_text(0, "SELECT date('%.4q-%.2q-%.2q')",
2526 zDay, zDay+4, zDay+6);
2527 nDay = db_int(0, "SELECT julianday(%Q)-julianday(%Q)", zEnd, zStart);
2528 if( nDay==0 ){
2529 zDay = &zDay[9];
2530 goto single_ymd;
2531 }
2532 if( nDay<0 ){
2533 char *zTemp = zEnd;
2534 zEnd = zStart;
2535 zStart = zTemp;
2536 nDay = 1 - nDay;
2537 }else{
2538 nDay += 1;
2539 }
2540 zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00";
2541 if( nDay>0 && db_int(0,
2542 "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2543 " WHERE blob.rid=event.objid"
2544 " AND mtime>=julianday(%Q,'1 day',%Q)%s)",
2545 zEnd, zTZMod, blob_sql_text(&cond))
2546 ){
2547 zNext = db_text(0,
2548 "SELECT strftime('%%Y%%m%%d-',%Q,'%d days')||"
2549 "strftime('%%Y%%m%%d%q',%Q,'%d day');",
2550 zStart, nDay, &"Z"[!bZulu], zEnd, nDay);
2551 zNewerButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
2552 zNewerButtonLabel = mprintf("Following %d days", nDay);
2553 fossil_free(zNext);
2554 }
2555 if( nDay>1 && db_int(0,
2556 "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2557 " WHERE blob.rid=event.objid"
2558 " AND mtime<julianday(%Q,'-1 day',%Q)%s)",
2559 zStart, zTZMod, blob_sql_text(&cond))
2560 ){
2561 zNext = db_text(0,
2562 "SELECT strftime('%%Y%%m%%d-',%Q,'%d days')||"
2563 "strftime('%%Y%%m%%d%q',%Q,'%d day');",
2564 zStart, -nDay, &"Z"[!bZulu], zEnd, -nDay);
2565 zOlderButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
2566 zOlderButtonLabel = mprintf("Previous %d days", nDay);
2567 fossil_free(zNext);
2568 }
2569 blob_append_sql(&cond,
2570 " AND event.mtime>=julianday(%Q,%Q)"
2571 " AND event.mtime<julianday(%Q,%Q,'+1 day')\n",
2572 zStart, zTZMod, zEnd, zTZMod);
2573 nEntry = -1;
2574
2575 if( fossil_ui_localtime() && bZulu ){
2576 zDay = mprintf("%d days between %zZ and %zZ", nDay, zStart, zEnd);
2577 }else{
2578 zDay = mprintf("%d days between %z and %z", nDay, zStart, zEnd);
2579 }
2580 }
2581 else if( zDay ){
2582 char *zNext;
2583 int bZulu = 0;
2584 const char *zTZMod;
2585 single_ymd:
2586 bZulu = 0;
2587 zDay = timeline_expand_datetime(zDay, &bZulu);
2588 zDay = db_text(0, "SELECT date(%Q)", zDay);
2589 if( zDay==0 || zDay[0]==0 ){
2590 zDay = db_text(0, "SELECT date('now')");
2591 }
2592 zTZMod = (bZulu==0 && fossil_ui_localtime()) ? "utc" : "+00:00";
2593 if( db_int(0,
2594 "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2595 " WHERE blob.rid=event.objid"
2596 " AND mtime>=julianday(%Q,'+1 day',%Q)%s)",
2597 zDay, zTZMod, blob_sql_text(&cond))
2598 ){
2599 zNext = db_text(0,"SELECT strftime('%%Y%%m%%d%q',%Q,'+1 day');",
2600 &"Z"[!bZulu], zDay);
2601 zNewerButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
2602 zNewerButtonLabel = "Following day";
2603 fossil_free(zNext);
2604 }
2605 if( db_int(0,
2606 "SELECT EXISTS (SELECT 1 FROM event CROSS JOIN blob"
2607 " WHERE blob.rid=event.objid"
2608 " AND mtime<julianday(%Q,'-1 day',%Q)%s)",
2609 zDay, zTZMod, blob_sql_text(&cond))
2610 ){
2611 zNext = db_text(0,"SELECT strftime('%%Y%%m%%d%q',%Q,'-1 day');",
2612 &"Z"[!bZulu], zDay);
2613 zOlderButton = fossil_strdup(url_render(&url, "ymd", zNext, 0, 0));
2614 zOlderButtonLabel = "Previous day";
2615 fossil_free(zNext);
2616 }
2617 blob_append_sql(&cond,
2618 " AND event.mtime>=julianday(%Q,%Q)"
2619 " AND event.mtime<julianday(%Q,%Q,'+1 day')\n",
2620 zDay, zTZMod, zDay, zTZMod);
2621 nEntry = -1;
2622 if( fossil_ui_localtime() && bZulu ){
2623 zDay = mprintf("%zZ", zDay); /* Add Z suffix to day for the title */
2624 }
2625 }
2626 else if( zNDays ){
2627 nDays = atoi(zNDays);
2628 if( nDays<1 ) nDays = 1;
2629 blob_append_sql(&cond, " AND event.mtime>=julianday('now','-%d days') ",
@@ -2662,39 +2669,24 @@
2669 nEntry = -1;
2670 }
2671 if( zTagSql ){
2672 db_multi_exec(
2673 "CREATE TEMP TABLE selected_nodes(rid INTEGER PRIMARY KEY);"
2674 "INSERT OR IGNORE INTO selected_nodes\n"
2675 " SELECT tagxref.rid FROM tagxref NATURAL JOIN tag\n"
2676 " WHERE tagtype>0\n"
2677 " AND %s", zTagSql/*safe-for-%s*/
2678 );
2679 if( zMark ){
2680 /* If the t=release option is used with m=UUID, then also
2681 ** include the UUID check-in in the display list */
2682 int ridMark = name_to_rid(zMark);
2683 db_multi_exec(
2684 "INSERT OR IGNORE INTO selected_nodes(rid) VALUES(%d)", ridMark);
2685 }
2686 add_extra_rids("selected_nodes",P("x"));
2687 if( related==0 ){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2688 blob_append_sql(&cond, " AND blob.rid IN selected_nodes");
2689 }else{
2690 db_multi_exec(
2691 "CREATE TEMP TABLE related_nodes(rid INTEGER PRIMARY KEY);"
2692 "INSERT INTO related_nodes SELECT rid FROM selected_nodes;"
@@ -2705,40 +2697,42 @@
2697 ** branch to be included in the report. These related check-ins are
2698 ** useful in helping to visualize what has happened on a quiescent
2699 ** branch that is infrequently merged with a much more activate branch.
2700 */
2701 db_multi_exec(
2702 "INSERT OR IGNORE INTO related_nodes\n"
2703 " SELECT pid FROM selected_nodes CROSS JOIN plink\n"
2704 " WHERE selected_nodes.rid=plink.cid;"
2705 );
2706 if( related==1 ){
2707 db_multi_exec(
2708 "INSERT OR IGNORE INTO related_nodes\n"
2709 " SELECT cid FROM selected_nodes CROSS JOIN plink\n"
2710 " WHERE selected_nodes.rid=plink.pid;"
2711 );
2712 if( showCherrypicks ){
2713 db_multi_exec(
2714 "INSERT OR IGNORE INTO related_nodes\n"
2715 " SELECT childid FROM selected_nodes CROSS JOIN cherrypick\n"
2716 " WHERE selected_nodes.rid=cherrypick.parentid;"
2717 );
2718 }
2719 }
2720 if( showCherrypicks ){
2721 db_multi_exec(
2722 "INSERT OR IGNORE INTO related_nodes\n"
2723 " SELECT parentid FROM selected_nodes CROSS JOIN cherrypick\n"
2724 " WHERE selected_nodes.rid=cherrypick.childid;"
2725 );
2726 }
2727 if( (tmFlags & TIMELINE_UNHIDE)==0 ){
2728 db_multi_exec(
2729 "DELETE FROM related_nodes\n"
2730 " WHERE rid IN (SELECT related_nodes.rid\n"
2731 " FROM related_nodes, tagxref\n"
2732 " WHERE tagid=%d AND tagtype>0\n"
2733 " AND tagxref.rid=related_nodes.rid)",
2734 TAG_HIDDEN
2735 );
2736 }
2737 }
2738 }
@@ -2828,45 +2822,42 @@
2822 rCirca = symbolic_name_to_mtime(zCirca, &zCirca);
2823 blob_append_sql(&sql, "%s", blob_sql_text(&cond));
2824 if( rAfter>0.0 ){
2825 if( rBefore>0.0 ){
2826 blob_append_sql(&sql,
2827 " AND event.mtime>=%.17g AND event.mtime<=%.17g\n"
2828 " ORDER BY event.mtime ASC", rAfter-ONE_SECOND, rBefore+ONE_SECOND);
2829 nEntry = -1;
2830 }else{
2831 blob_append_sql(&sql,
2832 " AND event.mtime>=%.17g\n ORDER BY event.mtime ASC",
2833 rAfter-ONE_SECOND);
2834 }
2835 zCirca = 0;
2836 url_add_parameter(&url, "c", 0);
2837 }else if( rBefore>0.0 ){
2838 blob_append_sql(&sql,
2839 " AND event.mtime<=%.17g\n ORDER BY event.mtime DESC",
2840 rBefore+ONE_SECOND);
2841 zCirca = 0;
2842 url_add_parameter(&url, "c", 0);
2843 }else if( rCirca>0.0 ){
2844 Blob sql2;
2845 blob_init(&sql2, blob_sql_text(&sql), -1);
2846 blob_append_sql(&sql2,
2847 " AND event.mtime>=%f\n ORDER BY event.mtime ASC", rCirca);
2848 if( nEntry>0 ){
2849 blob_append_sql(&sql2," LIMIT %d", (nEntry+1)/2);
2850 }
 
 
 
2851 db_multi_exec("%s", blob_sql_text(&sql2));
2852 if( nEntry>0 ){
2853 nEntry -= db_int(0,"select count(*) from timeline");
2854 if( nEntry<=0 ) nEntry = 1;
2855 }
2856 blob_reset(&sql2);
2857 blob_append_sql(&sql,
2858 " AND event.mtime<=%f\n ORDER BY event.mtime DESC",
2859 rCirca
2860 );
2861 if( zMark==0 ) zMark = zCirca;
2862 }else{
2863 blob_append_sql(&sql, " ORDER BY event.mtime DESC");
@@ -2875,11 +2866,11 @@
2866 db_multi_exec("%s", blob_sql_text(&sql));
2867
2868 n = db_int(0, "SELECT count(*) FROM timeline WHERE etype!='div' /*scan*/");
2869 zPlural = n==1 ? "" : "s";
2870 if( zYearMonth ){
2871 blob_appendf(&desc, "%d %s%s for the month beginning %h",
2872 n, zEType, zPlural, zYearMonth);
2873 }else if( zYearWeek ){
2874 blob_appendf(&desc, "%d %s%s for week %h beginning on %h",
2875 n, zEType, zPlural, zYearWeek, zYearWeekStart);
2876 }else if( zDay ){
@@ -3009,12 +3000,14 @@
3000 style_submenu_multichoice("ms", count(azMatchStyles)/2,azMatchStyles,0);
3001 }
3002 }
3003 blob_zero(&cond);
3004 }
3005 if( showSql ){
3006 db_append_dml_to_blob(0);
3007 @ <pre>%h(blob_str(&allSql))</pre>
3008 blob_reset(&allSql);
3009 }
3010 if( search_restrict(SRCH_CKIN)!=0 ){
3011 style_submenu_element("Search", "%R/search?y=c");
3012 }
3013 if( advancedMenu ){
@@ -3068,18 +3061,36 @@
3061 if( zNewerButton ){
3062 @ %z(chref("button","%s",zNewerButton))%h(zNewerButtonLabel)\
3063 @ &nbsp;&uarr;</a>
3064 }
3065 cgi_check_for_malice();
3066 {
3067 Matcher *pLeftBranch;
3068 const char *zPattern = P("sl");
3069 if( zPattern!=0 ){
3070 MatchStyle ms;
3071 if( zMatchStyle!=0 ){
3072 ms = matchStyle;
3073 }else{
3074 ms = strpbrk(zPattern,"*[?")!=0 ? MS_GLOB : MS_BRLIST;
3075 }
3076 pLeftBranch = match_create(ms,zPattern);
3077 }else{
3078 pLeftBranch = match_create(matchStyle, zBrName?zBrName:zTagName);
3079 }
3080 www_print_timeline(&q, tmFlags, zThisUser, zThisTag, pLeftBranch,
3081 selectedRid, secondaryRid, 0);
3082 match_free(pLeftBranch);
3083 }
3084 db_finalize(&q);
3085 if( zOlderButton ){
3086 @ %z(chref("button","%s",zOlderButton))%h(zOlderButtonLabel)\
3087 @ &nbsp;&darr;</a>
3088 }
3089 document_emit_js(/*handles pikchrs rendered above*/);
3090 blob_reset(&sql);
3091 blob_reset(&desc);
3092 style_finish_page();
3093 }
3094
3095 /*
3096 ** Translate a timeline entry into the printable format by
@@ -3744,10 +3755,11 @@
3755 const char *zToday;
3756 char *zStartOfProject;
3757 int i;
3758 Stmt q;
3759 char *z;
3760 int bZulu = 0;
3761
3762 login_check_credentials();
3763 if( (!g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum) ){
3764 login_needed(g.anon.Read && g.anon.RdTkt && g.anon.RdWiki);
3765 return;
@@ -3754,11 +3766,11 @@
3766 }
3767 style_set_current_feature("timeline");
3768 style_header("Today In History");
3769 zToday = (char*)P("today");
3770 if( zToday ){
3771 zToday = timeline_expand_datetime(zToday, &bZulu);
3772 if( !fossil_isdate(zToday) ) zToday = 0;
3773 }
3774 if( zToday==0 ){
3775 zToday = db_text(0, "SELECT date('now',toLocal())");
3776 }
3777
+1 -1
--- src/tkt.c
+++ src/tkt.c
@@ -781,11 +781,11 @@
781781
782782
zFullName = db_text(0,
783783
"SELECT tkt_uuid FROM ticket"
784784
" WHERE tkt_uuid GLOB '%q*'", zUuid);
785785
if( zFullName ){
786
- attachment_list(zFullName, "<hr><h2>Attachments:</h2><ul>");
786
+ attachment_list(zFullName, "<h2>Attachments:</h2>", 1);
787787
}
788788
789789
style_finish_page();
790790
}
791791
792792
--- src/tkt.c
+++ src/tkt.c
@@ -781,11 +781,11 @@
781
782 zFullName = db_text(0,
783 "SELECT tkt_uuid FROM ticket"
784 " WHERE tkt_uuid GLOB '%q*'", zUuid);
785 if( zFullName ){
786 attachment_list(zFullName, "<hr><h2>Attachments:</h2><ul>");
787 }
788
789 style_finish_page();
790 }
791
792
--- src/tkt.c
+++ src/tkt.c
@@ -781,11 +781,11 @@
781
782 zFullName = db_text(0,
783 "SELECT tkt_uuid FROM ticket"
784 " WHERE tkt_uuid GLOB '%q*'", zUuid);
785 if( zFullName ){
786 attachment_list(zFullName, "<h2>Attachments:</h2>", 1);
787 }
788
789 style_finish_page();
790 }
791
792
+71 -13
--- src/update.c
+++ src/update.c
@@ -132,10 +132,13 @@
132132
int nUpdate = 0; /* Number of changes of any kind */
133133
int bNosync = 0; /* --nosync. Omit the auto-sync */
134134
int width; /* Width of printed comment lines */
135135
Stmt mtimeXfer; /* Statement to transfer mtimes */
136136
const char *zWidth; /* Width option string value */
137
+ const char *zCurBrName; /* Current branch name */
138
+ const char *zNewBrName; /* New branch name */
139
+ const char *zBrChgMsg = ""; /* Message to display if branch changes */
137140
138141
if( !internalUpdate ){
139142
undo_capture_command_line();
140143
url_proxy_options();
141144
}
@@ -163,10 +166,11 @@
163166
/* We should be done with options.. */
164167
verify_all_options();
165168
166169
db_must_be_within_tree();
167170
vid = db_lget_int("checkout", 0);
171
+ zCurBrName = branch_of_rid(vid);
168172
user_select();
169173
if( !dryRunFlag && !internalUpdate && !bNosync ){
170174
if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag, 1, "update") ){
171175
fossil_fatal("update abandoned due to sync failure");
172176
}
@@ -403,10 +407,11 @@
403407
" WHERE id=:idt"
404408
);
405409
assert( g.zLocalRoot!=0 );
406410
assert( strlen(g.zLocalRoot)>0 );
407411
assert( g.zLocalRoot[strlen(g.zLocalRoot)-1]=='/' );
412
+ merge_info_init();
408413
while( db_step(&q)==SQLITE_ROW ){
409414
const char *zName = db_column_text(&q, 0); /* The filename from root */
410415
int idv = db_column_int(&q, 1); /* VFILE entry for current */
411416
int ridv = db_column_int(&q, 2); /* RecordID for current */
412417
int idt = db_column_int(&q, 3); /* VFILE entry for target */
@@ -418,13 +423,18 @@
418423
int islinkt = db_column_int(&q, 9); /* Is target file is a link */
419424
int deleted = db_column_int(&q, 10); /* Marked for deletion */
420425
char *zFullPath; /* Full pathname of the file */
421426
char *zFullNewPath; /* Full pathname of dest */
422427
char nameChng; /* True if the name changed */
428
+ const char *zOp = 0; /* Type of change. */
429
+ i64 sz = 0; /* Size of the file */
430
+ int nc = 0; /* Number of conflicts */
431
+ const char *zErrMsg = 0; /* Error message */
423432
424433
zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
425434
zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName);
435
+ sz = file_size(zFullNewPath, ExtFILE);
426436
nameChng = fossil_strcmp(zName, zNewName);
427437
nUpdate++;
428438
if( deleted ){
429439
db_multi_exec("UPDATE vfile SET deleted=1 WHERE id=%d", idt);
430440
}
@@ -432,10 +442,13 @@
432442
/* Conflict. This file has been added to the current check-out
433443
** but also exists in the target check-out. Use the current version.
434444
*/
435445
fossil_print("CONFLICT %s\n", zName);
436446
nConflict++;
447
+ zOp = "CONFLICT";
448
+ nc = 1;
449
+ zErrMsg = "duplicate file";
437450
}else if( idt>0 && idv==0 ){
438451
/* File added in the target. */
439452
if( file_isfile_or_link(zFullPath) ){
440453
/* Name of backup file with Original content */
441454
char *zOrig = file_newname(zFullPath, "original", 1);
@@ -444,10 +457,13 @@
444457
fossil_free(zOrig);
445458
fossil_print("ADD %s - overwrites an unmanaged file", zName);
446459
if( !dryRunFlag ) fossil_print(", original copy backed up locally");
447460
fossil_print("\n");
448461
nOverwrite++;
462
+ nc = 1;
463
+ zOp = "CONFLICT";
464
+ zErrMsg = "new file overwrites unmanaged file";
449465
}else{
450466
fossil_print("ADD %s\n", zName);
451467
}
452468
if( !dryRunFlag && !internalUpdate ) undo_save(zName);
453469
if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
@@ -458,16 +474,18 @@
458474
}else{
459475
fossil_print("UPDATE %s\n", zName);
460476
}
461477
if( !dryRunFlag && !internalUpdate ) undo_save(zName);
462478
if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
479
+ zOp = "UPDATE";
463480
}else if( idt>0 && idv>0 && !deleted && file_size(zFullPath, RepoFILE)<0 ){
464481
/* The file missing from the local check-out. Restore it to the
465482
** version that appears in the target. */
466483
fossil_print("UPDATE %s\n", zName);
467484
if( !dryRunFlag && !internalUpdate ) undo_save(zName);
468485
if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
486
+ zOp = "UPDATE";
469487
}else if( idt==0 && idv>0 ){
470488
if( ridv==0 ){
471489
/* Added in current check-out. Continue to hold the file as
472490
** as an addition */
473491
db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv);
@@ -474,10 +492,13 @@
474492
}else if( chnged ){
475493
/* Edited locally but deleted from the target. Do not track the
476494
** file but keep the edited version around. */
477495
fossil_print("CONFLICT %s - edited locally but deleted by update\n",
478496
zName);
497
+ zOp = "CONFLICT";
498
+ zErrMsg = "edited locally but deleted by update";
499
+ nc = 1;
479500
nConflict++;
480501
}else{
481502
fossil_print("REMOVE %s\n", zName);
482503
if( !dryRunFlag && !internalUpdate ) undo_save(zName);
483504
if( !dryRunFlag ){
@@ -493,22 +514,23 @@
493514
zDir = zNext;
494515
}
495516
}
496517
}
497518
}else if( idt>0 && idv>0 && ridt!=ridv && chnged ){
498
- /* Merge the changes in the current tree into the target version */
499
- Blob r, t, v;
500
- int rc;
501519
if( nameChng ){
502520
fossil_print("MERGE %s -> %s\n", zName, zNewName);
503521
}else{
504522
fossil_print("MERGE %s\n", zName);
505523
}
506524
if( islinkv || islinkt ){
507525
fossil_print("***** Cannot merge symlink %s\n", zNewName);
526
+ zOp = "CONFLICT";
508527
nConflict++;
509528
}else{
529
+ /* Merge the changes in the current tree into the target version */
530
+ Blob r, t, v;
531
+ int rc;
510532
unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0;
511533
if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES;
512534
if( !dryRunFlag && !internalUpdate ) undo_save(zName);
513535
content_get(ridt, &t);
514536
content_get(ridv, &v);
@@ -517,12 +539,17 @@
517539
if( !dryRunFlag ){
518540
blob_write_to_file(&r, zFullNewPath);
519541
file_setexe(zFullNewPath, isexe);
520542
}
521543
if( rc>0 ){
544
+ nc = rc;
545
+ zOp = "CONFLICT";
546
+ zErrMsg = "merge conflicts";
522547
fossil_print("***** %d merge conflicts in %s\n", rc, zNewName);
523548
nConflict++;
549
+ }else{
550
+ zOp = "MERGE";
524551
}
525552
}else{
526553
if( !dryRunFlag ){
527554
if( !keepMergeFlag ){
528555
/* Name of backup file with Original content */
@@ -539,16 +566,19 @@
539566
if( !dryRunFlag ){
540567
fossil_print(", original copy backed up locally");
541568
}
542569
fossil_print("\n");
543570
nConflict++;
571
+ zOp = "ERROR";
572
+ zErrMsg = "cannot merge binary file";
573
+ nc = 1;
544574
}
575
+ blob_reset(&v);
576
+ blob_reset(&t);
577
+ blob_reset(&r);
545578
}
546579
if( nameChng && !dryRunFlag ) file_delete(zFullPath);
547
- blob_reset(&v);
548
- blob_reset(&t);
549
- blob_reset(&r);
550580
}else{
551581
nUpdate--;
552582
if( chnged ){
553583
if( verboseFlag ) fossil_print("EDITED %s\n", zName);
554584
}else{
@@ -556,27 +586,48 @@
556586
db_bind_int(&mtimeXfer, ":idt", idt);
557587
db_step(&mtimeXfer);
558588
db_reset(&mtimeXfer);
559589
if( verboseFlag ) fossil_print("UNCHANGED %s\n", zName);
560590
}
591
+ }
592
+ if( zOp!=0 ){
593
+ db_multi_exec(
594
+ "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)"
595
+ "VALUES(%Q,%Q,%d,%Q,NULL,%lld,%Q,%d,%Q,%d,%Q)",
596
+ /* op */ zOp,
597
+ /* fnp */ zName,
598
+ /* ridp */ ridv,
599
+ /* fn */ zNewName,
600
+ /* sz */ sz,
601
+ /* fnm */ zName,
602
+ /* ridm */ ridt,
603
+ /* fnr */ zNewName,
604
+ /* nc */ nc,
605
+ /* msg */ zErrMsg
606
+ );
561607
}
562608
free(zFullPath);
563609
free(zFullNewPath);
564610
}
565611
db_finalize(&q);
566612
db_finalize(&mtimeXfer);
567613
fossil_print("%.79c\n",'-');
614
+ zNewBrName = branch_of_rid(tid);
615
+ if( g.argc<3 && fossil_strcmp(zCurBrName, zNewBrName)!=0 ){
616
+ zBrChgMsg = mprintf(" Branch changed from %s to %s.",
617
+ zCurBrName, zNewBrName);
618
+ }
568619
if( nUpdate==0 ){
569620
show_common_info(tid, "checkout:", 1, 0);
570
- fossil_print("%-13s None. Already up-to-date\n", "changes:");
621
+ fossil_print("%-13s None. Already up-to-date.%s\n", "changes:", zBrChgMsg);
571622
}else{
572623
fossil_print("%-13s %.40s %s\n", "updated-from:", rid_to_uuid(vid),
573624
db_text("", "SELECT datetime(mtime) || ' UTC' FROM event "
574625
" WHERE objid=%d", vid));
575626
show_common_info(tid, "updated-to:", 1, 0);
576
- fossil_print("%-13s %d file%s modified.\n", "changes:",
577
- nUpdate, nUpdate>1 ? "s" : "");
627
+ fossil_print("%-13s %d file%s modified.%s\n", "changes:",
628
+ nUpdate, nUpdate>1 ? "s" : "", zBrChgMsg);
578629
}
579630
580631
/* Report on conflicts
581632
*/
582633
if( !dryRunFlag ){
@@ -802,10 +853,11 @@
802853
**
803854
** If a file is reverted accidentally, it can be restored using
804855
** the "fossil undo" command.
805856
**
806857
** Options:
858
+** --noundo Do not record changes in the undo/redo log.
807859
** -r|--revision VERSION Revert given FILE(s) back to given
808860
** VERSION
809861
**
810862
** See also: [[redo]], [[undo]], [[checkout]], [[update]]
811863
*/
@@ -815,17 +867,19 @@
815867
ManifestFile *pCoFile; /* File within current check-out manifest */
816868
ManifestFile *pRvFile; /* File within revert version manifest */
817869
const char *zFile; /* Filename relative to check-out root */
818870
const char *zRevision; /* Selected revert version, NULL if current */
819871
Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */
872
+ int useUndo = 1; /* True to record changes in UNDO */
820873
int i;
821874
Stmt q;
822875
int revertAll = 0;
823876
int revisionOptNotSupported = 0;
824877
825878
undo_capture_command_line();
826879
zRevision = find_option("revision", "r", 1);
880
+ useUndo = find_option("noundo", 0, 0)==0;
827881
verify_all_options();
828882
829883
if( g.argc<2 ){
830884
usage("?OPTIONS? [FILE] ...");
831885
}
@@ -838,11 +892,15 @@
838892
/* Get manifests of revert version and (if different) current check-out. */
839893
pRvManifest = historical_manifest(zRevision);
840894
pCoManifest = zRevision ? historical_manifest(0) : 0;
841895
842896
db_begin_transaction();
843
- undo_begin();
897
+ if( useUndo ){
898
+ undo_begin();
899
+ }else{
900
+ undo_reset();
901
+ }
844902
db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);");
845903
846904
if( g.argc>2 ){
847905
for(i=2; i<g.argc; i++){
848906
Blob fname;
@@ -935,11 +993,11 @@
935993
if( !pRvFile ){
936994
if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q",
937995
zFile, zFile)==0 ){
938996
fossil_print("UNMANAGE %s\n", zFile);
939997
}else{
940
- undo_save(zFile);
998
+ if( useUndo ) undo_save(zFile);
941999
file_delete(zFull);
9421000
fossil_print("DELETE %s\n", zFile);
9431001
}
9441002
db_multi_exec(
9451003
"UPDATE OR REPLACE vfile"
@@ -962,11 +1020,11 @@
9621020
}
9631021
9641022
/* Get contents of reverted-to file. */
9651023
content_get(fast_uuid_to_rid(pRvFile->zUuid), &record);
9661024
967
- undo_save(zFile);
1025
+ if( useUndo ) undo_save(zFile);
9681026
if( file_size(zFull, RepoFILE)>=0
9691027
&& (rvPerm==PERM_LNK || file_islink(0))
9701028
){
9711029
file_delete(zFull);
9721030
}
@@ -988,12 +1046,12 @@
9881046
}
9891047
blob_reset(&record);
9901048
free(zFull);
9911049
}
9921050
db_finalize(&q);
993
- undo_finish();
1051
+ if( useUndo) undo_finish();
9941052
db_end_transaction(0);
9951053
9961054
/* Deallocate parsed manifest structures. */
9971055
manifest_destroy(pRvManifest);
9981056
manifest_destroy(pCoManifest);
9991057
}
10001058
--- src/update.c
+++ src/update.c
@@ -132,10 +132,13 @@
132 int nUpdate = 0; /* Number of changes of any kind */
133 int bNosync = 0; /* --nosync. Omit the auto-sync */
134 int width; /* Width of printed comment lines */
135 Stmt mtimeXfer; /* Statement to transfer mtimes */
136 const char *zWidth; /* Width option string value */
 
 
 
137
138 if( !internalUpdate ){
139 undo_capture_command_line();
140 url_proxy_options();
141 }
@@ -163,10 +166,11 @@
163 /* We should be done with options.. */
164 verify_all_options();
165
166 db_must_be_within_tree();
167 vid = db_lget_int("checkout", 0);
 
168 user_select();
169 if( !dryRunFlag && !internalUpdate && !bNosync ){
170 if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag, 1, "update") ){
171 fossil_fatal("update abandoned due to sync failure");
172 }
@@ -403,10 +407,11 @@
403 " WHERE id=:idt"
404 );
405 assert( g.zLocalRoot!=0 );
406 assert( strlen(g.zLocalRoot)>0 );
407 assert( g.zLocalRoot[strlen(g.zLocalRoot)-1]=='/' );
 
408 while( db_step(&q)==SQLITE_ROW ){
409 const char *zName = db_column_text(&q, 0); /* The filename from root */
410 int idv = db_column_int(&q, 1); /* VFILE entry for current */
411 int ridv = db_column_int(&q, 2); /* RecordID for current */
412 int idt = db_column_int(&q, 3); /* VFILE entry for target */
@@ -418,13 +423,18 @@
418 int islinkt = db_column_int(&q, 9); /* Is target file is a link */
419 int deleted = db_column_int(&q, 10); /* Marked for deletion */
420 char *zFullPath; /* Full pathname of the file */
421 char *zFullNewPath; /* Full pathname of dest */
422 char nameChng; /* True if the name changed */
 
 
 
 
423
424 zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
425 zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName);
 
426 nameChng = fossil_strcmp(zName, zNewName);
427 nUpdate++;
428 if( deleted ){
429 db_multi_exec("UPDATE vfile SET deleted=1 WHERE id=%d", idt);
430 }
@@ -432,10 +442,13 @@
432 /* Conflict. This file has been added to the current check-out
433 ** but also exists in the target check-out. Use the current version.
434 */
435 fossil_print("CONFLICT %s\n", zName);
436 nConflict++;
 
 
 
437 }else if( idt>0 && idv==0 ){
438 /* File added in the target. */
439 if( file_isfile_or_link(zFullPath) ){
440 /* Name of backup file with Original content */
441 char *zOrig = file_newname(zFullPath, "original", 1);
@@ -444,10 +457,13 @@
444 fossil_free(zOrig);
445 fossil_print("ADD %s - overwrites an unmanaged file", zName);
446 if( !dryRunFlag ) fossil_print(", original copy backed up locally");
447 fossil_print("\n");
448 nOverwrite++;
 
 
 
449 }else{
450 fossil_print("ADD %s\n", zName);
451 }
452 if( !dryRunFlag && !internalUpdate ) undo_save(zName);
453 if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
@@ -458,16 +474,18 @@
458 }else{
459 fossil_print("UPDATE %s\n", zName);
460 }
461 if( !dryRunFlag && !internalUpdate ) undo_save(zName);
462 if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
 
463 }else if( idt>0 && idv>0 && !deleted && file_size(zFullPath, RepoFILE)<0 ){
464 /* The file missing from the local check-out. Restore it to the
465 ** version that appears in the target. */
466 fossil_print("UPDATE %s\n", zName);
467 if( !dryRunFlag && !internalUpdate ) undo_save(zName);
468 if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
 
469 }else if( idt==0 && idv>0 ){
470 if( ridv==0 ){
471 /* Added in current check-out. Continue to hold the file as
472 ** as an addition */
473 db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv);
@@ -474,10 +492,13 @@
474 }else if( chnged ){
475 /* Edited locally but deleted from the target. Do not track the
476 ** file but keep the edited version around. */
477 fossil_print("CONFLICT %s - edited locally but deleted by update\n",
478 zName);
 
 
 
479 nConflict++;
480 }else{
481 fossil_print("REMOVE %s\n", zName);
482 if( !dryRunFlag && !internalUpdate ) undo_save(zName);
483 if( !dryRunFlag ){
@@ -493,22 +514,23 @@
493 zDir = zNext;
494 }
495 }
496 }
497 }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){
498 /* Merge the changes in the current tree into the target version */
499 Blob r, t, v;
500 int rc;
501 if( nameChng ){
502 fossil_print("MERGE %s -> %s\n", zName, zNewName);
503 }else{
504 fossil_print("MERGE %s\n", zName);
505 }
506 if( islinkv || islinkt ){
507 fossil_print("***** Cannot merge symlink %s\n", zNewName);
 
508 nConflict++;
509 }else{
 
 
 
510 unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0;
511 if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES;
512 if( !dryRunFlag && !internalUpdate ) undo_save(zName);
513 content_get(ridt, &t);
514 content_get(ridv, &v);
@@ -517,12 +539,17 @@
517 if( !dryRunFlag ){
518 blob_write_to_file(&r, zFullNewPath);
519 file_setexe(zFullNewPath, isexe);
520 }
521 if( rc>0 ){
 
 
 
522 fossil_print("***** %d merge conflicts in %s\n", rc, zNewName);
523 nConflict++;
 
 
524 }
525 }else{
526 if( !dryRunFlag ){
527 if( !keepMergeFlag ){
528 /* Name of backup file with Original content */
@@ -539,16 +566,19 @@
539 if( !dryRunFlag ){
540 fossil_print(", original copy backed up locally");
541 }
542 fossil_print("\n");
543 nConflict++;
 
 
 
544 }
 
 
 
545 }
546 if( nameChng && !dryRunFlag ) file_delete(zFullPath);
547 blob_reset(&v);
548 blob_reset(&t);
549 blob_reset(&r);
550 }else{
551 nUpdate--;
552 if( chnged ){
553 if( verboseFlag ) fossil_print("EDITED %s\n", zName);
554 }else{
@@ -556,27 +586,48 @@
556 db_bind_int(&mtimeXfer, ":idt", idt);
557 db_step(&mtimeXfer);
558 db_reset(&mtimeXfer);
559 if( verboseFlag ) fossil_print("UNCHANGED %s\n", zName);
560 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561 }
562 free(zFullPath);
563 free(zFullNewPath);
564 }
565 db_finalize(&q);
566 db_finalize(&mtimeXfer);
567 fossil_print("%.79c\n",'-');
 
 
 
 
 
568 if( nUpdate==0 ){
569 show_common_info(tid, "checkout:", 1, 0);
570 fossil_print("%-13s None. Already up-to-date\n", "changes:");
571 }else{
572 fossil_print("%-13s %.40s %s\n", "updated-from:", rid_to_uuid(vid),
573 db_text("", "SELECT datetime(mtime) || ' UTC' FROM event "
574 " WHERE objid=%d", vid));
575 show_common_info(tid, "updated-to:", 1, 0);
576 fossil_print("%-13s %d file%s modified.\n", "changes:",
577 nUpdate, nUpdate>1 ? "s" : "");
578 }
579
580 /* Report on conflicts
581 */
582 if( !dryRunFlag ){
@@ -802,10 +853,11 @@
802 **
803 ** If a file is reverted accidentally, it can be restored using
804 ** the "fossil undo" command.
805 **
806 ** Options:
 
807 ** -r|--revision VERSION Revert given FILE(s) back to given
808 ** VERSION
809 **
810 ** See also: [[redo]], [[undo]], [[checkout]], [[update]]
811 */
@@ -815,17 +867,19 @@
815 ManifestFile *pCoFile; /* File within current check-out manifest */
816 ManifestFile *pRvFile; /* File within revert version manifest */
817 const char *zFile; /* Filename relative to check-out root */
818 const char *zRevision; /* Selected revert version, NULL if current */
819 Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */
 
820 int i;
821 Stmt q;
822 int revertAll = 0;
823 int revisionOptNotSupported = 0;
824
825 undo_capture_command_line();
826 zRevision = find_option("revision", "r", 1);
 
827 verify_all_options();
828
829 if( g.argc<2 ){
830 usage("?OPTIONS? [FILE] ...");
831 }
@@ -838,11 +892,15 @@
838 /* Get manifests of revert version and (if different) current check-out. */
839 pRvManifest = historical_manifest(zRevision);
840 pCoManifest = zRevision ? historical_manifest(0) : 0;
841
842 db_begin_transaction();
843 undo_begin();
 
 
 
 
844 db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);");
845
846 if( g.argc>2 ){
847 for(i=2; i<g.argc; i++){
848 Blob fname;
@@ -935,11 +993,11 @@
935 if( !pRvFile ){
936 if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q",
937 zFile, zFile)==0 ){
938 fossil_print("UNMANAGE %s\n", zFile);
939 }else{
940 undo_save(zFile);
941 file_delete(zFull);
942 fossil_print("DELETE %s\n", zFile);
943 }
944 db_multi_exec(
945 "UPDATE OR REPLACE vfile"
@@ -962,11 +1020,11 @@
962 }
963
964 /* Get contents of reverted-to file. */
965 content_get(fast_uuid_to_rid(pRvFile->zUuid), &record);
966
967 undo_save(zFile);
968 if( file_size(zFull, RepoFILE)>=0
969 && (rvPerm==PERM_LNK || file_islink(0))
970 ){
971 file_delete(zFull);
972 }
@@ -988,12 +1046,12 @@
988 }
989 blob_reset(&record);
990 free(zFull);
991 }
992 db_finalize(&q);
993 undo_finish();
994 db_end_transaction(0);
995
996 /* Deallocate parsed manifest structures. */
997 manifest_destroy(pRvManifest);
998 manifest_destroy(pCoManifest);
999 }
1000
--- src/update.c
+++ src/update.c
@@ -132,10 +132,13 @@
132 int nUpdate = 0; /* Number of changes of any kind */
133 int bNosync = 0; /* --nosync. Omit the auto-sync */
134 int width; /* Width of printed comment lines */
135 Stmt mtimeXfer; /* Statement to transfer mtimes */
136 const char *zWidth; /* Width option string value */
137 const char *zCurBrName; /* Current branch name */
138 const char *zNewBrName; /* New branch name */
139 const char *zBrChgMsg = ""; /* Message to display if branch changes */
140
141 if( !internalUpdate ){
142 undo_capture_command_line();
143 url_proxy_options();
144 }
@@ -163,10 +166,11 @@
166 /* We should be done with options.. */
167 verify_all_options();
168
169 db_must_be_within_tree();
170 vid = db_lget_int("checkout", 0);
171 zCurBrName = branch_of_rid(vid);
172 user_select();
173 if( !dryRunFlag && !internalUpdate && !bNosync ){
174 if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag, 1, "update") ){
175 fossil_fatal("update abandoned due to sync failure");
176 }
@@ -403,10 +407,11 @@
407 " WHERE id=:idt"
408 );
409 assert( g.zLocalRoot!=0 );
410 assert( strlen(g.zLocalRoot)>0 );
411 assert( g.zLocalRoot[strlen(g.zLocalRoot)-1]=='/' );
412 merge_info_init();
413 while( db_step(&q)==SQLITE_ROW ){
414 const char *zName = db_column_text(&q, 0); /* The filename from root */
415 int idv = db_column_int(&q, 1); /* VFILE entry for current */
416 int ridv = db_column_int(&q, 2); /* RecordID for current */
417 int idt = db_column_int(&q, 3); /* VFILE entry for target */
@@ -418,13 +423,18 @@
423 int islinkt = db_column_int(&q, 9); /* Is target file is a link */
424 int deleted = db_column_int(&q, 10); /* Marked for deletion */
425 char *zFullPath; /* Full pathname of the file */
426 char *zFullNewPath; /* Full pathname of dest */
427 char nameChng; /* True if the name changed */
428 const char *zOp = 0; /* Type of change. */
429 i64 sz = 0; /* Size of the file */
430 int nc = 0; /* Number of conflicts */
431 const char *zErrMsg = 0; /* Error message */
432
433 zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
434 zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName);
435 sz = file_size(zFullNewPath, ExtFILE);
436 nameChng = fossil_strcmp(zName, zNewName);
437 nUpdate++;
438 if( deleted ){
439 db_multi_exec("UPDATE vfile SET deleted=1 WHERE id=%d", idt);
440 }
@@ -432,10 +442,13 @@
442 /* Conflict. This file has been added to the current check-out
443 ** but also exists in the target check-out. Use the current version.
444 */
445 fossil_print("CONFLICT %s\n", zName);
446 nConflict++;
447 zOp = "CONFLICT";
448 nc = 1;
449 zErrMsg = "duplicate file";
450 }else if( idt>0 && idv==0 ){
451 /* File added in the target. */
452 if( file_isfile_or_link(zFullPath) ){
453 /* Name of backup file with Original content */
454 char *zOrig = file_newname(zFullPath, "original", 1);
@@ -444,10 +457,13 @@
457 fossil_free(zOrig);
458 fossil_print("ADD %s - overwrites an unmanaged file", zName);
459 if( !dryRunFlag ) fossil_print(", original copy backed up locally");
460 fossil_print("\n");
461 nOverwrite++;
462 nc = 1;
463 zOp = "CONFLICT";
464 zErrMsg = "new file overwrites unmanaged file";
465 }else{
466 fossil_print("ADD %s\n", zName);
467 }
468 if( !dryRunFlag && !internalUpdate ) undo_save(zName);
469 if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
@@ -458,16 +474,18 @@
474 }else{
475 fossil_print("UPDATE %s\n", zName);
476 }
477 if( !dryRunFlag && !internalUpdate ) undo_save(zName);
478 if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
479 zOp = "UPDATE";
480 }else if( idt>0 && idv>0 && !deleted && file_size(zFullPath, RepoFILE)<0 ){
481 /* The file missing from the local check-out. Restore it to the
482 ** version that appears in the target. */
483 fossil_print("UPDATE %s\n", zName);
484 if( !dryRunFlag && !internalUpdate ) undo_save(zName);
485 if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
486 zOp = "UPDATE";
487 }else if( idt==0 && idv>0 ){
488 if( ridv==0 ){
489 /* Added in current check-out. Continue to hold the file as
490 ** as an addition */
491 db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv);
@@ -474,10 +492,13 @@
492 }else if( chnged ){
493 /* Edited locally but deleted from the target. Do not track the
494 ** file but keep the edited version around. */
495 fossil_print("CONFLICT %s - edited locally but deleted by update\n",
496 zName);
497 zOp = "CONFLICT";
498 zErrMsg = "edited locally but deleted by update";
499 nc = 1;
500 nConflict++;
501 }else{
502 fossil_print("REMOVE %s\n", zName);
503 if( !dryRunFlag && !internalUpdate ) undo_save(zName);
504 if( !dryRunFlag ){
@@ -493,22 +514,23 @@
514 zDir = zNext;
515 }
516 }
517 }
518 }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){
 
 
 
519 if( nameChng ){
520 fossil_print("MERGE %s -> %s\n", zName, zNewName);
521 }else{
522 fossil_print("MERGE %s\n", zName);
523 }
524 if( islinkv || islinkt ){
525 fossil_print("***** Cannot merge symlink %s\n", zNewName);
526 zOp = "CONFLICT";
527 nConflict++;
528 }else{
529 /* Merge the changes in the current tree into the target version */
530 Blob r, t, v;
531 int rc;
532 unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0;
533 if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES;
534 if( !dryRunFlag && !internalUpdate ) undo_save(zName);
535 content_get(ridt, &t);
536 content_get(ridv, &v);
@@ -517,12 +539,17 @@
539 if( !dryRunFlag ){
540 blob_write_to_file(&r, zFullNewPath);
541 file_setexe(zFullNewPath, isexe);
542 }
543 if( rc>0 ){
544 nc = rc;
545 zOp = "CONFLICT";
546 zErrMsg = "merge conflicts";
547 fossil_print("***** %d merge conflicts in %s\n", rc, zNewName);
548 nConflict++;
549 }else{
550 zOp = "MERGE";
551 }
552 }else{
553 if( !dryRunFlag ){
554 if( !keepMergeFlag ){
555 /* Name of backup file with Original content */
@@ -539,16 +566,19 @@
566 if( !dryRunFlag ){
567 fossil_print(", original copy backed up locally");
568 }
569 fossil_print("\n");
570 nConflict++;
571 zOp = "ERROR";
572 zErrMsg = "cannot merge binary file";
573 nc = 1;
574 }
575 blob_reset(&v);
576 blob_reset(&t);
577 blob_reset(&r);
578 }
579 if( nameChng && !dryRunFlag ) file_delete(zFullPath);
 
 
 
580 }else{
581 nUpdate--;
582 if( chnged ){
583 if( verboseFlag ) fossil_print("EDITED %s\n", zName);
584 }else{
@@ -556,27 +586,48 @@
586 db_bind_int(&mtimeXfer, ":idt", idt);
587 db_step(&mtimeXfer);
588 db_reset(&mtimeXfer);
589 if( verboseFlag ) fossil_print("UNCHANGED %s\n", zName);
590 }
591 }
592 if( zOp!=0 ){
593 db_multi_exec(
594 "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)"
595 "VALUES(%Q,%Q,%d,%Q,NULL,%lld,%Q,%d,%Q,%d,%Q)",
596 /* op */ zOp,
597 /* fnp */ zName,
598 /* ridp */ ridv,
599 /* fn */ zNewName,
600 /* sz */ sz,
601 /* fnm */ zName,
602 /* ridm */ ridt,
603 /* fnr */ zNewName,
604 /* nc */ nc,
605 /* msg */ zErrMsg
606 );
607 }
608 free(zFullPath);
609 free(zFullNewPath);
610 }
611 db_finalize(&q);
612 db_finalize(&mtimeXfer);
613 fossil_print("%.79c\n",'-');
614 zNewBrName = branch_of_rid(tid);
615 if( g.argc<3 && fossil_strcmp(zCurBrName, zNewBrName)!=0 ){
616 zBrChgMsg = mprintf(" Branch changed from %s to %s.",
617 zCurBrName, zNewBrName);
618 }
619 if( nUpdate==0 ){
620 show_common_info(tid, "checkout:", 1, 0);
621 fossil_print("%-13s None. Already up-to-date.%s\n", "changes:", zBrChgMsg);
622 }else{
623 fossil_print("%-13s %.40s %s\n", "updated-from:", rid_to_uuid(vid),
624 db_text("", "SELECT datetime(mtime) || ' UTC' FROM event "
625 " WHERE objid=%d", vid));
626 show_common_info(tid, "updated-to:", 1, 0);
627 fossil_print("%-13s %d file%s modified.%s\n", "changes:",
628 nUpdate, nUpdate>1 ? "s" : "", zBrChgMsg);
629 }
630
631 /* Report on conflicts
632 */
633 if( !dryRunFlag ){
@@ -802,10 +853,11 @@
853 **
854 ** If a file is reverted accidentally, it can be restored using
855 ** the "fossil undo" command.
856 **
857 ** Options:
858 ** --noundo Do not record changes in the undo/redo log.
859 ** -r|--revision VERSION Revert given FILE(s) back to given
860 ** VERSION
861 **
862 ** See also: [[redo]], [[undo]], [[checkout]], [[update]]
863 */
@@ -815,17 +867,19 @@
867 ManifestFile *pCoFile; /* File within current check-out manifest */
868 ManifestFile *pRvFile; /* File within revert version manifest */
869 const char *zFile; /* Filename relative to check-out root */
870 const char *zRevision; /* Selected revert version, NULL if current */
871 Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */
872 int useUndo = 1; /* True to record changes in UNDO */
873 int i;
874 Stmt q;
875 int revertAll = 0;
876 int revisionOptNotSupported = 0;
877
878 undo_capture_command_line();
879 zRevision = find_option("revision", "r", 1);
880 useUndo = find_option("noundo", 0, 0)==0;
881 verify_all_options();
882
883 if( g.argc<2 ){
884 usage("?OPTIONS? [FILE] ...");
885 }
@@ -838,11 +892,15 @@
892 /* Get manifests of revert version and (if different) current check-out. */
893 pRvManifest = historical_manifest(zRevision);
894 pCoManifest = zRevision ? historical_manifest(0) : 0;
895
896 db_begin_transaction();
897 if( useUndo ){
898 undo_begin();
899 }else{
900 undo_reset();
901 }
902 db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);");
903
904 if( g.argc>2 ){
905 for(i=2; i<g.argc; i++){
906 Blob fname;
@@ -935,11 +993,11 @@
993 if( !pRvFile ){
994 if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q",
995 zFile, zFile)==0 ){
996 fossil_print("UNMANAGE %s\n", zFile);
997 }else{
998 if( useUndo ) undo_save(zFile);
999 file_delete(zFull);
1000 fossil_print("DELETE %s\n", zFile);
1001 }
1002 db_multi_exec(
1003 "UPDATE OR REPLACE vfile"
@@ -962,11 +1020,11 @@
1020 }
1021
1022 /* Get contents of reverted-to file. */
1023 content_get(fast_uuid_to_rid(pRvFile->zUuid), &record);
1024
1025 if( useUndo ) undo_save(zFile);
1026 if( file_size(zFull, RepoFILE)>=0
1027 && (rvPerm==PERM_LNK || file_islink(0))
1028 ){
1029 file_delete(zFull);
1030 }
@@ -988,12 +1046,12 @@
1046 }
1047 blob_reset(&record);
1048 free(zFull);
1049 }
1050 db_finalize(&q);
1051 if( useUndo) undo_finish();
1052 db_end_transaction(0);
1053
1054 /* Deallocate parsed manifest structures. */
1055 manifest_destroy(pRvManifest);
1056 manifest_destroy(pCoManifest);
1057 }
1058
+4 -4
--- src/wiki.c
+++ src/wiki.c
@@ -622,14 +622,14 @@
622622
wiki_render_by_mimetype(&wiki, zMimetype);
623623
blob_reset(&wiki);
624624
}
625625
manifest_destroy(pWiki);
626626
if( !isPopup ){
627
- char * zLabel = mprintf("<hr><h2><a href='%R/attachlist?name=%T'>"
628
- "Attachments</a>:</h2><ul>",
627
+ char * zLabel = mprintf("<h2><a href='%R/attachlist?page=%T'>"
628
+ "Attachments</a>:</h2>",
629629
zPageName);
630
- attachment_list(zPageName, zLabel);
630
+ attachment_list(zPageName, zLabel, 1);
631631
fossil_free(zLabel);
632632
document_emit_js(/*for optional pikchr support*/);
633633
style_finish_page();
634634
}
635635
}
@@ -1331,11 +1331,11 @@
13311331
CX("<div id='fossil-status-bar' "
13321332
"title='Status message area. Double-click to clear them.'>"
13331333
"Status messages will go here.</div>\n"
13341334
/* will be moved into the tab container via JS */);
13351335
1336
- CX("<div id='wikiedit-edit-status''>"
1336
+ CX("<div id='wikiedit-edit-status'>"
13371337
"<span class='name'></span>"
13381338
"<span class='links'></span>"
13391339
"</div>");
13401340
13411341
/* Main tab container... */
13421342
--- src/wiki.c
+++ src/wiki.c
@@ -622,14 +622,14 @@
622 wiki_render_by_mimetype(&wiki, zMimetype);
623 blob_reset(&wiki);
624 }
625 manifest_destroy(pWiki);
626 if( !isPopup ){
627 char * zLabel = mprintf("<hr><h2><a href='%R/attachlist?name=%T'>"
628 "Attachments</a>:</h2><ul>",
629 zPageName);
630 attachment_list(zPageName, zLabel);
631 fossil_free(zLabel);
632 document_emit_js(/*for optional pikchr support*/);
633 style_finish_page();
634 }
635 }
@@ -1331,11 +1331,11 @@
1331 CX("<div id='fossil-status-bar' "
1332 "title='Status message area. Double-click to clear them.'>"
1333 "Status messages will go here.</div>\n"
1334 /* will be moved into the tab container via JS */);
1335
1336 CX("<div id='wikiedit-edit-status''>"
1337 "<span class='name'></span>"
1338 "<span class='links'></span>"
1339 "</div>");
1340
1341 /* Main tab container... */
1342
--- src/wiki.c
+++ src/wiki.c
@@ -622,14 +622,14 @@
622 wiki_render_by_mimetype(&wiki, zMimetype);
623 blob_reset(&wiki);
624 }
625 manifest_destroy(pWiki);
626 if( !isPopup ){
627 char * zLabel = mprintf("<h2><a href='%R/attachlist?page=%T'>"
628 "Attachments</a>:</h2>",
629 zPageName);
630 attachment_list(zPageName, zLabel, 1);
631 fossil_free(zLabel);
632 document_emit_js(/*for optional pikchr support*/);
633 style_finish_page();
634 }
635 }
@@ -1331,11 +1331,11 @@
1331 CX("<div id='fossil-status-bar' "
1332 "title='Status message area. Double-click to clear them.'>"
1333 "Status messages will go here.</div>\n"
1334 /* will be moved into the tab container via JS */);
1335
1336 CX("<div id='wikiedit-edit-status'>"
1337 "<span class='name'></span>"
1338 "<span class='links'></span>"
1339 "</div>");
1340
1341 /* Main tab container... */
1342
--- src/winfile.c
+++ src/winfile.c
@@ -451,6 +451,74 @@
451451
i = j;
452452
}
453453
fossil_free(zBuf);
454454
return zRes;
455455
}
456
+
457
+/* Return the unique identifier (UID) for a file, made up of the file identifier
458
+** (equal to "inode" for Unix-style file systems) plus the volume serial number.
459
+** Call the GetFileInformationByHandleEx() function on Windows Vista, and resort
460
+** to the GetFileInformationByHandle() function on Windows XP. The result string
461
+** is allocated by mprintf(), or NULL on failure.
462
+*/
463
+char *win32_file_id(
464
+ const char *zFileName
465
+){
466
+ static FARPROC fnGetFileInformationByHandleEx;
467
+ static int loaded_fnGetFileInformationByHandleEx;
468
+ wchar_t *wzFileName = fossil_utf8_to_path(zFileName,0);
469
+ HANDLE hFile;
470
+ char *zFileId = 0;
471
+ hFile = CreateFileW(
472
+ wzFileName,
473
+ 0,
474
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
475
+ NULL,
476
+ OPEN_EXISTING,
477
+ FILE_FLAG_BACKUP_SEMANTICS,
478
+ NULL);
479
+ if( hFile!=INVALID_HANDLE_VALUE ){
480
+ BY_HANDLE_FILE_INFORMATION fi;
481
+ struct { /* FILE_ID_INFO from <winbase.h> */
482
+ u64 VolumeSerialNumber;
483
+ unsigned char FileId[16];
484
+ } fi2;
485
+ if( !loaded_fnGetFileInformationByHandleEx ){
486
+ fnGetFileInformationByHandleEx = GetProcAddress(
487
+ GetModuleHandleA("kernel32"),"GetFileInformationByHandleEx");
488
+ loaded_fnGetFileInformationByHandleEx = 1;
489
+ }
490
+ if( fnGetFileInformationByHandleEx ){
491
+ if( fnGetFileInformationByHandleEx(
492
+ hFile,/*FileIdInfo*/0x12,&fi2,sizeof(fi2)) ){
493
+ zFileId = mprintf(
494
+ "%016llx/"
495
+ "%02x%02x%02x%02x%02x%02x%02x%02x"
496
+ "%02x%02x%02x%02x%02x%02x%02x%02x",
497
+ fi2.VolumeSerialNumber,
498
+ fi2.FileId[15], fi2.FileId[14],
499
+ fi2.FileId[13], fi2.FileId[12],
500
+ fi2.FileId[11], fi2.FileId[10],
501
+ fi2.FileId[9], fi2.FileId[8],
502
+ fi2.FileId[7], fi2.FileId[6],
503
+ fi2.FileId[5], fi2.FileId[4],
504
+ fi2.FileId[3], fi2.FileId[2],
505
+ fi2.FileId[1], fi2.FileId[0]);
506
+ }
507
+ }
508
+ if( zFileId==0 ){
509
+ if( GetFileInformationByHandle(hFile,&fi) ){
510
+ ULARGE_INTEGER FileId = {
511
+ /*.LowPart = */ fi.nFileIndexLow,
512
+ /*.HighPart = */ fi.nFileIndexHigh
513
+ };
514
+ zFileId = mprintf(
515
+ "%08x/%016llx",
516
+ fi.dwVolumeSerialNumber,(u64)FileId.QuadPart);
517
+ }
518
+ }
519
+ CloseHandle(hFile);
520
+ }
521
+ fossil_path_free(wzFileName);
522
+ return zFileId;
523
+}
456524
#endif /* _WIN32 -- This code is for win32 only */
457525
--- src/winfile.c
+++ src/winfile.c
@@ -451,6 +451,74 @@
451 i = j;
452 }
453 fossil_free(zBuf);
454 return zRes;
455 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456 #endif /* _WIN32 -- This code is for win32 only */
457
--- src/winfile.c
+++ src/winfile.c
@@ -451,6 +451,74 @@
451 i = j;
452 }
453 fossil_free(zBuf);
454 return zRes;
455 }
456
457 /* Return the unique identifier (UID) for a file, made up of the file identifier
458 ** (equal to "inode" for Unix-style file systems) plus the volume serial number.
459 ** Call the GetFileInformationByHandleEx() function on Windows Vista, and resort
460 ** to the GetFileInformationByHandle() function on Windows XP. The result string
461 ** is allocated by mprintf(), or NULL on failure.
462 */
463 char *win32_file_id(
464 const char *zFileName
465 ){
466 static FARPROC fnGetFileInformationByHandleEx;
467 static int loaded_fnGetFileInformationByHandleEx;
468 wchar_t *wzFileName = fossil_utf8_to_path(zFileName,0);
469 HANDLE hFile;
470 char *zFileId = 0;
471 hFile = CreateFileW(
472 wzFileName,
473 0,
474 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
475 NULL,
476 OPEN_EXISTING,
477 FILE_FLAG_BACKUP_SEMANTICS,
478 NULL);
479 if( hFile!=INVALID_HANDLE_VALUE ){
480 BY_HANDLE_FILE_INFORMATION fi;
481 struct { /* FILE_ID_INFO from <winbase.h> */
482 u64 VolumeSerialNumber;
483 unsigned char FileId[16];
484 } fi2;
485 if( !loaded_fnGetFileInformationByHandleEx ){
486 fnGetFileInformationByHandleEx = GetProcAddress(
487 GetModuleHandleA("kernel32"),"GetFileInformationByHandleEx");
488 loaded_fnGetFileInformationByHandleEx = 1;
489 }
490 if( fnGetFileInformationByHandleEx ){
491 if( fnGetFileInformationByHandleEx(
492 hFile,/*FileIdInfo*/0x12,&fi2,sizeof(fi2)) ){
493 zFileId = mprintf(
494 "%016llx/"
495 "%02x%02x%02x%02x%02x%02x%02x%02x"
496 "%02x%02x%02x%02x%02x%02x%02x%02x",
497 fi2.VolumeSerialNumber,
498 fi2.FileId[15], fi2.FileId[14],
499 fi2.FileId[13], fi2.FileId[12],
500 fi2.FileId[11], fi2.FileId[10],
501 fi2.FileId[9], fi2.FileId[8],
502 fi2.FileId[7], fi2.FileId[6],
503 fi2.FileId[5], fi2.FileId[4],
504 fi2.FileId[3], fi2.FileId[2],
505 fi2.FileId[1], fi2.FileId[0]);
506 }
507 }
508 if( zFileId==0 ){
509 if( GetFileInformationByHandle(hFile,&fi) ){
510 ULARGE_INTEGER FileId = {
511 /*.LowPart = */ fi.nFileIndexLow,
512 /*.HighPart = */ fi.nFileIndexHigh
513 };
514 zFileId = mprintf(
515 "%08x/%016llx",
516 fi.dwVolumeSerialNumber,(u64)FileId.QuadPart);
517 }
518 }
519 CloseHandle(hFile);
520 }
521 fossil_path_free(wzFileName);
522 return zFileId;
523 }
524 #endif /* _WIN32 -- This code is for win32 only */
525
+55 -2
--- src/xfer.c
+++ src/xfer.c
@@ -1041,10 +1041,43 @@
10411041
}
10421042
db_finalize(&q);
10431043
if( cnt==0 ) pXfer->resync = 0;
10441044
return cnt;
10451045
}
1046
+
1047
+/*
1048
+** Send an igot message for every cluster artifact that is not a phantom,
1049
+** is not shunned, is not private, and that is not in the UNCLUSTERED table.
1050
+** Return the number of cards sent.
1051
+*/
1052
+static int send_all_clusters(Xfer *pXfer){
1053
+ Stmt q;
1054
+ int cnt = 0;
1055
+ const char *zExtra;
1056
+ if( db_table_exists("temp","onremote") ){
1057
+ zExtra = " AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)";
1058
+ }else{
1059
+ zExtra = "";
1060
+ }
1061
+ db_prepare(&q,
1062
+ "SELECT uuid"
1063
+ " FROM tagxref JOIN blob ON tagxref.rid=blob.rid AND tagxref.tagid=%d"
1064
+ " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
1065
+ " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
1066
+ " AND NOT EXISTS(SELECT 1 FROM unclustered WHERE rid=blob.rid)"
1067
+ " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s",
1068
+ TAG_CLUSTER, zExtra /*safe-for-%s*/
1069
+ );
1070
+ while( db_step(&q)==SQLITE_ROW ){
1071
+ if( cnt==0 ) blob_appendf(pXfer->pOut, "# sending-clusters\n");
1072
+ blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
1073
+ cnt++;
1074
+ }
1075
+ db_finalize(&q);
1076
+ if( cnt ) blob_appendf(pXfer->pOut, "# end-of-clusters\n");
1077
+ return cnt;
1078
+}
10461079
10471080
/*
10481081
** Send an igot message for every artifact.
10491082
*/
10501083
static void send_all(Xfer *pXfer){
@@ -1781,10 +1814,19 @@
17811814
** The client sends this message to the server to ask the server
17821815
** to tell it about alternative repositories in the reply.
17831816
*/
17841817
if( blob_eq(&xfer.aToken[1], "req-links") ){
17851818
bSendLinks = 1;
1819
+ }else
1820
+
1821
+ /* pragma req-clusters
1822
+ **
1823
+ ** This pragma requests that the server send igot cards for every
1824
+ ** cluster artifact that it knows about.
1825
+ */
1826
+ if( blob_eq(&xfer.aToken[1], "req-clusters") ){
1827
+ send_all_clusters(&xfer);
17861828
}
17871829
17881830
}else
17891831
17901832
/* Unknown message
@@ -1981,11 +2023,11 @@
19812023
int nCardSent = 0; /* Number of cards sent */
19822024
int nCardRcvd = 0; /* Number of cards received */
19832025
int nCycle = 0; /* Number of round trips to the server */
19842026
int size; /* Size of a config value or uvfile */
19852027
int origConfigRcvMask; /* Original value of configRcvMask */
1986
- int nFileRecv; /* Number of files received */
2028
+ int nFileRecv = 0; /* Number of files received */
19872029
int mxPhantomReq = 200; /* Max number of phantoms to request per comm */
19882030
const char *zCookie; /* Server cookie */
19892031
i64 nUncSent, nUncRcvd; /* Bytes sent and received (before compression) */
19902032
i64 nSent, nRcvd; /* Bytes sent and received (after compression) */
19912033
int cloneSeqno = 1; /* Sequence number for clones */
@@ -2007,10 +2049,11 @@
20072049
int uvHashSent = 0; /* The "pragma uv-hash" message has been sent */
20082050
int uvDoPush = 0; /* Generate uvfile messages to send to server */
20092051
int uvPullOnly = 0; /* 1: pull-only. 2: pull-only warning issued */
20102052
int nUvGimmeSent = 0; /* Number of uvgimme cards sent on this cycle */
20112053
int nUvFileRcvd = 0; /* Number of uvfile cards received on this cycle */
2054
+ int nGimmeRcvd = 0; /* Number of gimme cards recevied on the prev cycle */
20122055
sqlite3_int64 mtime; /* Modification time on a UV file */
20132056
int autopushFailed = 0; /* Autopush following commit failed if true */
20142057
const char *zCkinLock; /* Name of check-in to lock. NULL for none */
20152058
const char *zClientId; /* A unique identifier for this check-out */
20162059
unsigned int mHttpFlags;/* Flags for the http_exchange() subsystem */
@@ -2181,15 +2224,21 @@
21812224
*/
21822225
if( (syncFlags & SYNC_PULL)!=0
21832226
|| ((syncFlags & SYNC_CLONE)!=0 && cloneSeqno==1)
21842227
){
21852228
request_phantoms(&xfer, mxPhantomReq);
2229
+ if( xfer.nGimmeSent>0 && nCycle==2 && (syncFlags & SYNC_PULL)!=0 ){
2230
+ blob_appendf(&send, "pragma req-clusters\n");
2231
+ }
21862232
}
21872233
if( syncFlags & SYNC_PUSH ){
21882234
send_unsent(&xfer);
21892235
nCardSent += send_unclustered(&xfer);
21902236
if( syncFlags & SYNC_PRIVATE ) send_private(&xfer);
2237
+ if( nGimmeRcvd>0 && nCycle==2 ){
2238
+ send_all_clusters(&xfer);
2239
+ }
21912240
}
21922241
21932242
/* Client sends configuration parameter requests. On a clone, delay sending
21942243
** this until the second cycle since the login card might fail on
21952244
** the first cycle.
@@ -2369,10 +2418,11 @@
23692418
nCardSent++;
23702419
}
23712420
go = 0;
23722421
nUvGimmeSent = 0;
23732422
nUvFileRcvd = 0;
2423
+ nGimmeRcvd = 0;
23742424
nPriorArtifact = nArtifactRcvd;
23752425
23762426
/* Process the reply that came back from the server */
23772427
while( blob_line(&recv, &xfer.line) ){
23782428
if( blob_buffer(&xfer.line)[0]=='#' ){
@@ -2449,11 +2499,14 @@
24492499
&& blob_is_hname(&xfer.aToken[1])
24502500
){
24512501
remote_unk(&xfer.aToken[1]);
24522502
if( syncFlags & SYNC_PUSH ){
24532503
int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
2454
- if( rid ) send_file(&xfer, rid, &xfer.aToken[1], 0);
2504
+ if( rid ){
2505
+ send_file(&xfer, rid, &xfer.aToken[1], 0);
2506
+ nGimmeRcvd++;
2507
+ }
24552508
}
24562509
}else
24572510
24582511
/* igot HASH ?PRIVATEFLAG?
24592512
**
24602513
--- src/xfer.c
+++ src/xfer.c
@@ -1041,10 +1041,43 @@
1041 }
1042 db_finalize(&q);
1043 if( cnt==0 ) pXfer->resync = 0;
1044 return cnt;
1045 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1046
1047 /*
1048 ** Send an igot message for every artifact.
1049 */
1050 static void send_all(Xfer *pXfer){
@@ -1781,10 +1814,19 @@
1781 ** The client sends this message to the server to ask the server
1782 ** to tell it about alternative repositories in the reply.
1783 */
1784 if( blob_eq(&xfer.aToken[1], "req-links") ){
1785 bSendLinks = 1;
 
 
 
 
 
 
 
 
 
1786 }
1787
1788 }else
1789
1790 /* Unknown message
@@ -1981,11 +2023,11 @@
1981 int nCardSent = 0; /* Number of cards sent */
1982 int nCardRcvd = 0; /* Number of cards received */
1983 int nCycle = 0; /* Number of round trips to the server */
1984 int size; /* Size of a config value or uvfile */
1985 int origConfigRcvMask; /* Original value of configRcvMask */
1986 int nFileRecv; /* Number of files received */
1987 int mxPhantomReq = 200; /* Max number of phantoms to request per comm */
1988 const char *zCookie; /* Server cookie */
1989 i64 nUncSent, nUncRcvd; /* Bytes sent and received (before compression) */
1990 i64 nSent, nRcvd; /* Bytes sent and received (after compression) */
1991 int cloneSeqno = 1; /* Sequence number for clones */
@@ -2007,10 +2049,11 @@
2007 int uvHashSent = 0; /* The "pragma uv-hash" message has been sent */
2008 int uvDoPush = 0; /* Generate uvfile messages to send to server */
2009 int uvPullOnly = 0; /* 1: pull-only. 2: pull-only warning issued */
2010 int nUvGimmeSent = 0; /* Number of uvgimme cards sent on this cycle */
2011 int nUvFileRcvd = 0; /* Number of uvfile cards received on this cycle */
 
2012 sqlite3_int64 mtime; /* Modification time on a UV file */
2013 int autopushFailed = 0; /* Autopush following commit failed if true */
2014 const char *zCkinLock; /* Name of check-in to lock. NULL for none */
2015 const char *zClientId; /* A unique identifier for this check-out */
2016 unsigned int mHttpFlags;/* Flags for the http_exchange() subsystem */
@@ -2181,15 +2224,21 @@
2181 */
2182 if( (syncFlags & SYNC_PULL)!=0
2183 || ((syncFlags & SYNC_CLONE)!=0 && cloneSeqno==1)
2184 ){
2185 request_phantoms(&xfer, mxPhantomReq);
 
 
 
2186 }
2187 if( syncFlags & SYNC_PUSH ){
2188 send_unsent(&xfer);
2189 nCardSent += send_unclustered(&xfer);
2190 if( syncFlags & SYNC_PRIVATE ) send_private(&xfer);
 
 
 
2191 }
2192
2193 /* Client sends configuration parameter requests. On a clone, delay sending
2194 ** this until the second cycle since the login card might fail on
2195 ** the first cycle.
@@ -2369,10 +2418,11 @@
2369 nCardSent++;
2370 }
2371 go = 0;
2372 nUvGimmeSent = 0;
2373 nUvFileRcvd = 0;
 
2374 nPriorArtifact = nArtifactRcvd;
2375
2376 /* Process the reply that came back from the server */
2377 while( blob_line(&recv, &xfer.line) ){
2378 if( blob_buffer(&xfer.line)[0]=='#' ){
@@ -2449,11 +2499,14 @@
2449 && blob_is_hname(&xfer.aToken[1])
2450 ){
2451 remote_unk(&xfer.aToken[1]);
2452 if( syncFlags & SYNC_PUSH ){
2453 int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
2454 if( rid ) send_file(&xfer, rid, &xfer.aToken[1], 0);
 
 
 
2455 }
2456 }else
2457
2458 /* igot HASH ?PRIVATEFLAG?
2459 **
2460
--- src/xfer.c
+++ src/xfer.c
@@ -1041,10 +1041,43 @@
1041 }
1042 db_finalize(&q);
1043 if( cnt==0 ) pXfer->resync = 0;
1044 return cnt;
1045 }
1046
1047 /*
1048 ** Send an igot message for every cluster artifact that is not a phantom,
1049 ** is not shunned, is not private, and that is not in the UNCLUSTERED table.
1050 ** Return the number of cards sent.
1051 */
1052 static int send_all_clusters(Xfer *pXfer){
1053 Stmt q;
1054 int cnt = 0;
1055 const char *zExtra;
1056 if( db_table_exists("temp","onremote") ){
1057 zExtra = " AND NOT EXISTS(SELECT 1 FROM onremote WHERE rid=blob.rid)";
1058 }else{
1059 zExtra = "";
1060 }
1061 db_prepare(&q,
1062 "SELECT uuid"
1063 " FROM tagxref JOIN blob ON tagxref.rid=blob.rid AND tagxref.tagid=%d"
1064 " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
1065 " AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
1066 " AND NOT EXISTS(SELECT 1 FROM unclustered WHERE rid=blob.rid)"
1067 " AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)%s",
1068 TAG_CLUSTER, zExtra /*safe-for-%s*/
1069 );
1070 while( db_step(&q)==SQLITE_ROW ){
1071 if( cnt==0 ) blob_appendf(pXfer->pOut, "# sending-clusters\n");
1072 blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
1073 cnt++;
1074 }
1075 db_finalize(&q);
1076 if( cnt ) blob_appendf(pXfer->pOut, "# end-of-clusters\n");
1077 return cnt;
1078 }
1079
1080 /*
1081 ** Send an igot message for every artifact.
1082 */
1083 static void send_all(Xfer *pXfer){
@@ -1781,10 +1814,19 @@
1814 ** The client sends this message to the server to ask the server
1815 ** to tell it about alternative repositories in the reply.
1816 */
1817 if( blob_eq(&xfer.aToken[1], "req-links") ){
1818 bSendLinks = 1;
1819 }else
1820
1821 /* pragma req-clusters
1822 **
1823 ** This pragma requests that the server send igot cards for every
1824 ** cluster artifact that it knows about.
1825 */
1826 if( blob_eq(&xfer.aToken[1], "req-clusters") ){
1827 send_all_clusters(&xfer);
1828 }
1829
1830 }else
1831
1832 /* Unknown message
@@ -1981,11 +2023,11 @@
2023 int nCardSent = 0; /* Number of cards sent */
2024 int nCardRcvd = 0; /* Number of cards received */
2025 int nCycle = 0; /* Number of round trips to the server */
2026 int size; /* Size of a config value or uvfile */
2027 int origConfigRcvMask; /* Original value of configRcvMask */
2028 int nFileRecv = 0; /* Number of files received */
2029 int mxPhantomReq = 200; /* Max number of phantoms to request per comm */
2030 const char *zCookie; /* Server cookie */
2031 i64 nUncSent, nUncRcvd; /* Bytes sent and received (before compression) */
2032 i64 nSent, nRcvd; /* Bytes sent and received (after compression) */
2033 int cloneSeqno = 1; /* Sequence number for clones */
@@ -2007,10 +2049,11 @@
2049 int uvHashSent = 0; /* The "pragma uv-hash" message has been sent */
2050 int uvDoPush = 0; /* Generate uvfile messages to send to server */
2051 int uvPullOnly = 0; /* 1: pull-only. 2: pull-only warning issued */
2052 int nUvGimmeSent = 0; /* Number of uvgimme cards sent on this cycle */
2053 int nUvFileRcvd = 0; /* Number of uvfile cards received on this cycle */
2054 int nGimmeRcvd = 0; /* Number of gimme cards recevied on the prev cycle */
2055 sqlite3_int64 mtime; /* Modification time on a UV file */
2056 int autopushFailed = 0; /* Autopush following commit failed if true */
2057 const char *zCkinLock; /* Name of check-in to lock. NULL for none */
2058 const char *zClientId; /* A unique identifier for this check-out */
2059 unsigned int mHttpFlags;/* Flags for the http_exchange() subsystem */
@@ -2181,15 +2224,21 @@
2224 */
2225 if( (syncFlags & SYNC_PULL)!=0
2226 || ((syncFlags & SYNC_CLONE)!=0 && cloneSeqno==1)
2227 ){
2228 request_phantoms(&xfer, mxPhantomReq);
2229 if( xfer.nGimmeSent>0 && nCycle==2 && (syncFlags & SYNC_PULL)!=0 ){
2230 blob_appendf(&send, "pragma req-clusters\n");
2231 }
2232 }
2233 if( syncFlags & SYNC_PUSH ){
2234 send_unsent(&xfer);
2235 nCardSent += send_unclustered(&xfer);
2236 if( syncFlags & SYNC_PRIVATE ) send_private(&xfer);
2237 if( nGimmeRcvd>0 && nCycle==2 ){
2238 send_all_clusters(&xfer);
2239 }
2240 }
2241
2242 /* Client sends configuration parameter requests. On a clone, delay sending
2243 ** this until the second cycle since the login card might fail on
2244 ** the first cycle.
@@ -2369,10 +2418,11 @@
2418 nCardSent++;
2419 }
2420 go = 0;
2421 nUvGimmeSent = 0;
2422 nUvFileRcvd = 0;
2423 nGimmeRcvd = 0;
2424 nPriorArtifact = nArtifactRcvd;
2425
2426 /* Process the reply that came back from the server */
2427 while( blob_line(&recv, &xfer.line) ){
2428 if( blob_buffer(&xfer.line)[0]=='#' ){
@@ -2449,11 +2499,14 @@
2499 && blob_is_hname(&xfer.aToken[1])
2500 ){
2501 remote_unk(&xfer.aToken[1]);
2502 if( syncFlags & SYNC_PUSH ){
2503 int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
2504 if( rid ){
2505 send_file(&xfer, rid, &xfer.aToken[1], 0);
2506 nGimmeRcvd++;
2507 }
2508 }
2509 }else
2510
2511 /* igot HASH ?PRIVATEFLAG?
2512 **
2513
--- test/merge1.test
+++ test/merge1.test
@@ -75,10 +75,12 @@
7575
555 - we think it well and other stuff too - 5555
7676
}
7777
write_file_indented t23 {
7878
<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1)
7979
111 - This is line ONE of the demo program - 1111
80
+ ####### SUGGESTED CONFLICT RESOLUTION follows ###################
81
+ 111 - This is line ONE OF the demo program - 1111
8082
||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1)
8183
111 - This is line one of the demo program - 1111
8284
======= MERGED IN content follows =============================== (line 1)
8385
111 - This is line one OF the demo program - 1111
8486
>>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@@ -88,10 +90,12 @@
8890
555 - we think it well and other stuff too - 5555
8991
}
9092
write_file_indented t32 {
9193
<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1)
9294
111 - This is line one OF the demo program - 1111
95
+ ####### SUGGESTED CONFLICT RESOLUTION follows ###################
96
+ 111 - This is line ONE OF the demo program - 1111
9397
||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1)
9498
111 - This is line one of the demo program - 1111
9599
======= MERGED IN content follows =============================== (line 1)
96100
111 - This is line ONE of the demo program - 1111
97101
>>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@@ -159,10 +163,13 @@
159163
444 - If all goes well, we will be pleased - 4444
160164
555 - we think it well and other stuff too - 5555
161165
}
162166
write_file_indented t32 {
163167
<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1)
168
+ ####### SUGGESTED CONFLICT RESOLUTION follows ###################
169
+ 000 - Zero lines added to the beginning of - 0000
170
+ 111 - This is line one of the demo program - 1111
164171
||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1)
165172
111 - This is line one of the demo program - 1111
166173
======= MERGED IN content follows =============================== (line 1)
167174
000 - Zero lines added to the beginning of - 0000
168175
111 - This is line one of the demo program - 1111
@@ -305,10 +312,19 @@
305312
mnop 2
306313
qrst
307314
uvwx
308315
yzAB 2
309316
CDEF 2
317
+ GHIJ 2
318
+ ####### SUGGESTED CONFLICT RESOLUTION follows ###################
319
+ efgh 2
320
+ ijkl 2
321
+ mnop 3
322
+ qrst 3
323
+ uvwx 3
324
+ yzAB 3
325
+ CDEF 2
310326
GHIJ 2
311327
||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 2)
312328
efgh
313329
ijkl
314330
mnop
@@ -372,10 +388,19 @@
372388
ijkl 2
373389
mnop
374390
qrst
375391
uvwx
376392
yzAB 2
393
+ CDEF 2
394
+ GHIJ 2
395
+ ####### SUGGESTED CONFLICT RESOLUTION follows ###################
396
+ efgh 2
397
+ ijkl 2
398
+ mnop 3
399
+ qrst 3
400
+ uvwx 3
401
+ yzAB 3
377402
CDEF 2
378403
GHIJ 2
379404
||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 2)
380405
efgh
381406
ijkl
382407
--- test/merge1.test
+++ test/merge1.test
@@ -75,10 +75,12 @@
75 555 - we think it well and other stuff too - 5555
76 }
77 write_file_indented t23 {
78 <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1)
79 111 - This is line ONE of the demo program - 1111
 
 
80 ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1)
81 111 - This is line one of the demo program - 1111
82 ======= MERGED IN content follows =============================== (line 1)
83 111 - This is line one OF the demo program - 1111
84 >>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@@ -88,10 +90,12 @@
88 555 - we think it well and other stuff too - 5555
89 }
90 write_file_indented t32 {
91 <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1)
92 111 - This is line one OF the demo program - 1111
 
 
93 ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1)
94 111 - This is line one of the demo program - 1111
95 ======= MERGED IN content follows =============================== (line 1)
96 111 - This is line ONE of the demo program - 1111
97 >>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@@ -159,10 +163,13 @@
159 444 - If all goes well, we will be pleased - 4444
160 555 - we think it well and other stuff too - 5555
161 }
162 write_file_indented t32 {
163 <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1)
 
 
 
164 ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1)
165 111 - This is line one of the demo program - 1111
166 ======= MERGED IN content follows =============================== (line 1)
167 000 - Zero lines added to the beginning of - 0000
168 111 - This is line one of the demo program - 1111
@@ -305,10 +312,19 @@
305 mnop 2
306 qrst
307 uvwx
308 yzAB 2
309 CDEF 2
 
 
 
 
 
 
 
 
 
310 GHIJ 2
311 ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 2)
312 efgh
313 ijkl
314 mnop
@@ -372,10 +388,19 @@
372 ijkl 2
373 mnop
374 qrst
375 uvwx
376 yzAB 2
 
 
 
 
 
 
 
 
 
377 CDEF 2
378 GHIJ 2
379 ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 2)
380 efgh
381 ijkl
382
--- test/merge1.test
+++ test/merge1.test
@@ -75,10 +75,12 @@
75 555 - we think it well and other stuff too - 5555
76 }
77 write_file_indented t23 {
78 <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1)
79 111 - This is line ONE of the demo program - 1111
80 ####### SUGGESTED CONFLICT RESOLUTION follows ###################
81 111 - This is line ONE OF the demo program - 1111
82 ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1)
83 111 - This is line one of the demo program - 1111
84 ======= MERGED IN content follows =============================== (line 1)
85 111 - This is line one OF the demo program - 1111
86 >>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@@ -88,10 +90,12 @@
90 555 - we think it well and other stuff too - 5555
91 }
92 write_file_indented t32 {
93 <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1)
94 111 - This is line one OF the demo program - 1111
95 ####### SUGGESTED CONFLICT RESOLUTION follows ###################
96 111 - This is line ONE OF the demo program - 1111
97 ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1)
98 111 - This is line one of the demo program - 1111
99 ======= MERGED IN content follows =============================== (line 1)
100 111 - This is line ONE of the demo program - 1111
101 >>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
@@ -159,10 +163,13 @@
163 444 - If all goes well, we will be pleased - 4444
164 555 - we think it well and other stuff too - 5555
165 }
166 write_file_indented t32 {
167 <<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<< (line 1)
168 ####### SUGGESTED CONFLICT RESOLUTION follows ###################
169 000 - Zero lines added to the beginning of - 0000
170 111 - This is line one of the demo program - 1111
171 ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 1)
172 111 - This is line one of the demo program - 1111
173 ======= MERGED IN content follows =============================== (line 1)
174 000 - Zero lines added to the beginning of - 0000
175 111 - This is line one of the demo program - 1111
@@ -305,10 +312,19 @@
312 mnop 2
313 qrst
314 uvwx
315 yzAB 2
316 CDEF 2
317 GHIJ 2
318 ####### SUGGESTED CONFLICT RESOLUTION follows ###################
319 efgh 2
320 ijkl 2
321 mnop 3
322 qrst 3
323 uvwx 3
324 yzAB 3
325 CDEF 2
326 GHIJ 2
327 ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 2)
328 efgh
329 ijkl
330 mnop
@@ -372,10 +388,19 @@
388 ijkl 2
389 mnop
390 qrst
391 uvwx
392 yzAB 2
393 CDEF 2
394 GHIJ 2
395 ####### SUGGESTED CONFLICT RESOLUTION follows ###################
396 efgh 2
397 ijkl 2
398 mnop 3
399 qrst 3
400 uvwx 3
401 yzAB 3
402 CDEF 2
403 GHIJ 2
404 ||||||| COMMON ANCESTOR content follows ||||||||||||||||||||||||| (line 2)
405 efgh
406 ijkl
407
+28 -25
--- test/merge3.test
+++ test/merge3.test
@@ -27,10 +27,13 @@
2727
fossil 3-way-merge t1 t2 t3 t4 {*}$fossil_args
2828
set x [read_file t4]
2929
regsub -all \
3030
{<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <+ \(line \d+\)} \
3131
$x {MINE:} x
32
+ regsub -all \
33
+ {####### SUGGESTED CONFLICT RESOLUTION follows #+} \
34
+ $x {BOT:} x
3235
regsub -all \
3336
{\|\|\|\|\|\|\| COMMON ANCESTOR content follows \|+ \(line \d+\)} \
3437
$x {COM:} x
3538
regsub -all \
3639
{======= MERGED IN content follows =+ \(line \d+\)} \
@@ -73,56 +76,56 @@
7376
} {
7477
1 2 3b 4b 5b 6 7 8 9
7578
} {
7679
1 2 3 4 5c 6 7 8 9
7780
} {
78
- 1 2 MINE: 3b 4b 5b COM: 3 4 5 YOURS: 3 4 5c END 6 7 8 9
81
+ 1 2 MINE: 3b 4b 5b BOT: 3b 4b 5c COM: 3 4 5 YOURS: 3 4 5c END 6 7 8 9
7982
} -expectError
8083
merge-test 4 {
8184
1 2 3 4 5 6 7 8 9
8285
} {
8386
1 2 3b 4b 5b 6b 7 8 9
8487
} {
8588
1 2 3 4 5c 6 7 8 9
8689
} {
87
- 1 2 MINE: 3b 4b 5b 6b COM: 3 4 5 6 YOURS: 3 4 5c 6 END 7 8 9
90
+ 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
8891
} -expectError
8992
merge-test 5 {
9093
1 2 3 4 5 6 7 8 9
9194
} {
9295
1 2 3b 4b 5b 6b 7 8 9
9396
} {
9497
1 2 3 4 5c 6c 7c 8 9
9598
} {
96
- 1 2 MINE: 3b 4b 5b 6b 7 COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8 9
99
+ 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
97100
} -expectError
98101
merge-test 6 {
99102
1 2 3 4 5 6 7 8 9
100103
} {
101104
1 2 3b 4b 5b 6b 7 8b 9
102105
} {
103106
1 2 3 4 5c 6c 7c 8 9
104107
} {
105
- 1 2 MINE: 3b 4b 5b 6b 7 COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8b 9
108
+ 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
106109
} -expectError
107110
merge-test 7 {
108111
1 2 3 4 5 6 7 8 9
109112
} {
110113
1 2 3b 4b 5b 6b 7 8b 9
111114
} {
112115
1 2 3 4 5c 6c 7c 8c 9
113116
} {
114
- 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
117
+ 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
115118
} -expectError
116119
merge-test 8 {
117120
1 2 3 4 5 6 7 8 9
118121
} {
119122
1 2 3b 4b 5b 6b 7 8b 9b
120123
} {
121124
1 2 3 4 5c 6c 7c 8c 9
122125
} {
123
- 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
126
+ 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
124127
} -expectError
125128
merge-test 9 {
126129
1 2 3 4 5 6 7 8 9
127130
} {
128131
1 2 3b 4b 5 6 7 8b 9b
@@ -146,11 +149,11 @@
146149
} {
147150
1 2 3b 4b 5 6 7 8b 9b
148151
} {
149152
1 2 3b 4c 5 6c 7c 8 9
150153
} {
151
- 1 2 MINE: 3b 4b COM: 3 4 YOURS: 3b 4c END 5 6c 7c 8b 9b
154
+ 1 2 MINE: 3b 4b BOT: 3b 4c COM: 3 4 YOURS: 3b 4c END 5 6c 7c 8b 9b
152155
} -expectError
153156
merge-test 12 {
154157
1 2 3 4 5 6 7 8 9
155158
} {
156159
1 2 3b4b 5 6 7 8b 9b
@@ -201,20 +204,20 @@
201204
} {
202205
1 6 7 8 9
203206
} {
204207
1 2 3 4 9
205208
} {
206
- 1 MINE: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9
209
+ 1 MINE: 6 7 8 BOT: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9
207210
} -expectError
208211
merge-test 25 {
209212
1 2 3 4 5 6 7 8 9
210213
} {
211214
1 7 8 9
212215
} {
213216
1 2 3 9
214217
} {
215
- 1 MINE: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9
218
+ 1 MINE: 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9
216219
} -expectError
217220
218221
merge-test 30 {
219222
1 2 3 4 5 6 7 8 9
220223
} {
@@ -256,20 +259,20 @@
256259
} {
257260
1 2 3 4 9
258261
} {
259262
1 6 7 8 9
260263
} {
261
- 1 MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END 9
264
+ 1 MINE: 2 3 4 BOT: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END 9
262265
} -expectError
263266
merge-test 35 {
264267
1 2 3 4 5 6 7 8 9
265268
} {
266269
1 2 3 9
267270
} {
268271
1 7 8 9
269272
} {
270
- 1 MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END 9
273
+ 1 MINE: 2 3 BOT: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END 9
271274
} -expectError
272275
273276
merge-test 40 {
274277
2 3 4 5 6 7 8
275278
} {
@@ -311,20 +314,20 @@
311314
} {
312315
6 7 8
313316
} {
314317
2 3 4
315318
} {
316
- MINE: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END
319
+ MINE: 6 7 8 BOT: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END
317320
} -expectError
318321
merge-test 45 {
319322
2 3 4 5 6 7 8
320323
} {
321324
7 8
322325
} {
323326
2 3
324327
} {
325
- MINE: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END
328
+ MINE: 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END
326329
} -expectError
327330
328331
merge-test 50 {
329332
2 3 4 5 6 7 8
330333
} {
@@ -365,20 +368,20 @@
365368
} {
366369
2 3 4
367370
} {
368371
6 7 8
369372
} {
370
- MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END
373
+ MINE: 2 3 4 BOT: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END
371374
} -expectError
372375
merge-test 55 {
373376
2 3 4 5 6 7 8
374377
} {
375378
2 3
376379
} {
377380
7 8
378381
} {
379
- MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END
382
+ MINE: 2 3 BOT: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END
380383
} -expectError
381384
382385
merge-test 60 {
383386
1 2 3 4 5 6 7 8 9
384387
} {
@@ -420,20 +423,20 @@
420423
} {
421424
1 2b 3b 4b 5b 6 7 8 9
422425
} {
423426
1 2 3 4 9
424427
} {
425
- 1 MINE: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9
428
+ 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
426429
} -expectError
427430
merge-test 65 {
428431
1 2 3 4 5 6 7 8 9
429432
} {
430433
1 2b 3b 4b 5b 6b 7 8 9
431434
} {
432435
1 2 3 9
433436
} {
434
- 1 MINE: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9
437
+ 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
435438
} -expectError
436439
437440
merge-test 70 {
438441
1 2 3 4 5 6 7 8 9
439442
} {
@@ -475,20 +478,20 @@
475478
} {
476479
1 2 3 4 9
477480
} {
478481
1 2b 3b 4b 5b 6 7 8 9
479482
} {
480
- 1 MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END 9
483
+ 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
481484
} -expectError
482485
merge-test 75 {
483486
1 2 3 4 5 6 7 8 9
484487
} {
485488
1 2 3 9
486489
} {
487490
1 2b 3b 4b 5b 6b 7 8 9
488491
} {
489
- 1 MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END 9
492
+ 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
490493
} -expectError
491494
492495
merge-test 80 {
493496
2 3 4 5 6 7 8
494497
} {
@@ -530,20 +533,20 @@
530533
} {
531534
2b 3b 4b 5b 6 7 8
532535
} {
533536
2 3 4
534537
} {
535
- MINE: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END
538
+ 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
536539
} -expectError
537540
merge-test 85 {
538541
2 3 4 5 6 7 8
539542
} {
540543
2b 3b 4b 5b 6b 7 8
541544
} {
542545
2 3
543546
} {
544
- MINE: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END
547
+ MINE: 2b 3b 4b 5b 6b 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END
545548
} -expectError
546549
547550
merge-test 90 {
548551
2 3 4 5 6 7 8
549552
} {
@@ -585,20 +588,20 @@
585588
} {
586589
2 3 4
587590
} {
588591
2b 3b 4b 5b 6 7 8
589592
} {
590
- MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END
593
+ 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
591594
} -expectError
592595
merge-test 95 {
593596
2 3 4 5 6 7 8
594597
} {
595598
2 3
596599
} {
597600
2b 3b 4b 5b 6b 7 8
598601
} {
599
- MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END
602
+ 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
600603
} -expectError
601604
602605
merge-test 100 {
603606
1 2 3 4 5 6 7 8 9
604607
} {
@@ -631,20 +634,20 @@
631634
} {
632635
1 2 3 4 5 7 8 9b
633636
} {
634637
1 2 3 4 5 7 8 9b a b c d e
635638
} {
636
- 1 2 3 4 5 7 8 MINE: 9b COM: 9 YOURS: 9b a b c d e END
639
+ 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
637640
} -expectError
638641
merge-test 104 {
639642
1 2 3 4 5 6 7 8 9
640643
} {
641644
1 2 3 4 5 7 8 9b a b c d e
642645
} {
643646
1 2 3 4 5 7 8 9b
644647
} {
645
- 1 2 3 4 5 7 8 MINE: 9b a b c d e COM: 9 YOURS: 9b END
648
+ 1 2 3 4 5 7 8 MINE: 9b a b c d e BOT: 9b COM: 9 YOURS: 9b END
646649
} -expectError
647650
648651
###############################################################################
649652
650653
test_cleanup
651654
--- test/merge3.test
+++ test/merge3.test
@@ -27,10 +27,13 @@
27 fossil 3-way-merge t1 t2 t3 t4 {*}$fossil_args
28 set x [read_file t4]
29 regsub -all \
30 {<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <+ \(line \d+\)} \
31 $x {MINE:} x
 
 
 
32 regsub -all \
33 {\|\|\|\|\|\|\| COMMON ANCESTOR content follows \|+ \(line \d+\)} \
34 $x {COM:} x
35 regsub -all \
36 {======= MERGED IN content follows =+ \(line \d+\)} \
@@ -73,56 +76,56 @@
73 } {
74 1 2 3b 4b 5b 6 7 8 9
75 } {
76 1 2 3 4 5c 6 7 8 9
77 } {
78 1 2 MINE: 3b 4b 5b COM: 3 4 5 YOURS: 3 4 5c END 6 7 8 9
79 } -expectError
80 merge-test 4 {
81 1 2 3 4 5 6 7 8 9
82 } {
83 1 2 3b 4b 5b 6b 7 8 9
84 } {
85 1 2 3 4 5c 6 7 8 9
86 } {
87 1 2 MINE: 3b 4b 5b 6b COM: 3 4 5 6 YOURS: 3 4 5c 6 END 7 8 9
88 } -expectError
89 merge-test 5 {
90 1 2 3 4 5 6 7 8 9
91 } {
92 1 2 3b 4b 5b 6b 7 8 9
93 } {
94 1 2 3 4 5c 6c 7c 8 9
95 } {
96 1 2 MINE: 3b 4b 5b 6b 7 COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8 9
97 } -expectError
98 merge-test 6 {
99 1 2 3 4 5 6 7 8 9
100 } {
101 1 2 3b 4b 5b 6b 7 8b 9
102 } {
103 1 2 3 4 5c 6c 7c 8 9
104 } {
105 1 2 MINE: 3b 4b 5b 6b 7 COM: 3 4 5 6 7 YOURS: 3 4 5c 6c 7c END 8b 9
106 } -expectError
107 merge-test 7 {
108 1 2 3 4 5 6 7 8 9
109 } {
110 1 2 3b 4b 5b 6b 7 8b 9
111 } {
112 1 2 3 4 5c 6c 7c 8c 9
113 } {
114 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
115 } -expectError
116 merge-test 8 {
117 1 2 3 4 5 6 7 8 9
118 } {
119 1 2 3b 4b 5b 6b 7 8b 9b
120 } {
121 1 2 3 4 5c 6c 7c 8c 9
122 } {
123 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
124 } -expectError
125 merge-test 9 {
126 1 2 3 4 5 6 7 8 9
127 } {
128 1 2 3b 4b 5 6 7 8b 9b
@@ -146,11 +149,11 @@
146 } {
147 1 2 3b 4b 5 6 7 8b 9b
148 } {
149 1 2 3b 4c 5 6c 7c 8 9
150 } {
151 1 2 MINE: 3b 4b COM: 3 4 YOURS: 3b 4c END 5 6c 7c 8b 9b
152 } -expectError
153 merge-test 12 {
154 1 2 3 4 5 6 7 8 9
155 } {
156 1 2 3b4b 5 6 7 8b 9b
@@ -201,20 +204,20 @@
201 } {
202 1 6 7 8 9
203 } {
204 1 2 3 4 9
205 } {
206 1 MINE: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9
207 } -expectError
208 merge-test 25 {
209 1 2 3 4 5 6 7 8 9
210 } {
211 1 7 8 9
212 } {
213 1 2 3 9
214 } {
215 1 MINE: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9
216 } -expectError
217
218 merge-test 30 {
219 1 2 3 4 5 6 7 8 9
220 } {
@@ -256,20 +259,20 @@
256 } {
257 1 2 3 4 9
258 } {
259 1 6 7 8 9
260 } {
261 1 MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END 9
262 } -expectError
263 merge-test 35 {
264 1 2 3 4 5 6 7 8 9
265 } {
266 1 2 3 9
267 } {
268 1 7 8 9
269 } {
270 1 MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END 9
271 } -expectError
272
273 merge-test 40 {
274 2 3 4 5 6 7 8
275 } {
@@ -311,20 +314,20 @@
311 } {
312 6 7 8
313 } {
314 2 3 4
315 } {
316 MINE: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END
317 } -expectError
318 merge-test 45 {
319 2 3 4 5 6 7 8
320 } {
321 7 8
322 } {
323 2 3
324 } {
325 MINE: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END
326 } -expectError
327
328 merge-test 50 {
329 2 3 4 5 6 7 8
330 } {
@@ -365,20 +368,20 @@
365 } {
366 2 3 4
367 } {
368 6 7 8
369 } {
370 MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END
371 } -expectError
372 merge-test 55 {
373 2 3 4 5 6 7 8
374 } {
375 2 3
376 } {
377 7 8
378 } {
379 MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END
380 } -expectError
381
382 merge-test 60 {
383 1 2 3 4 5 6 7 8 9
384 } {
@@ -420,20 +423,20 @@
420 } {
421 1 2b 3b 4b 5b 6 7 8 9
422 } {
423 1 2 3 4 9
424 } {
425 1 MINE: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9
426 } -expectError
427 merge-test 65 {
428 1 2 3 4 5 6 7 8 9
429 } {
430 1 2b 3b 4b 5b 6b 7 8 9
431 } {
432 1 2 3 9
433 } {
434 1 MINE: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9
435 } -expectError
436
437 merge-test 70 {
438 1 2 3 4 5 6 7 8 9
439 } {
@@ -475,20 +478,20 @@
475 } {
476 1 2 3 4 9
477 } {
478 1 2b 3b 4b 5b 6 7 8 9
479 } {
480 1 MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END 9
481 } -expectError
482 merge-test 75 {
483 1 2 3 4 5 6 7 8 9
484 } {
485 1 2 3 9
486 } {
487 1 2b 3b 4b 5b 6b 7 8 9
488 } {
489 1 MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END 9
490 } -expectError
491
492 merge-test 80 {
493 2 3 4 5 6 7 8
494 } {
@@ -530,20 +533,20 @@
530 } {
531 2b 3b 4b 5b 6 7 8
532 } {
533 2 3 4
534 } {
535 MINE: 2b 3b 4b 5b 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END
536 } -expectError
537 merge-test 85 {
538 2 3 4 5 6 7 8
539 } {
540 2b 3b 4b 5b 6b 7 8
541 } {
542 2 3
543 } {
544 MINE: 2b 3b 4b 5b 6b 7 8 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END
545 } -expectError
546
547 merge-test 90 {
548 2 3 4 5 6 7 8
549 } {
@@ -585,20 +588,20 @@
585 } {
586 2 3 4
587 } {
588 2b 3b 4b 5b 6 7 8
589 } {
590 MINE: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6 7 8 END
591 } -expectError
592 merge-test 95 {
593 2 3 4 5 6 7 8
594 } {
595 2 3
596 } {
597 2b 3b 4b 5b 6b 7 8
598 } {
599 MINE: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2b 3b 4b 5b 6b 7 8 END
600 } -expectError
601
602 merge-test 100 {
603 1 2 3 4 5 6 7 8 9
604 } {
@@ -631,20 +634,20 @@
631 } {
632 1 2 3 4 5 7 8 9b
633 } {
634 1 2 3 4 5 7 8 9b a b c d e
635 } {
636 1 2 3 4 5 7 8 MINE: 9b COM: 9 YOURS: 9b a b c d e END
637 } -expectError
638 merge-test 104 {
639 1 2 3 4 5 6 7 8 9
640 } {
641 1 2 3 4 5 7 8 9b a b c d e
642 } {
643 1 2 3 4 5 7 8 9b
644 } {
645 1 2 3 4 5 7 8 MINE: 9b a b c d e COM: 9 YOURS: 9b END
646 } -expectError
647
648 ###############################################################################
649
650 test_cleanup
651
--- test/merge3.test
+++ test/merge3.test
@@ -27,10 +27,13 @@
27 fossil 3-way-merge t1 t2 t3 t4 {*}$fossil_args
28 set x [read_file t4]
29 regsub -all \
30 {<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <+ \(line \d+\)} \
31 $x {MINE:} x
32 regsub -all \
33 {####### SUGGESTED CONFLICT RESOLUTION follows #+} \
34 $x {BOT:} x
35 regsub -all \
36 {\|\|\|\|\|\|\| COMMON ANCESTOR content follows \|+ \(line \d+\)} \
37 $x {COM:} x
38 regsub -all \
39 {======= MERGED IN content follows =+ \(line \d+\)} \
@@ -73,56 +76,56 @@
76 } {
77 1 2 3b 4b 5b 6 7 8 9
78 } {
79 1 2 3 4 5c 6 7 8 9
80 } {
81 1 2 MINE: 3b 4b 5b BOT: 3b 4b 5c COM: 3 4 5 YOURS: 3 4 5c END 6 7 8 9
82 } -expectError
83 merge-test 4 {
84 1 2 3 4 5 6 7 8 9
85 } {
86 1 2 3b 4b 5b 6b 7 8 9
87 } {
88 1 2 3 4 5c 6 7 8 9
89 } {
90 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
91 } -expectError
92 merge-test 5 {
93 1 2 3 4 5 6 7 8 9
94 } {
95 1 2 3b 4b 5b 6b 7 8 9
96 } {
97 1 2 3 4 5c 6c 7c 8 9
98 } {
99 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
100 } -expectError
101 merge-test 6 {
102 1 2 3 4 5 6 7 8 9
103 } {
104 1 2 3b 4b 5b 6b 7 8b 9
105 } {
106 1 2 3 4 5c 6c 7c 8 9
107 } {
108 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
109 } -expectError
110 merge-test 7 {
111 1 2 3 4 5 6 7 8 9
112 } {
113 1 2 3b 4b 5b 6b 7 8b 9
114 } {
115 1 2 3 4 5c 6c 7c 8c 9
116 } {
117 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
118 } -expectError
119 merge-test 8 {
120 1 2 3 4 5 6 7 8 9
121 } {
122 1 2 3b 4b 5b 6b 7 8b 9b
123 } {
124 1 2 3 4 5c 6c 7c 8c 9
125 } {
126 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
127 } -expectError
128 merge-test 9 {
129 1 2 3 4 5 6 7 8 9
130 } {
131 1 2 3b 4b 5 6 7 8b 9b
@@ -146,11 +149,11 @@
149 } {
150 1 2 3b 4b 5 6 7 8b 9b
151 } {
152 1 2 3b 4c 5 6c 7c 8 9
153 } {
154 1 2 MINE: 3b 4b BOT: 3b 4c COM: 3 4 YOURS: 3b 4c END 5 6c 7c 8b 9b
155 } -expectError
156 merge-test 12 {
157 1 2 3 4 5 6 7 8 9
158 } {
159 1 2 3b4b 5 6 7 8b 9b
@@ -201,20 +204,20 @@
204 } {
205 1 6 7 8 9
206 } {
207 1 2 3 4 9
208 } {
209 1 MINE: 6 7 8 BOT: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END 9
210 } -expectError
211 merge-test 25 {
212 1 2 3 4 5 6 7 8 9
213 } {
214 1 7 8 9
215 } {
216 1 2 3 9
217 } {
218 1 MINE: 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END 9
219 } -expectError
220
221 merge-test 30 {
222 1 2 3 4 5 6 7 8 9
223 } {
@@ -256,20 +259,20 @@
259 } {
260 1 2 3 4 9
261 } {
262 1 6 7 8 9
263 } {
264 1 MINE: 2 3 4 BOT: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END 9
265 } -expectError
266 merge-test 35 {
267 1 2 3 4 5 6 7 8 9
268 } {
269 1 2 3 9
270 } {
271 1 7 8 9
272 } {
273 1 MINE: 2 3 BOT: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END 9
274 } -expectError
275
276 merge-test 40 {
277 2 3 4 5 6 7 8
278 } {
@@ -311,20 +314,20 @@
314 } {
315 6 7 8
316 } {
317 2 3 4
318 } {
319 MINE: 6 7 8 BOT: 2 3 4 COM: 2 3 4 5 6 7 8 YOURS: 2 3 4 END
320 } -expectError
321 merge-test 45 {
322 2 3 4 5 6 7 8
323 } {
324 7 8
325 } {
326 2 3
327 } {
328 MINE: 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END
329 } -expectError
330
331 merge-test 50 {
332 2 3 4 5 6 7 8
333 } {
@@ -365,20 +368,20 @@
368 } {
369 2 3 4
370 } {
371 6 7 8
372 } {
373 MINE: 2 3 4 BOT: 6 7 8 COM: 2 3 4 5 6 7 8 YOURS: 6 7 8 END
374 } -expectError
375 merge-test 55 {
376 2 3 4 5 6 7 8
377 } {
378 2 3
379 } {
380 7 8
381 } {
382 MINE: 2 3 BOT: 7 8 COM: 2 3 4 5 6 7 8 YOURS: 7 8 END
383 } -expectError
384
385 merge-test 60 {
386 1 2 3 4 5 6 7 8 9
387 } {
@@ -420,20 +423,20 @@
423 } {
424 1 2b 3b 4b 5b 6 7 8 9
425 } {
426 1 2 3 4 9
427 } {
428 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
429 } -expectError
430 merge-test 65 {
431 1 2 3 4 5 6 7 8 9
432 } {
433 1 2b 3b 4b 5b 6b 7 8 9
434 } {
435 1 2 3 9
436 } {
437 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
438 } -expectError
439
440 merge-test 70 {
441 1 2 3 4 5 6 7 8 9
442 } {
@@ -475,20 +478,20 @@
478 } {
479 1 2 3 4 9
480 } {
481 1 2b 3b 4b 5b 6 7 8 9
482 } {
483 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
484 } -expectError
485 merge-test 75 {
486 1 2 3 4 5 6 7 8 9
487 } {
488 1 2 3 9
489 } {
490 1 2b 3b 4b 5b 6b 7 8 9
491 } {
492 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
493 } -expectError
494
495 merge-test 80 {
496 2 3 4 5 6 7 8
497 } {
@@ -530,20 +533,20 @@
533 } {
534 2b 3b 4b 5b 6 7 8
535 } {
536 2 3 4
537 } {
538 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
539 } -expectError
540 merge-test 85 {
541 2 3 4 5 6 7 8
542 } {
543 2b 3b 4b 5b 6b 7 8
544 } {
545 2 3
546 } {
547 MINE: 2b 3b 4b 5b 6b 7 8 BOT: 2 3 COM: 2 3 4 5 6 7 8 YOURS: 2 3 END
548 } -expectError
549
550 merge-test 90 {
551 2 3 4 5 6 7 8
552 } {
@@ -585,20 +588,20 @@
588 } {
589 2 3 4
590 } {
591 2b 3b 4b 5b 6 7 8
592 } {
593 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
594 } -expectError
595 merge-test 95 {
596 2 3 4 5 6 7 8
597 } {
598 2 3
599 } {
600 2b 3b 4b 5b 6b 7 8
601 } {
602 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
603 } -expectError
604
605 merge-test 100 {
606 1 2 3 4 5 6 7 8 9
607 } {
@@ -631,20 +634,20 @@
634 } {
635 1 2 3 4 5 7 8 9b
636 } {
637 1 2 3 4 5 7 8 9b a b c d e
638 } {
639 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
640 } -expectError
641 merge-test 104 {
642 1 2 3 4 5 6 7 8 9
643 } {
644 1 2 3 4 5 7 8 9b a b c d e
645 } {
646 1 2 3 4 5 7 8 9b
647 } {
648 1 2 3 4 5 7 8 MINE: 9b a b c d e BOT: 9b COM: 9 YOURS: 9b END
649 } -expectError
650
651 ###############################################################################
652
653 test_cleanup
654
--- test/merge4.test
+++ test/merge4.test
@@ -26,15 +26,17 @@
2626
write_file t3 [join [string trim $v2] \n]\n
2727
fossil 3-way-merge t1 t2 t3 t4 {*}$fossil_args
2828
fossil 3-way-merge t1 t3 t2 t5 {*}$fossil_args
2929
set x [read_file t4]
3030
regsub -all {<<<<<<< BEGIN MERGE CONFLICT.*<< \(line \d+\)} $x {>} x
31
+ regsub -all {####### SUGGESTED CONFLICT RESOLUTION.*##} $x {#} x
3132
regsub -all {\|\|\|\|\|\|\|.*======= \(line \d+\)} $x {=} x
3233
regsub -all {>>>>>>> END MERGE CONFLICT.*>>>>} $x {<} x
3334
set x [split [string trim $x] \n]
3435
set y [read_file t5]
3536
regsub -all {<<<<<<< BEGIN MERGE CONFLICT.*<< \(line \d+\)} $y {>} y
37
+ regsub -all {####### SUGGESTED CONFLICT RESOLUTION.*##} $y {#} y
3638
regsub -all {\|\|\|\|\|\|\|.*======= \(line \d+\)} $y {=} y
3739
regsub -all {>>>>>>> END MERGE CONFLICT.*>>>>} $y {<} y
3840
set y [split [string trim $y] \n]
3941
set result1 [string trim $result1]
4042
if {$x!=$result1} {
@@ -58,13 +60,13 @@
5860
} {
5961
1 2b 3b 4b 5 6b 7b 8b 9
6062
} {
6163
1 2 3 4c 5c 6c 7 8 9
6264
} {
63
- 1 > 2b 3b 4b 5 6b 7b 8b = 2 3 4c 5c 6c 7 8 < 9
65
+ 1 > 2b 3b 4b 5 6b 7b 8b # 2b 3b 4c 5c 6c 7b 8b = 2 3 4c 5c 6c 7 8 < 9
6466
} {
65
- 1 > 2 3 4c 5c 6c 7 8 = 2b 3b 4b 5 6b 7b 8b < 9
67
+ 1 > 2 3 4c 5c 6c 7 8 # 2b 3b 4b 5c 6b 7b 8b = 2b 3b 4b 5 6b 7b 8b < 9
6668
} -expectError
6769
merge-test 1001 {
6870
1 2 3 4 5 6 7 8 9
6971
} {
7072
1 2b 3b 4 5 6 7b 8b 9
@@ -80,13 +82,13 @@
8082
} {
8183
2b 3b 4b 5 6b 7b 8b
8284
} {
8385
2 3 4c 5c 6c 7 8
8486
} {
85
- > 2b 3b 4b 5 6b 7b 8b = 2 3 4c 5c 6c 7 8 <
87
+ > 2b 3b 4b 5 6b 7b 8b # 2b 3b 4c 5c 6c 7b 8b = 2 3 4c 5c 6c 7 8 <
8688
} {
87
- > 2 3 4c 5c 6c 7 8 = 2b 3b 4b 5 6b 7b 8b <
89
+ > 2 3 4c 5c 6c 7 8 # 2b 3b 4b 5c 6b 7b 8b = 2b 3b 4b 5 6b 7b 8b <
8890
} -expectError
8991
merge-test 1003 {
9092
2 3 4 5 6 7 8
9193
} {
9294
2b 3b 4 5 6 7b 8b
9395
--- test/merge4.test
+++ test/merge4.test
@@ -26,15 +26,17 @@
26 write_file t3 [join [string trim $v2] \n]\n
27 fossil 3-way-merge t1 t2 t3 t4 {*}$fossil_args
28 fossil 3-way-merge t1 t3 t2 t5 {*}$fossil_args
29 set x [read_file t4]
30 regsub -all {<<<<<<< BEGIN MERGE CONFLICT.*<< \(line \d+\)} $x {>} x
 
31 regsub -all {\|\|\|\|\|\|\|.*======= \(line \d+\)} $x {=} x
32 regsub -all {>>>>>>> END MERGE CONFLICT.*>>>>} $x {<} x
33 set x [split [string trim $x] \n]
34 set y [read_file t5]
35 regsub -all {<<<<<<< BEGIN MERGE CONFLICT.*<< \(line \d+\)} $y {>} y
 
36 regsub -all {\|\|\|\|\|\|\|.*======= \(line \d+\)} $y {=} y
37 regsub -all {>>>>>>> END MERGE CONFLICT.*>>>>} $y {<} y
38 set y [split [string trim $y] \n]
39 set result1 [string trim $result1]
40 if {$x!=$result1} {
@@ -58,13 +60,13 @@
58 } {
59 1 2b 3b 4b 5 6b 7b 8b 9
60 } {
61 1 2 3 4c 5c 6c 7 8 9
62 } {
63 1 > 2b 3b 4b 5 6b 7b 8b = 2 3 4c 5c 6c 7 8 < 9
64 } {
65 1 > 2 3 4c 5c 6c 7 8 = 2b 3b 4b 5 6b 7b 8b < 9
66 } -expectError
67 merge-test 1001 {
68 1 2 3 4 5 6 7 8 9
69 } {
70 1 2b 3b 4 5 6 7b 8b 9
@@ -80,13 +82,13 @@
80 } {
81 2b 3b 4b 5 6b 7b 8b
82 } {
83 2 3 4c 5c 6c 7 8
84 } {
85 > 2b 3b 4b 5 6b 7b 8b = 2 3 4c 5c 6c 7 8 <
86 } {
87 > 2 3 4c 5c 6c 7 8 = 2b 3b 4b 5 6b 7b 8b <
88 } -expectError
89 merge-test 1003 {
90 2 3 4 5 6 7 8
91 } {
92 2b 3b 4 5 6 7b 8b
93
--- test/merge4.test
+++ test/merge4.test
@@ -26,15 +26,17 @@
26 write_file t3 [join [string trim $v2] \n]\n
27 fossil 3-way-merge t1 t2 t3 t4 {*}$fossil_args
28 fossil 3-way-merge t1 t3 t2 t5 {*}$fossil_args
29 set x [read_file t4]
30 regsub -all {<<<<<<< BEGIN MERGE CONFLICT.*<< \(line \d+\)} $x {>} x
31 regsub -all {####### SUGGESTED CONFLICT RESOLUTION.*##} $x {#} x
32 regsub -all {\|\|\|\|\|\|\|.*======= \(line \d+\)} $x {=} x
33 regsub -all {>>>>>>> END MERGE CONFLICT.*>>>>} $x {<} x
34 set x [split [string trim $x] \n]
35 set y [read_file t5]
36 regsub -all {<<<<<<< BEGIN MERGE CONFLICT.*<< \(line \d+\)} $y {>} y
37 regsub -all {####### SUGGESTED CONFLICT RESOLUTION.*##} $y {#} y
38 regsub -all {\|\|\|\|\|\|\|.*======= \(line \d+\)} $y {=} y
39 regsub -all {>>>>>>> END MERGE CONFLICT.*>>>>} $y {<} y
40 set y [split [string trim $y] \n]
41 set result1 [string trim $result1]
42 if {$x!=$result1} {
@@ -58,13 +60,13 @@
60 } {
61 1 2b 3b 4b 5 6b 7b 8b 9
62 } {
63 1 2 3 4c 5c 6c 7 8 9
64 } {
65 1 > 2b 3b 4b 5 6b 7b 8b # 2b 3b 4c 5c 6c 7b 8b = 2 3 4c 5c 6c 7 8 < 9
66 } {
67 1 > 2 3 4c 5c 6c 7 8 # 2b 3b 4b 5c 6b 7b 8b = 2b 3b 4b 5 6b 7b 8b < 9
68 } -expectError
69 merge-test 1001 {
70 1 2 3 4 5 6 7 8 9
71 } {
72 1 2b 3b 4 5 6 7b 8b 9
@@ -80,13 +82,13 @@
82 } {
83 2b 3b 4b 5 6b 7b 8b
84 } {
85 2 3 4c 5c 6c 7 8
86 } {
87 > 2b 3b 4b 5 6b 7b 8b # 2b 3b 4c 5c 6c 7b 8b = 2 3 4c 5c 6c 7 8 <
88 } {
89 > 2 3 4c 5c 6c 7 8 # 2b 3b 4b 5c 6b 7b 8b = 2b 3b 4b 5 6b 7b 8b <
90 } -expectError
91 merge-test 1003 {
92 2 3 4 5 6 7 8
93 } {
94 2b 3b 4 5 6 7b 8b
95
--- test/tester.tcl
+++ test/tester.tcl
@@ -332,10 +332,11 @@
332332
encoding-glob \
333333
exec-rel-paths \
334334
fileedit-glob \
335335
forbid-delta-manifests \
336336
forum-close-policy \
337
+ forum-title \
337338
gdiff-command \
338339
gmerge-command \
339340
hash-digits \
340341
hooks \
341342
http-port \
@@ -372,10 +373,11 @@
372373
ssl-identity \
373374
tclsh \
374375
th1-setup \
375376
th1-uri-regexp \
376377
ticket-default-report \
378
+ timeline-utc \
377379
user-color-map \
378380
uv-sync \
379381
web-browser]
380382
381383
fossil test-th-eval "hasfeature legacyMvRm"
382384
--- test/tester.tcl
+++ test/tester.tcl
@@ -332,10 +332,11 @@
332 encoding-glob \
333 exec-rel-paths \
334 fileedit-glob \
335 forbid-delta-manifests \
336 forum-close-policy \
 
337 gdiff-command \
338 gmerge-command \
339 hash-digits \
340 hooks \
341 http-port \
@@ -372,10 +373,11 @@
372 ssl-identity \
373 tclsh \
374 th1-setup \
375 th1-uri-regexp \
376 ticket-default-report \
 
377 user-color-map \
378 uv-sync \
379 web-browser]
380
381 fossil test-th-eval "hasfeature legacyMvRm"
382
--- test/tester.tcl
+++ test/tester.tcl
@@ -332,10 +332,11 @@
332 encoding-glob \
333 exec-rel-paths \
334 fileedit-glob \
335 forbid-delta-manifests \
336 forum-close-policy \
337 forum-title \
338 gdiff-command \
339 gmerge-command \
340 hash-digits \
341 hooks \
342 http-port \
@@ -372,10 +373,11 @@
373 ssl-identity \
374 tclsh \
375 th1-setup \
376 th1-uri-regexp \
377 ticket-default-report \
378 timeline-utc \
379 user-color-map \
380 uv-sync \
381 web-browser]
382
383 fossil test-th-eval "hasfeature legacyMvRm"
384
--- test/update.test
+++ test/update.test
@@ -57,11 +57,11 @@
5757
5858
###############################################################################
5959
6060
fossil update --verbose
6161
test update-already-up-to-date {
62
- [regexp {^-{79}\ncheckout: .*\nchanges: +None. Already up-to-date$} $RESULT]
62
+ [regexp {^-{79}\ncheckout: .*\nchanges: +None. Already up-to-date.$} $RESULT]
6363
}
6464
6565
# Remaining tests are carried out in the order update_cmd() performs checks.
6666
#
6767
# Common approach for tests below:
6868
--- test/update.test
+++ test/update.test
@@ -57,11 +57,11 @@
57
58 ###############################################################################
59
60 fossil update --verbose
61 test update-already-up-to-date {
62 [regexp {^-{79}\ncheckout: .*\nchanges: +None. Already up-to-date$} $RESULT]
63 }
64
65 # Remaining tests are carried out in the order update_cmd() performs checks.
66 #
67 # Common approach for tests below:
68
--- test/update.test
+++ test/update.test
@@ -57,11 +57,11 @@
57
58 ###############################################################################
59
60 fossil update --verbose
61 test update-already-up-to-date {
62 [regexp {^-{79}\ncheckout: .*\nchanges: +None. Already up-to-date.$} $RESULT]
63 }
64
65 # Remaining tests are carried out in the order update_cmd() performs checks.
66 #
67 # Common approach for tests below:
68
--- tools/makemake.tcl
+++ tools/makemake.tcl
@@ -132,10 +132,11 @@
132132
lookslike
133133
main
134134
manifest
135135
markdown
136136
markdown_html
137
+ match
137138
md5
138139
merge
139140
merge3
140141
moderate
141142
name
@@ -209,10 +210,11 @@
209210
# Additional resource files that get built into the executable.
210211
# These paths are all resolved from the src/ directory, so must
211212
# be relative to that.
212213
set extra_files {
213214
diff.tcl
215
+ merge.tcl
214216
markdown.md
215217
wiki.wiki
216218
*.js
217219
default.css
218220
style.*.css
@@ -445,11 +447,11 @@
445447
446448
# The USE_LINENOISE variable may be undefined, set to 0, or set
447449
# to 1. If it is set to 0, then there is no need to build or link
448450
# the linenoise.o object.
449451
LINENOISE_DEF.0 =
450
-LINENOISE_DEF.1 = -DHAVE_LINENOISE
452
+LINENOISE_DEF.1 = -DHAVE_LINENOISE=2
451453
LINENOISE_DEF. = $(LINENOISE_DEF.0)
452454
LINENOISE_OBJ.0 =
453455
LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o
454456
LINENOISE_OBJ. = $(LINENOISE_OBJ.0)
455457
456458
--- tools/makemake.tcl
+++ tools/makemake.tcl
@@ -132,10 +132,11 @@
132 lookslike
133 main
134 manifest
135 markdown
136 markdown_html
 
137 md5
138 merge
139 merge3
140 moderate
141 name
@@ -209,10 +210,11 @@
209 # Additional resource files that get built into the executable.
210 # These paths are all resolved from the src/ directory, so must
211 # be relative to that.
212 set extra_files {
213 diff.tcl
 
214 markdown.md
215 wiki.wiki
216 *.js
217 default.css
218 style.*.css
@@ -445,11 +447,11 @@
445
446 # The USE_LINENOISE variable may be undefined, set to 0, or set
447 # to 1. If it is set to 0, then there is no need to build or link
448 # the linenoise.o object.
449 LINENOISE_DEF.0 =
450 LINENOISE_DEF.1 = -DHAVE_LINENOISE
451 LINENOISE_DEF. = $(LINENOISE_DEF.0)
452 LINENOISE_OBJ.0 =
453 LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o
454 LINENOISE_OBJ. = $(LINENOISE_OBJ.0)
455
456
--- tools/makemake.tcl
+++ tools/makemake.tcl
@@ -132,10 +132,11 @@
132 lookslike
133 main
134 manifest
135 markdown
136 markdown_html
137 match
138 md5
139 merge
140 merge3
141 moderate
142 name
@@ -209,10 +210,11 @@
210 # Additional resource files that get built into the executable.
211 # These paths are all resolved from the src/ directory, so must
212 # be relative to that.
213 set extra_files {
214 diff.tcl
215 merge.tcl
216 markdown.md
217 wiki.wiki
218 *.js
219 default.css
220 style.*.css
@@ -445,11 +447,11 @@
447
448 # The USE_LINENOISE variable may be undefined, set to 0, or set
449 # to 1. If it is set to 0, then there is no need to build or link
450 # the linenoise.o object.
451 LINENOISE_DEF.0 =
452 LINENOISE_DEF.1 = -DHAVE_LINENOISE=2
453 LINENOISE_DEF. = $(LINENOISE_DEF.0)
454 LINENOISE_OBJ.0 =
455 LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o
456 LINENOISE_OBJ. = $(LINENOISE_OBJ.0)
457
458
--- tools/man_page_command_list.tcl
+++ tools/man_page_command_list.tcl
@@ -10,11 +10,11 @@
1010
set file [lindex $argv 0]
1111
}
1212
1313
# Get list of common commands.
1414
set commands [exec fossil help]
15
-regsub -nocase {.*?\ncommon commands:.*\n} $commands {} commands
15
+regsub -nocase {.*?\nfrequently used commands:.*\n} $commands {} commands
1616
regsub -nocase {\nthis is fossil version.*} $commands {} commands
1717
regsub -all {\s+} $commands " " commands
1818
set commands [lsort $commands]
1919
2020
# Compute number of rows.
2121
--- tools/man_page_command_list.tcl
+++ tools/man_page_command_list.tcl
@@ -10,11 +10,11 @@
10 set file [lindex $argv 0]
11 }
12
13 # Get list of common commands.
14 set commands [exec fossil help]
15 regsub -nocase {.*?\ncommon commands:.*\n} $commands {} commands
16 regsub -nocase {\nthis is fossil version.*} $commands {} commands
17 regsub -all {\s+} $commands " " commands
18 set commands [lsort $commands]
19
20 # Compute number of rows.
21
--- tools/man_page_command_list.tcl
+++ tools/man_page_command_list.tcl
@@ -10,11 +10,11 @@
10 set file [lindex $argv 0]
11 }
12
13 # Get list of common commands.
14 set commands [exec fossil help]
15 regsub -nocase {.*?\nfrequently used commands:.*\n} $commands {} commands
16 regsub -nocase {\nthis is fossil version.*} $commands {} commands
17 regsub -all {\s+} $commands " " commands
18 set commands [lsort $commands]
19
20 # Compute number of rows.
21
--- tools/translate.c
+++ tools/translate.c
@@ -78,10 +78,21 @@
7878
7979
/*
8080
** Name of files being processed
8181
*/
8282
static const char *zInFile = "(stdin)";
83
+
84
+/*
85
+** The `fossil_isspace()' function copied from the Fossil source code.
86
+** Some MSVC runtime library versions of `isspace()' break with an `assert()' if
87
+** the input is smaller than -1 or greater than 255 in debug builds, due to sign
88
+** extension when promoting `signed char' to `int' for non-ASCII characters. Use
89
+** an `isspace()' replacement instead of explicit type casts to `unsigned char'.
90
+*/
91
+int fossil_isspace(char c){
92
+ return c==' ' || (c<='\r' && c>='\t');
93
+}
8394
8495
/*
8596
** Terminate an active cgi_printf() or free string
8697
*/
8798
static void end_block(FILE *out){
@@ -106,21 +117,21 @@
106117
char zOut[4000]; /* The input line translated into appropriate output */
107118
108119
c1 = c2 = '-';
109120
while( fgets(zLine, sizeof(zLine), in) ){
110121
lineNo++;
111
- for(i=0; zLine[i] && isspace(zLine[i]); i++){}
122
+ for(i=0; zLine[i] && fossil_isspace(zLine[i]); i++){}
112123
if( zLine[i]!='@' ){
113124
if( inPrint || inStr ) end_block(out);
114125
fprintf(out,"%s",zLine);
115126
/* 0123456789 12345 */
116127
if( strncmp(zLine, "/* @-comment: ", 14)==0 ){
117128
c1 = zLine[14];
118129
c2 = zLine[15];
119130
}
120131
i += strlen(&zLine[i]);
121
- while( i>0 && isspace(zLine[i-1]) ){ i--; }
132
+ while( i>0 && fossil_isspace(zLine[i-1]) ){ i--; }
122133
lastWasEq = i>0 && zLine[i-1]=='=';
123134
lastWasComma = i>0 && zLine[i-1]==',';
124135
}else if( lastWasEq || lastWasComma){
125136
/* If the last non-whitespace character before the first @ was
126137
** an "="(var init/set) or a ","(const definition in list) then
@@ -129,11 +140,11 @@
129140
** and end of line.
130141
*/
131142
int indent, omitline;
132143
char *zNewline = "\\n";
133144
i++;
134
- if( isspace(zLine[i]) ){ i++; }
145
+ if( fossil_isspace(zLine[i]) ){ i++; }
135146
indent = i - 2;
136147
if( indent<0 ) indent = 0;
137148
omitline = 0;
138149
for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
139150
if( zLine[i]==c1 && (c2==' ' || zLine[i+1]==c2) ){
@@ -147,11 +158,11 @@
147158
break;
148159
}
149160
if( zLine[i]=='\\' || zLine[i]=='"' ){ zOut[j++] = '\\'; }
150161
zOut[j++] = zLine[i];
151162
}
152
- if( zNewline[0] ) while( j>0 && isspace(zOut[j-1]) ){ j--; }
163
+ if( zNewline[0] ) while( j>0 && fossil_isspace(zOut[j-1]) ){ j--; }
153164
zOut[j] = 0;
154165
if( j<=0 && omitline ){
155166
fprintf(out,"\n");
156167
}else{
157168
fprintf(out,"%*s\"%s%s\"\n",indent, "", zOut, zNewline);
@@ -171,11 +182,11 @@
171182
int indent;
172183
int nC;
173184
int nParam;
174185
char c;
175186
i++;
176
- if( isspace(zLine[i]) ){ i++; }
187
+ if( fossil_isspace(zLine[i]) ){ i++; }
177188
indent = i;
178189
for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
179190
if( zLine[i]=='\\' && (!zLine[i+1] || zLine[i+1]=='\r'
180191
|| zLine[i+1]=='\n') ){
181192
zNewline = "";
182193
--- tools/translate.c
+++ tools/translate.c
@@ -78,10 +78,21 @@
78
79 /*
80 ** Name of files being processed
81 */
82 static const char *zInFile = "(stdin)";
 
 
 
 
 
 
 
 
 
 
 
83
84 /*
85 ** Terminate an active cgi_printf() or free string
86 */
87 static void end_block(FILE *out){
@@ -106,21 +117,21 @@
106 char zOut[4000]; /* The input line translated into appropriate output */
107
108 c1 = c2 = '-';
109 while( fgets(zLine, sizeof(zLine), in) ){
110 lineNo++;
111 for(i=0; zLine[i] && isspace(zLine[i]); i++){}
112 if( zLine[i]!='@' ){
113 if( inPrint || inStr ) end_block(out);
114 fprintf(out,"%s",zLine);
115 /* 0123456789 12345 */
116 if( strncmp(zLine, "/* @-comment: ", 14)==0 ){
117 c1 = zLine[14];
118 c2 = zLine[15];
119 }
120 i += strlen(&zLine[i]);
121 while( i>0 && isspace(zLine[i-1]) ){ i--; }
122 lastWasEq = i>0 && zLine[i-1]=='=';
123 lastWasComma = i>0 && zLine[i-1]==',';
124 }else if( lastWasEq || lastWasComma){
125 /* If the last non-whitespace character before the first @ was
126 ** an "="(var init/set) or a ","(const definition in list) then
@@ -129,11 +140,11 @@
129 ** and end of line.
130 */
131 int indent, omitline;
132 char *zNewline = "\\n";
133 i++;
134 if( isspace(zLine[i]) ){ i++; }
135 indent = i - 2;
136 if( indent<0 ) indent = 0;
137 omitline = 0;
138 for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
139 if( zLine[i]==c1 && (c2==' ' || zLine[i+1]==c2) ){
@@ -147,11 +158,11 @@
147 break;
148 }
149 if( zLine[i]=='\\' || zLine[i]=='"' ){ zOut[j++] = '\\'; }
150 zOut[j++] = zLine[i];
151 }
152 if( zNewline[0] ) while( j>0 && isspace(zOut[j-1]) ){ j--; }
153 zOut[j] = 0;
154 if( j<=0 && omitline ){
155 fprintf(out,"\n");
156 }else{
157 fprintf(out,"%*s\"%s%s\"\n",indent, "", zOut, zNewline);
@@ -171,11 +182,11 @@
171 int indent;
172 int nC;
173 int nParam;
174 char c;
175 i++;
176 if( isspace(zLine[i]) ){ i++; }
177 indent = i;
178 for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
179 if( zLine[i]=='\\' && (!zLine[i+1] || zLine[i+1]=='\r'
180 || zLine[i+1]=='\n') ){
181 zNewline = "";
182
--- tools/translate.c
+++ tools/translate.c
@@ -78,10 +78,21 @@
78
79 /*
80 ** Name of files being processed
81 */
82 static const char *zInFile = "(stdin)";
83
84 /*
85 ** The `fossil_isspace()' function copied from the Fossil source code.
86 ** Some MSVC runtime library versions of `isspace()' break with an `assert()' if
87 ** the input is smaller than -1 or greater than 255 in debug builds, due to sign
88 ** extension when promoting `signed char' to `int' for non-ASCII characters. Use
89 ** an `isspace()' replacement instead of explicit type casts to `unsigned char'.
90 */
91 int fossil_isspace(char c){
92 return c==' ' || (c<='\r' && c>='\t');
93 }
94
95 /*
96 ** Terminate an active cgi_printf() or free string
97 */
98 static void end_block(FILE *out){
@@ -106,21 +117,21 @@
117 char zOut[4000]; /* The input line translated into appropriate output */
118
119 c1 = c2 = '-';
120 while( fgets(zLine, sizeof(zLine), in) ){
121 lineNo++;
122 for(i=0; zLine[i] && fossil_isspace(zLine[i]); i++){}
123 if( zLine[i]!='@' ){
124 if( inPrint || inStr ) end_block(out);
125 fprintf(out,"%s",zLine);
126 /* 0123456789 12345 */
127 if( strncmp(zLine, "/* @-comment: ", 14)==0 ){
128 c1 = zLine[14];
129 c2 = zLine[15];
130 }
131 i += strlen(&zLine[i]);
132 while( i>0 && fossil_isspace(zLine[i-1]) ){ i--; }
133 lastWasEq = i>0 && zLine[i-1]=='=';
134 lastWasComma = i>0 && zLine[i-1]==',';
135 }else if( lastWasEq || lastWasComma){
136 /* If the last non-whitespace character before the first @ was
137 ** an "="(var init/set) or a ","(const definition in list) then
@@ -129,11 +140,11 @@
140 ** and end of line.
141 */
142 int indent, omitline;
143 char *zNewline = "\\n";
144 i++;
145 if( fossil_isspace(zLine[i]) ){ i++; }
146 indent = i - 2;
147 if( indent<0 ) indent = 0;
148 omitline = 0;
149 for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
150 if( zLine[i]==c1 && (c2==' ' || zLine[i+1]==c2) ){
@@ -147,11 +158,11 @@
158 break;
159 }
160 if( zLine[i]=='\\' || zLine[i]=='"' ){ zOut[j++] = '\\'; }
161 zOut[j++] = zLine[i];
162 }
163 if( zNewline[0] ) while( j>0 && fossil_isspace(zOut[j-1]) ){ j--; }
164 zOut[j] = 0;
165 if( j<=0 && omitline ){
166 fprintf(out,"\n");
167 }else{
168 fprintf(out,"%*s\"%s%s\"\n",indent, "", zOut, zNewline);
@@ -171,11 +182,11 @@
182 int indent;
183 int nC;
184 int nParam;
185 char c;
186 i++;
187 if( fossil_isspace(zLine[i]) ){ i++; }
188 indent = i;
189 for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
190 if( zLine[i]=='\\' && (!zLine[i+1] || zLine[i+1]=='\r'
191 || zLine[i+1]=='\n') ){
192 zNewline = "";
193
+10 -4
--- win/Makefile.dmc
+++ win/Makefile.dmc
@@ -32,13 +32,13 @@
3232
3333
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
3434
3535
PIKCHR_OPTIONS = -DPIKCHR_TOKEN_LIMIT=10000
3636
37
-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
37
+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 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
3838
39
-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
39
+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)\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
4040
4141
4242
RC=$(DMDIR)\bin\rcc
4343
RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__
4444
@@ -53,11 +53,11 @@
5353
5454
$(OBJDIR)\fossil.res: $B\win\fossil.rc
5555
$(RC) $(RCFLAGS) -o$@ $**
5656
5757
$(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
58
- +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 > $@
58
+ +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 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 > $@
5959
+echo fossil >> $@
6060
+echo fossil >> $@
6161
+echo $(LIBS) >> $@
6262
+echo. >> $@
6363
+echo fossil >> $@
@@ -635,10 +635,16 @@
635635
$(OBJDIR)\markdown_html$O : markdown_html_.c markdown_html.h
636636
$(TCC) -o$@ -c markdown_html_.c
637637
638638
markdown_html_.c : $(SRCDIR)\markdown_html.c
639639
+translate$E $** > $@
640
+
641
+$(OBJDIR)\match$O : match_.c match.h
642
+ $(TCC) -o$@ -c match_.c
643
+
644
+match_.c : $(SRCDIR)\match.c
645
+ +translate$E $** > $@
640646
641647
$(OBJDIR)\md5$O : md5_.c md5.h
642648
$(TCC) -o$@ -c md5_.c
643649
644650
md5_.c : $(SRCDIR)\md5.c
@@ -1009,7 +1015,7 @@
10091015
10101016
zip_.c : $(SRCDIR)\zip.c
10111017
+translate$E $** > $@
10121018
10131019
headers: makeheaders$E page_index.h builtin_data.h VERSION.h
1014
- +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
1020
+ +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 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
10151021
@copy /Y nul: headers
10161022
--- win/Makefile.dmc
+++ win/Makefile.dmc
@@ -32,13 +32,13 @@
32
33 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
34
35 PIKCHR_OPTIONS = -DPIKCHR_TOKEN_LIMIT=10000
36
37 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
38
39 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
40
41
42 RC=$(DMDIR)\bin\rcc
43 RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__
44
@@ -53,11 +53,11 @@
53
54 $(OBJDIR)\fossil.res: $B\win\fossil.rc
55 $(RC) $(RCFLAGS) -o$@ $**
56
57 $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
58 +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 > $@
59 +echo fossil >> $@
60 +echo fossil >> $@
61 +echo $(LIBS) >> $@
62 +echo. >> $@
63 +echo fossil >> $@
@@ -635,10 +635,16 @@
635 $(OBJDIR)\markdown_html$O : markdown_html_.c markdown_html.h
636 $(TCC) -o$@ -c markdown_html_.c
637
638 markdown_html_.c : $(SRCDIR)\markdown_html.c
639 +translate$E $** > $@
 
 
 
 
 
 
640
641 $(OBJDIR)\md5$O : md5_.c md5.h
642 $(TCC) -o$@ -c md5_.c
643
644 md5_.c : $(SRCDIR)\md5.c
@@ -1009,7 +1015,7 @@
1009
1010 zip_.c : $(SRCDIR)\zip.c
1011 +translate$E $** > $@
1012
1013 headers: makeheaders$E page_index.h builtin_data.h VERSION.h
1014 +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
1015 @copy /Y nul: headers
1016
--- win/Makefile.dmc
+++ win/Makefile.dmc
@@ -32,13 +32,13 @@
32
33 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
34
35 PIKCHR_OPTIONS = -DPIKCHR_TOKEN_LIMIT=10000
36
37 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 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
38
39 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)\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
40
41
42 RC=$(DMDIR)\bin\rcc
43 RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__
44
@@ -53,11 +53,11 @@
53
54 $(OBJDIR)\fossil.res: $B\win\fossil.rc
55 $(RC) $(RCFLAGS) -o$@ $**
56
57 $(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
58 +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 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 > $@
59 +echo fossil >> $@
60 +echo fossil >> $@
61 +echo $(LIBS) >> $@
62 +echo. >> $@
63 +echo fossil >> $@
@@ -635,10 +635,16 @@
635 $(OBJDIR)\markdown_html$O : markdown_html_.c markdown_html.h
636 $(TCC) -o$@ -c markdown_html_.c
637
638 markdown_html_.c : $(SRCDIR)\markdown_html.c
639 +translate$E $** > $@
640
641 $(OBJDIR)\match$O : match_.c match.h
642 $(TCC) -o$@ -c match_.c
643
644 match_.c : $(SRCDIR)\match.c
645 +translate$E $** > $@
646
647 $(OBJDIR)\md5$O : md5_.c md5.h
648 $(TCC) -o$@ -c md5_.c
649
650 md5_.c : $(SRCDIR)\md5.c
@@ -1009,7 +1015,7 @@
1015
1016 zip_.c : $(SRCDIR)\zip.c
1017 +translate$E $** > $@
1018
1019 headers: makeheaders$E page_index.h builtin_data.h VERSION.h
1020 +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 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
1021 @copy /Y nul: headers
1022
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -485,10 +485,11 @@
485485
$(SRCDIR)/lookslike.c \
486486
$(SRCDIR)/main.c \
487487
$(SRCDIR)/manifest.c \
488488
$(SRCDIR)/markdown.c \
489489
$(SRCDIR)/markdown_html.c \
490
+ $(SRCDIR)/match.c \
490491
$(SRCDIR)/md5.c \
491492
$(SRCDIR)/merge.c \
492493
$(SRCDIR)/merge3.c \
493494
$(SRCDIR)/moderate.c \
494495
$(SRCDIR)/name.c \
@@ -634,10 +635,11 @@
634635
$(SRCDIR)/hbmenu.js \
635636
$(SRCDIR)/href.js \
636637
$(SRCDIR)/login.js \
637638
$(SRCDIR)/markdown.md \
638639
$(SRCDIR)/menu.js \
640
+ $(SRCDIR)/merge.tcl \
639641
$(SRCDIR)/scroll.js \
640642
$(SRCDIR)/skin.js \
641643
$(SRCDIR)/sorttable.js \
642644
$(SRCDIR)/sounds/0.wav \
643645
$(SRCDIR)/sounds/1.wav \
@@ -749,10 +751,11 @@
749751
$(OBJDIR)/lookslike_.c \
750752
$(OBJDIR)/main_.c \
751753
$(OBJDIR)/manifest_.c \
752754
$(OBJDIR)/markdown_.c \
753755
$(OBJDIR)/markdown_html_.c \
756
+ $(OBJDIR)/match_.c \
754757
$(OBJDIR)/md5_.c \
755758
$(OBJDIR)/merge_.c \
756759
$(OBJDIR)/merge3_.c \
757760
$(OBJDIR)/moderate_.c \
758761
$(OBJDIR)/name_.c \
@@ -898,10 +901,11 @@
898901
$(OBJDIR)/lookslike.o \
899902
$(OBJDIR)/main.o \
900903
$(OBJDIR)/manifest.o \
901904
$(OBJDIR)/markdown.o \
902905
$(OBJDIR)/markdown_html.o \
906
+ $(OBJDIR)/match.o \
903907
$(OBJDIR)/md5.o \
904908
$(OBJDIR)/merge.o \
905909
$(OBJDIR)/merge3.o \
906910
$(OBJDIR)/moderate.o \
907911
$(OBJDIR)/name.o \
@@ -1251,10 +1255,11 @@
12511255
$(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \
12521256
$(OBJDIR)/main_.c:$(OBJDIR)/main.h \
12531257
$(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \
12541258
$(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \
12551259
$(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \
1260
+ $(OBJDIR)/match_.c:$(OBJDIR)/match.h \
12561261
$(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \
12571262
$(OBJDIR)/merge_.c:$(OBJDIR)/merge.h \
12581263
$(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h \
12591264
$(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h \
12601265
$(OBJDIR)/name_.c:$(OBJDIR)/name.h \
@@ -2002,10 +2007,18 @@
20022007
20032008
$(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h
20042009
$(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c
20052010
20062011
$(OBJDIR)/markdown_html.h: $(OBJDIR)/headers
2012
+
2013
+$(OBJDIR)/match_.c: $(SRCDIR)/match.c $(TRANSLATE)
2014
+ $(TRANSLATE) $(SRCDIR)/match.c >$@
2015
+
2016
+$(OBJDIR)/match.o: $(OBJDIR)/match_.c $(OBJDIR)/match.h $(SRCDIR)/config.h
2017
+ $(XTCC) -o $(OBJDIR)/match.o -c $(OBJDIR)/match_.c
2018
+
2019
+$(OBJDIR)/match.h: $(OBJDIR)/headers
20072020
20082021
$(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(TRANSLATE)
20092022
$(TRANSLATE) $(SRCDIR)/md5.c >$@
20102023
20112024
$(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h
20122025
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -485,10 +485,11 @@
485 $(SRCDIR)/lookslike.c \
486 $(SRCDIR)/main.c \
487 $(SRCDIR)/manifest.c \
488 $(SRCDIR)/markdown.c \
489 $(SRCDIR)/markdown_html.c \
 
490 $(SRCDIR)/md5.c \
491 $(SRCDIR)/merge.c \
492 $(SRCDIR)/merge3.c \
493 $(SRCDIR)/moderate.c \
494 $(SRCDIR)/name.c \
@@ -634,10 +635,11 @@
634 $(SRCDIR)/hbmenu.js \
635 $(SRCDIR)/href.js \
636 $(SRCDIR)/login.js \
637 $(SRCDIR)/markdown.md \
638 $(SRCDIR)/menu.js \
 
639 $(SRCDIR)/scroll.js \
640 $(SRCDIR)/skin.js \
641 $(SRCDIR)/sorttable.js \
642 $(SRCDIR)/sounds/0.wav \
643 $(SRCDIR)/sounds/1.wav \
@@ -749,10 +751,11 @@
749 $(OBJDIR)/lookslike_.c \
750 $(OBJDIR)/main_.c \
751 $(OBJDIR)/manifest_.c \
752 $(OBJDIR)/markdown_.c \
753 $(OBJDIR)/markdown_html_.c \
 
754 $(OBJDIR)/md5_.c \
755 $(OBJDIR)/merge_.c \
756 $(OBJDIR)/merge3_.c \
757 $(OBJDIR)/moderate_.c \
758 $(OBJDIR)/name_.c \
@@ -898,10 +901,11 @@
898 $(OBJDIR)/lookslike.o \
899 $(OBJDIR)/main.o \
900 $(OBJDIR)/manifest.o \
901 $(OBJDIR)/markdown.o \
902 $(OBJDIR)/markdown_html.o \
 
903 $(OBJDIR)/md5.o \
904 $(OBJDIR)/merge.o \
905 $(OBJDIR)/merge3.o \
906 $(OBJDIR)/moderate.o \
907 $(OBJDIR)/name.o \
@@ -1251,10 +1255,11 @@
1251 $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \
1252 $(OBJDIR)/main_.c:$(OBJDIR)/main.h \
1253 $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \
1254 $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \
1255 $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \
 
1256 $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \
1257 $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h \
1258 $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h \
1259 $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h \
1260 $(OBJDIR)/name_.c:$(OBJDIR)/name.h \
@@ -2002,10 +2007,18 @@
2002
2003 $(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h
2004 $(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c
2005
2006 $(OBJDIR)/markdown_html.h: $(OBJDIR)/headers
 
 
 
 
 
 
 
 
2007
2008 $(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(TRANSLATE)
2009 $(TRANSLATE) $(SRCDIR)/md5.c >$@
2010
2011 $(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h
2012
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -485,10 +485,11 @@
485 $(SRCDIR)/lookslike.c \
486 $(SRCDIR)/main.c \
487 $(SRCDIR)/manifest.c \
488 $(SRCDIR)/markdown.c \
489 $(SRCDIR)/markdown_html.c \
490 $(SRCDIR)/match.c \
491 $(SRCDIR)/md5.c \
492 $(SRCDIR)/merge.c \
493 $(SRCDIR)/merge3.c \
494 $(SRCDIR)/moderate.c \
495 $(SRCDIR)/name.c \
@@ -634,10 +635,11 @@
635 $(SRCDIR)/hbmenu.js \
636 $(SRCDIR)/href.js \
637 $(SRCDIR)/login.js \
638 $(SRCDIR)/markdown.md \
639 $(SRCDIR)/menu.js \
640 $(SRCDIR)/merge.tcl \
641 $(SRCDIR)/scroll.js \
642 $(SRCDIR)/skin.js \
643 $(SRCDIR)/sorttable.js \
644 $(SRCDIR)/sounds/0.wav \
645 $(SRCDIR)/sounds/1.wav \
@@ -749,10 +751,11 @@
751 $(OBJDIR)/lookslike_.c \
752 $(OBJDIR)/main_.c \
753 $(OBJDIR)/manifest_.c \
754 $(OBJDIR)/markdown_.c \
755 $(OBJDIR)/markdown_html_.c \
756 $(OBJDIR)/match_.c \
757 $(OBJDIR)/md5_.c \
758 $(OBJDIR)/merge_.c \
759 $(OBJDIR)/merge3_.c \
760 $(OBJDIR)/moderate_.c \
761 $(OBJDIR)/name_.c \
@@ -898,10 +901,11 @@
901 $(OBJDIR)/lookslike.o \
902 $(OBJDIR)/main.o \
903 $(OBJDIR)/manifest.o \
904 $(OBJDIR)/markdown.o \
905 $(OBJDIR)/markdown_html.o \
906 $(OBJDIR)/match.o \
907 $(OBJDIR)/md5.o \
908 $(OBJDIR)/merge.o \
909 $(OBJDIR)/merge3.o \
910 $(OBJDIR)/moderate.o \
911 $(OBJDIR)/name.o \
@@ -1251,10 +1255,11 @@
1255 $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \
1256 $(OBJDIR)/main_.c:$(OBJDIR)/main.h \
1257 $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \
1258 $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \
1259 $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \
1260 $(OBJDIR)/match_.c:$(OBJDIR)/match.h \
1261 $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \
1262 $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h \
1263 $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h \
1264 $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h \
1265 $(OBJDIR)/name_.c:$(OBJDIR)/name.h \
@@ -2002,10 +2007,18 @@
2007
2008 $(OBJDIR)/markdown_html.o: $(OBJDIR)/markdown_html_.c $(OBJDIR)/markdown_html.h $(SRCDIR)/config.h
2009 $(XTCC) -o $(OBJDIR)/markdown_html.o -c $(OBJDIR)/markdown_html_.c
2010
2011 $(OBJDIR)/markdown_html.h: $(OBJDIR)/headers
2012
2013 $(OBJDIR)/match_.c: $(SRCDIR)/match.c $(TRANSLATE)
2014 $(TRANSLATE) $(SRCDIR)/match.c >$@
2015
2016 $(OBJDIR)/match.o: $(OBJDIR)/match_.c $(OBJDIR)/match.h $(SRCDIR)/config.h
2017 $(XTCC) -o $(OBJDIR)/match.o -c $(OBJDIR)/match_.c
2018
2019 $(OBJDIR)/match.h: $(OBJDIR)/headers
2020
2021 $(OBJDIR)/md5_.c: $(SRCDIR)/md5.c $(TRANSLATE)
2022 $(TRANSLATE) $(SRCDIR)/md5.c >$@
2023
2024 $(OBJDIR)/md5.o: $(OBJDIR)/md5_.c $(OBJDIR)/md5.h $(SRCDIR)/config.h
2025
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -443,10 +443,11 @@
443443
"$(OX)\lookslike_.c" \
444444
"$(OX)\main_.c" \
445445
"$(OX)\manifest_.c" \
446446
"$(OX)\markdown_.c" \
447447
"$(OX)\markdown_html_.c" \
448
+ "$(OX)\match_.c" \
448449
"$(OX)\md5_.c" \
449450
"$(OX)\merge_.c" \
450451
"$(OX)\merge3_.c" \
451452
"$(OX)\moderate_.c" \
452453
"$(OX)\name_.c" \
@@ -592,10 +593,11 @@
592593
"$(SRCDIR)\hbmenu.js" \
593594
"$(SRCDIR)\href.js" \
594595
"$(SRCDIR)\login.js" \
595596
"$(SRCDIR)\markdown.md" \
596597
"$(SRCDIR)\menu.js" \
598
+ "$(SRCDIR)\merge.tcl" \
597599
"$(SRCDIR)\scroll.js" \
598600
"$(SRCDIR)\skin.js" \
599601
"$(SRCDIR)\sorttable.js" \
600602
"$(SRCDIR)\sounds\0.wav" \
601603
"$(SRCDIR)\sounds\1.wav" \
@@ -707,10 +709,11 @@
707709
"$(OX)\lookslike$O" \
708710
"$(OX)\main$O" \
709711
"$(OX)\manifest$O" \
710712
"$(OX)\markdown$O" \
711713
"$(OX)\markdown_html$O" \
714
+ "$(OX)\match$O" \
712715
"$(OX)\md5$O" \
713716
"$(OX)\merge$O" \
714717
"$(OX)\merge3$O" \
715718
"$(OX)\moderate$O" \
716719
"$(OX)\name$O" \
@@ -956,10 +959,11 @@
956959
echo "$(OX)\lookslike.obj" >> $@
957960
echo "$(OX)\main.obj" >> $@
958961
echo "$(OX)\manifest.obj" >> $@
959962
echo "$(OX)\markdown.obj" >> $@
960963
echo "$(OX)\markdown_html.obj" >> $@
964
+ echo "$(OX)\match.obj" >> $@
961965
echo "$(OX)\md5.obj" >> $@
962966
echo "$(OX)\merge.obj" >> $@
963967
echo "$(OX)\merge3.obj" >> $@
964968
echo "$(OX)\moderate.obj" >> $@
965969
echo "$(OX)\name.obj" >> $@
@@ -1222,10 +1226,11 @@
12221226
echo "$(SRCDIR)\hbmenu.js" >> $@
12231227
echo "$(SRCDIR)\href.js" >> $@
12241228
echo "$(SRCDIR)\login.js" >> $@
12251229
echo "$(SRCDIR)\markdown.md" >> $@
12261230
echo "$(SRCDIR)\menu.js" >> $@
1231
+ echo "$(SRCDIR)\merge.tcl" >> $@
12271232
echo "$(SRCDIR)\scroll.js" >> $@
12281233
echo "$(SRCDIR)\skin.js" >> $@
12291234
echo "$(SRCDIR)\sorttable.js" >> $@
12301235
echo "$(SRCDIR)\sounds/0.wav" >> $@
12311236
echo "$(SRCDIR)\sounds/1.wav" >> $@
@@ -1760,10 +1765,16 @@
17601765
"$(OX)\markdown_html$O" : "$(OX)\markdown_html_.c" "$(OX)\markdown_html.h"
17611766
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\markdown_html_.c"
17621767
17631768
"$(OX)\markdown_html_.c" : "$(SRCDIR)\markdown_html.c"
17641769
"$(OBJDIR)\translate$E" $** > $@
1770
+
1771
+"$(OX)\match$O" : "$(OX)\match_.c" "$(OX)\match.h"
1772
+ $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\match_.c"
1773
+
1774
+"$(OX)\match_.c" : "$(SRCDIR)\match.c"
1775
+ "$(OBJDIR)\translate$E" $** > $@
17651776
17661777
"$(OX)\md5$O" : "$(OX)\md5_.c" "$(OX)\md5.h"
17671778
$(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\md5_.c"
17681779
17691780
"$(OX)\md5_.c" : "$(SRCDIR)\md5.c"
@@ -2222,10 +2233,11 @@
22222233
"$(OX)\lookslike_.c":"$(OX)\lookslike.h" \
22232234
"$(OX)\main_.c":"$(OX)\main.h" \
22242235
"$(OX)\manifest_.c":"$(OX)\manifest.h" \
22252236
"$(OX)\markdown_.c":"$(OX)\markdown.h" \
22262237
"$(OX)\markdown_html_.c":"$(OX)\markdown_html.h" \
2238
+ "$(OX)\match_.c":"$(OX)\match.h" \
22272239
"$(OX)\md5_.c":"$(OX)\md5.h" \
22282240
"$(OX)\merge_.c":"$(OX)\merge.h" \
22292241
"$(OX)\merge3_.c":"$(OX)\merge3.h" \
22302242
"$(OX)\moderate_.c":"$(OX)\moderate.h" \
22312243
"$(OX)\name_.c":"$(OX)\name.h" \
22322244
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -443,10 +443,11 @@
443 "$(OX)\lookslike_.c" \
444 "$(OX)\main_.c" \
445 "$(OX)\manifest_.c" \
446 "$(OX)\markdown_.c" \
447 "$(OX)\markdown_html_.c" \
 
448 "$(OX)\md5_.c" \
449 "$(OX)\merge_.c" \
450 "$(OX)\merge3_.c" \
451 "$(OX)\moderate_.c" \
452 "$(OX)\name_.c" \
@@ -592,10 +593,11 @@
592 "$(SRCDIR)\hbmenu.js" \
593 "$(SRCDIR)\href.js" \
594 "$(SRCDIR)\login.js" \
595 "$(SRCDIR)\markdown.md" \
596 "$(SRCDIR)\menu.js" \
 
597 "$(SRCDIR)\scroll.js" \
598 "$(SRCDIR)\skin.js" \
599 "$(SRCDIR)\sorttable.js" \
600 "$(SRCDIR)\sounds\0.wav" \
601 "$(SRCDIR)\sounds\1.wav" \
@@ -707,10 +709,11 @@
707 "$(OX)\lookslike$O" \
708 "$(OX)\main$O" \
709 "$(OX)\manifest$O" \
710 "$(OX)\markdown$O" \
711 "$(OX)\markdown_html$O" \
 
712 "$(OX)\md5$O" \
713 "$(OX)\merge$O" \
714 "$(OX)\merge3$O" \
715 "$(OX)\moderate$O" \
716 "$(OX)\name$O" \
@@ -956,10 +959,11 @@
956 echo "$(OX)\lookslike.obj" >> $@
957 echo "$(OX)\main.obj" >> $@
958 echo "$(OX)\manifest.obj" >> $@
959 echo "$(OX)\markdown.obj" >> $@
960 echo "$(OX)\markdown_html.obj" >> $@
 
961 echo "$(OX)\md5.obj" >> $@
962 echo "$(OX)\merge.obj" >> $@
963 echo "$(OX)\merge3.obj" >> $@
964 echo "$(OX)\moderate.obj" >> $@
965 echo "$(OX)\name.obj" >> $@
@@ -1222,10 +1226,11 @@
1222 echo "$(SRCDIR)\hbmenu.js" >> $@
1223 echo "$(SRCDIR)\href.js" >> $@
1224 echo "$(SRCDIR)\login.js" >> $@
1225 echo "$(SRCDIR)\markdown.md" >> $@
1226 echo "$(SRCDIR)\menu.js" >> $@
 
1227 echo "$(SRCDIR)\scroll.js" >> $@
1228 echo "$(SRCDIR)\skin.js" >> $@
1229 echo "$(SRCDIR)\sorttable.js" >> $@
1230 echo "$(SRCDIR)\sounds/0.wav" >> $@
1231 echo "$(SRCDIR)\sounds/1.wav" >> $@
@@ -1760,10 +1765,16 @@
1760 "$(OX)\markdown_html$O" : "$(OX)\markdown_html_.c" "$(OX)\markdown_html.h"
1761 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\markdown_html_.c"
1762
1763 "$(OX)\markdown_html_.c" : "$(SRCDIR)\markdown_html.c"
1764 "$(OBJDIR)\translate$E" $** > $@
 
 
 
 
 
 
1765
1766 "$(OX)\md5$O" : "$(OX)\md5_.c" "$(OX)\md5.h"
1767 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\md5_.c"
1768
1769 "$(OX)\md5_.c" : "$(SRCDIR)\md5.c"
@@ -2222,10 +2233,11 @@
2222 "$(OX)\lookslike_.c":"$(OX)\lookslike.h" \
2223 "$(OX)\main_.c":"$(OX)\main.h" \
2224 "$(OX)\manifest_.c":"$(OX)\manifest.h" \
2225 "$(OX)\markdown_.c":"$(OX)\markdown.h" \
2226 "$(OX)\markdown_html_.c":"$(OX)\markdown_html.h" \
 
2227 "$(OX)\md5_.c":"$(OX)\md5.h" \
2228 "$(OX)\merge_.c":"$(OX)\merge.h" \
2229 "$(OX)\merge3_.c":"$(OX)\merge3.h" \
2230 "$(OX)\moderate_.c":"$(OX)\moderate.h" \
2231 "$(OX)\name_.c":"$(OX)\name.h" \
2232
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -443,10 +443,11 @@
443 "$(OX)\lookslike_.c" \
444 "$(OX)\main_.c" \
445 "$(OX)\manifest_.c" \
446 "$(OX)\markdown_.c" \
447 "$(OX)\markdown_html_.c" \
448 "$(OX)\match_.c" \
449 "$(OX)\md5_.c" \
450 "$(OX)\merge_.c" \
451 "$(OX)\merge3_.c" \
452 "$(OX)\moderate_.c" \
453 "$(OX)\name_.c" \
@@ -592,10 +593,11 @@
593 "$(SRCDIR)\hbmenu.js" \
594 "$(SRCDIR)\href.js" \
595 "$(SRCDIR)\login.js" \
596 "$(SRCDIR)\markdown.md" \
597 "$(SRCDIR)\menu.js" \
598 "$(SRCDIR)\merge.tcl" \
599 "$(SRCDIR)\scroll.js" \
600 "$(SRCDIR)\skin.js" \
601 "$(SRCDIR)\sorttable.js" \
602 "$(SRCDIR)\sounds\0.wav" \
603 "$(SRCDIR)\sounds\1.wav" \
@@ -707,10 +709,11 @@
709 "$(OX)\lookslike$O" \
710 "$(OX)\main$O" \
711 "$(OX)\manifest$O" \
712 "$(OX)\markdown$O" \
713 "$(OX)\markdown_html$O" \
714 "$(OX)\match$O" \
715 "$(OX)\md5$O" \
716 "$(OX)\merge$O" \
717 "$(OX)\merge3$O" \
718 "$(OX)\moderate$O" \
719 "$(OX)\name$O" \
@@ -956,10 +959,11 @@
959 echo "$(OX)\lookslike.obj" >> $@
960 echo "$(OX)\main.obj" >> $@
961 echo "$(OX)\manifest.obj" >> $@
962 echo "$(OX)\markdown.obj" >> $@
963 echo "$(OX)\markdown_html.obj" >> $@
964 echo "$(OX)\match.obj" >> $@
965 echo "$(OX)\md5.obj" >> $@
966 echo "$(OX)\merge.obj" >> $@
967 echo "$(OX)\merge3.obj" >> $@
968 echo "$(OX)\moderate.obj" >> $@
969 echo "$(OX)\name.obj" >> $@
@@ -1222,10 +1226,11 @@
1226 echo "$(SRCDIR)\hbmenu.js" >> $@
1227 echo "$(SRCDIR)\href.js" >> $@
1228 echo "$(SRCDIR)\login.js" >> $@
1229 echo "$(SRCDIR)\markdown.md" >> $@
1230 echo "$(SRCDIR)\menu.js" >> $@
1231 echo "$(SRCDIR)\merge.tcl" >> $@
1232 echo "$(SRCDIR)\scroll.js" >> $@
1233 echo "$(SRCDIR)\skin.js" >> $@
1234 echo "$(SRCDIR)\sorttable.js" >> $@
1235 echo "$(SRCDIR)\sounds/0.wav" >> $@
1236 echo "$(SRCDIR)\sounds/1.wav" >> $@
@@ -1760,10 +1765,16 @@
1765 "$(OX)\markdown_html$O" : "$(OX)\markdown_html_.c" "$(OX)\markdown_html.h"
1766 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\markdown_html_.c"
1767
1768 "$(OX)\markdown_html_.c" : "$(SRCDIR)\markdown_html.c"
1769 "$(OBJDIR)\translate$E" $** > $@
1770
1771 "$(OX)\match$O" : "$(OX)\match_.c" "$(OX)\match.h"
1772 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\match_.c"
1773
1774 "$(OX)\match_.c" : "$(SRCDIR)\match.c"
1775 "$(OBJDIR)\translate$E" $** > $@
1776
1777 "$(OX)\md5$O" : "$(OX)\md5_.c" "$(OX)\md5.h"
1778 $(TCC) /Fo$@ /Fd$(@D)\ -c "$(OX)\md5_.c"
1779
1780 "$(OX)\md5_.c" : "$(SRCDIR)\md5.c"
@@ -2222,10 +2233,11 @@
2233 "$(OX)\lookslike_.c":"$(OX)\lookslike.h" \
2234 "$(OX)\main_.c":"$(OX)\main.h" \
2235 "$(OX)\manifest_.c":"$(OX)\manifest.h" \
2236 "$(OX)\markdown_.c":"$(OX)\markdown.h" \
2237 "$(OX)\markdown_html_.c":"$(OX)\markdown_html.h" \
2238 "$(OX)\match_.c":"$(OX)\match.h" \
2239 "$(OX)\md5_.c":"$(OX)\md5.h" \
2240 "$(OX)\merge_.c":"$(OX)\merge.h" \
2241 "$(OX)\merge3_.c":"$(OX)\merge3.h" \
2242 "$(OX)\moderate_.c":"$(OX)\moderate.h" \
2243 "$(OX)\name_.c":"$(OX)\name.h" \
2244
+2 -2
--- www/alerts.md
+++ www/alerts.md
@@ -311,11 +311,11 @@
311311
## Advanced Email Setups
312312
313313
Fossil offers several methods of sending email:
314314
315315
1. Pipe the email message text into a command.
316
- 2. Store email messages as entries in a SQLite database.
316
+ 2. Store email messages as entries in an SQLite database.
317317
3. Store email messages as individual files in a directory.
318318
4. Send emails to an SMTP relay.
319319
5. Send emails directly to the recipients via SMTP.
320320
321321
This wide range of options allows Fossil to talk to pretty much any
@@ -390,11 +390,11 @@
390390
currently uses this method rather than [the pipe method](#pipe) because
391391
it is running inside of a restrictive [chroot jail][cj] which is unable
392392
to hand off messages to the local MTA directly.
393393
394394
When you configure a Fossil server this way, it adds outgoing email
395
-messages to a SQLite database file. A separate daemon process can then
395
+messages to an SQLite database file. A separate daemon process can then
396396
extract those messages for further disposition.
397397
398398
Fossil includes a copy of [the daemon](/file/tools/email-sender.tcl)
399399
used on `fossil-scm.org`: it is just a short Tcl script that
400400
continuously monitors this database for new messages and hands any that
401401
--- www/alerts.md
+++ www/alerts.md
@@ -311,11 +311,11 @@
311 ## Advanced Email Setups
312
313 Fossil offers several methods of sending email:
314
315 1. Pipe the email message text into a command.
316 2. Store email messages as entries in a SQLite database.
317 3. Store email messages as individual files in a directory.
318 4. Send emails to an SMTP relay.
319 5. Send emails directly to the recipients via SMTP.
320
321 This wide range of options allows Fossil to talk to pretty much any
@@ -390,11 +390,11 @@
390 currently uses this method rather than [the pipe method](#pipe) because
391 it is running inside of a restrictive [chroot jail][cj] which is unable
392 to hand off messages to the local MTA directly.
393
394 When you configure a Fossil server this way, it adds outgoing email
395 messages to a SQLite database file. A separate daemon process can then
396 extract those messages for further disposition.
397
398 Fossil includes a copy of [the daemon](/file/tools/email-sender.tcl)
399 used on `fossil-scm.org`: it is just a short Tcl script that
400 continuously monitors this database for new messages and hands any that
401
--- www/alerts.md
+++ www/alerts.md
@@ -311,11 +311,11 @@
311 ## Advanced Email Setups
312
313 Fossil offers several methods of sending email:
314
315 1. Pipe the email message text into a command.
316 2. Store email messages as entries in an SQLite database.
317 3. Store email messages as individual files in a directory.
318 4. Send emails to an SMTP relay.
319 5. Send emails directly to the recipients via SMTP.
320
321 This wide range of options allows Fossil to talk to pretty much any
@@ -390,11 +390,11 @@
390 currently uses this method rather than [the pipe method](#pipe) because
391 it is running inside of a restrictive [chroot jail][cj] which is unable
392 to hand off messages to the local MTA directly.
393
394 When you configure a Fossil server this way, it adds outgoing email
395 messages to an SQLite database file. A separate daemon process can then
396 extract those messages for further disposition.
397
398 Fossil includes a copy of [the daemon](/file/tools/email-sender.tcl)
399 used on `fossil-scm.org`: it is just a short Tcl script that
400 continuously monitors this database for new messages and hands any that
401
--- www/caps/ref.html
+++ www/caps/ref.html
@@ -252,11 +252,11 @@
252252
Create new ticket report formats. Note that although this allows
253253
the user to provide SQL code to be run in the server’s context,
254254
and this capability is given to the untrusted “anonymous” user
255255
category by default, this is a safe capability to give to users
256256
because it is internally restricted to read-only queries on the
257
- tickets table only. (This restriction is done with a SQLite
257
+ tickets table only. (This restriction is done with an SQLite
258258
authorization hook, not by any method so weak as SQL text
259259
filtering.) Mnemonic: new <b>t</b>icket report.
260260
</td>
261261
</tr>
262262
263263
--- www/caps/ref.html
+++ www/caps/ref.html
@@ -252,11 +252,11 @@
252 Create new ticket report formats. Note that although this allows
253 the user to provide SQL code to be run in the server’s context,
254 and this capability is given to the untrusted “anonymous” user
255 category by default, this is a safe capability to give to users
256 because it is internally restricted to read-only queries on the
257 tickets table only. (This restriction is done with a SQLite
258 authorization hook, not by any method so weak as SQL text
259 filtering.) Mnemonic: new <b>t</b>icket report.
260 </td>
261 </tr>
262
263
--- www/caps/ref.html
+++ www/caps/ref.html
@@ -252,11 +252,11 @@
252 Create new ticket report formats. Note that although this allows
253 the user to provide SQL code to be run in the server’s context,
254 and this capability is given to the untrusted “anonymous” user
255 category by default, this is a safe capability to give to users
256 because it is internally restricted to read-only queries on the
257 tickets table only. (This restriction is done with an SQLite
258 authorization hook, not by any method so weak as SQL text
259 filtering.) Mnemonic: new <b>t</b>icket report.
260 </td>
261 </tr>
262
263
+51 -1
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,8 +1,55 @@
11
<title>Change Log</title>
22
3
-<h2 id='v2_25'>Changes for version 2.25 (pending)</h2>
3
+<h2 id='v2_26'>Changes for version 2.26 (pending)</h2>
4
+
5
+ * Enhanced the --from option on "[/help?cmd=diff|fossil diff]" so that
6
+ it optionally accepts a directory name as its argument, and uses files
7
+ under that directory as the baseline for the diff.
8
+ * Added the [/help?cmd=/ckout|/ckout web page] to provide information
9
+ about pending changes in a working check-out
10
+ * The [/help?cmd=ui|fossil ui] command defaults to using the
11
+ [/help?cmd=/ckout|/ckout page] as its start page. Or, if the
12
+ "--from PATH" option is present, the default start page becomes
13
+ "/ckout?exbase=PATH".
14
+ * Added the [/help?cmd=merge-info|fossil merge-info] command and especially
15
+ the --tk option to that command, to provide analysis of the most recent
16
+ merge or update operation.
17
+ * Issue a warning if a user tries to commit on a check-in where the
18
+ branch has been changed.
19
+ * When a merge conflict occurs, a new section is added to the conflict
20
+ text that shows Fossil's suggested resolution to the conflict.
21
+ * Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
22
+ diffs of multiple files.
23
+ * Enhancements to the [/help?cmd=/timeline|/timeline page]:
24
+ <ol type="a">
25
+ <li> Added the "ml=" ("Merge-in List") query parameter that works
26
+ like "rl=" ("Related List") but adds "mionly" style related
27
+ check-ins instead of the full "rel" style.
28
+ <li> For "tl=", "rl=", and "ml=", the order of the branches in the
29
+ graph now tries to match the order of the branches named in
30
+ the list.
31
+ <li> The "ms=" ("Match Style") query parameter is honored for
32
+ "tl=", "rl=", and "ml=".
33
+ <li> New query parameter "sl=BRANCHLIST" ("Sort List") strives to
34
+ put branches in the specified order in the graph. This
35
+ overrides any "tl=" or similar ordering.
36
+ <li> In the various "from=","to=" query formats, if the one of the
37
+ end points is an ancestor of the other, then the "rel" modifier
38
+ omits check-ins that are not ancestors of the newer endpoint.
39
+ <li> For "tl=" and similar query parameters, if the pattern contains
40
+ GLOB characters, then the matching style ("ms=") is set to GLOB
41
+ automatically and the "ms=" query parameter can be omitted.
42
+ </ol>
43
+ * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
44
+ and debugging
45
+ * Fix a bug in [/help?cmd=patch|fossil patch create] that causes
46
+ [/help?cmd=revert|fossil revert] operations that happened on individual
47
+ files after a [/help?cmd=merge|fossil merge] to be omitted from the
48
+ patch.
49
+
50
+<h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
451
552
* The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
653
that have non-ASCII filenames
754
* Add the [/help?cmd=tree|fossil tree] command.
855
* On case-insensitive filesystems, store files using the filesystem's
@@ -11,17 +58,20 @@
1158
which is more familiar to Git users. Retain the legacy name for
1259
compatibility.
1360
* Add new query parameters to the [/help?cmd=/timeline|/timeline page]:
1461
d2=, p2=, and dp2=.
1562
* Add options to the [/help?cmd=tag|fossil tag] command that will list tag values.
63
+ * Add the -b|--brief option to the [/help?cmd=status|fossil status] command.
1664
* Add ability to upload unversioned files via the [/help?cmd=/uvlist|/uvlist page].
1765
* Add history search to the [/help?cmd=/chat|/chat page].
1866
* Add Unix socket support to the [/help?cmd=server|server command].
1967
* On Windows, use the root certificates managed by the operating system
2068
(requires OpenSSL 3.2.0 or greater).
2169
* Take into account zero-width and double-width unicode characters when
2270
formatting the command-line timeline.
71
+ * Update the built-in SQLite to version 3.47.0. Precompiled binaries are
72
+ linked against OpenSSL 3.4.0.
2373
* Numerous minor fixes and additions.
2474
2575
2676
<h2 id='v2_24'>Changes for version 2.24 (2024-04-23)</h2>
2777
2878
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,8 +1,55 @@
1 <title>Change Log</title>
2
3 <h2 id='v2_25'>Changes for version 2.25 (pending)</h2>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
5 * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
6 that have non-ASCII filenames
7 * Add the [/help?cmd=tree|fossil tree] command.
8 * On case-insensitive filesystems, store files using the filesystem's
@@ -11,17 +58,20 @@
11 which is more familiar to Git users. Retain the legacy name for
12 compatibility.
13 * Add new query parameters to the [/help?cmd=/timeline|/timeline page]:
14 d2=, p2=, and dp2=.
15 * Add options to the [/help?cmd=tag|fossil tag] command that will list tag values.
 
16 * Add ability to upload unversioned files via the [/help?cmd=/uvlist|/uvlist page].
17 * Add history search to the [/help?cmd=/chat|/chat page].
18 * Add Unix socket support to the [/help?cmd=server|server command].
19 * On Windows, use the root certificates managed by the operating system
20 (requires OpenSSL 3.2.0 or greater).
21 * Take into account zero-width and double-width unicode characters when
22 formatting the command-line timeline.
 
 
23 * Numerous minor fixes and additions.
24
25
26 <h2 id='v2_24'>Changes for version 2.24 (2024-04-23)</h2>
27
28
--- www/changes.wiki
+++ www/changes.wiki
@@ -1,8 +1,55 @@
1 <title>Change Log</title>
2
3 <h2 id='v2_26'>Changes for version 2.26 (pending)</h2>
4
5 * Enhanced the --from option on "[/help?cmd=diff|fossil diff]" so that
6 it optionally accepts a directory name as its argument, and uses files
7 under that directory as the baseline for the diff.
8 * Added the [/help?cmd=/ckout|/ckout web page] to provide information
9 about pending changes in a working check-out
10 * The [/help?cmd=ui|fossil ui] command defaults to using the
11 [/help?cmd=/ckout|/ckout page] as its start page. Or, if the
12 "--from PATH" option is present, the default start page becomes
13 "/ckout?exbase=PATH".
14 * Added the [/help?cmd=merge-info|fossil merge-info] command and especially
15 the --tk option to that command, to provide analysis of the most recent
16 merge or update operation.
17 * Issue a warning if a user tries to commit on a check-in where the
18 branch has been changed.
19 * When a merge conflict occurs, a new section is added to the conflict
20 text that shows Fossil's suggested resolution to the conflict.
21 * Add the "Hide diffs/Show diffs" toggle to web-UI diff pages that show
22 diffs of multiple files.
23 * Enhancements to the [/help?cmd=/timeline|/timeline page]:
24 <ol type="a">
25 <li> Added the "ml=" ("Merge-in List") query parameter that works
26 like "rl=" ("Related List") but adds "mionly" style related
27 check-ins instead of the full "rel" style.
28 <li> For "tl=", "rl=", and "ml=", the order of the branches in the
29 graph now tries to match the order of the branches named in
30 the list.
31 <li> The "ms=" ("Match Style") query parameter is honored for
32 "tl=", "rl=", and "ml=".
33 <li> New query parameter "sl=BRANCHLIST" ("Sort List") strives to
34 put branches in the specified order in the graph. This
35 overrides any "tl=" or similar ordering.
36 <li> In the various "from=","to=" query formats, if the one of the
37 end points is an ancestor of the other, then the "rel" modifier
38 omits check-ins that are not ancestors of the newer endpoint.
39 <li> For "tl=" and similar query parameters, if the pattern contains
40 GLOB characters, then the matching style ("ms=") is set to GLOB
41 automatically and the "ms=" query parameter can be omitted.
42 </ol>
43 * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
44 and debugging
45 * Fix a bug in [/help?cmd=patch|fossil patch create] that causes
46 [/help?cmd=revert|fossil revert] operations that happened on individual
47 files after a [/help?cmd=merge|fossil merge] to be omitted from the
48 patch.
49
50 <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
51
52 * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
53 that have non-ASCII filenames
54 * Add the [/help?cmd=tree|fossil tree] command.
55 * On case-insensitive filesystems, store files using the filesystem's
@@ -11,17 +58,20 @@
58 which is more familiar to Git users. Retain the legacy name for
59 compatibility.
60 * Add new query parameters to the [/help?cmd=/timeline|/timeline page]:
61 d2=, p2=, and dp2=.
62 * Add options to the [/help?cmd=tag|fossil tag] command that will list tag values.
63 * Add the -b|--brief option to the [/help?cmd=status|fossil status] command.
64 * Add ability to upload unversioned files via the [/help?cmd=/uvlist|/uvlist page].
65 * Add history search to the [/help?cmd=/chat|/chat page].
66 * Add Unix socket support to the [/help?cmd=server|server command].
67 * On Windows, use the root certificates managed by the operating system
68 (requires OpenSSL 3.2.0 or greater).
69 * Take into account zero-width and double-width unicode characters when
70 formatting the command-line timeline.
71 * Update the built-in SQLite to version 3.47.0. Precompiled binaries are
72 linked against OpenSSL 3.4.0.
73 * Numerous minor fixes and additions.
74
75
76 <h2 id='v2_24'>Changes for version 2.24 (2024-04-23)</h2>
77
78
--- www/fossil-is-not-relational.md
+++ www/fossil-is-not-relational.md
@@ -131,11 +131,11 @@
131131
metadata.
132132
133133
- Raw file content of versioned files. These data are external to
134134
artifacts, which refer to them by their hashes. How they are stored
135135
is not the concern of the data model, but (spoiler alert!) Fossil
136
- stores them in an sqlite database, one record per distinct hash, in
136
+ stores them in an SQLite database, one record per distinct hash, in
137137
its `blob` table (which we will cover more very soon).
138138
139139
Non-SCM-relevant state includes:
140140
141141
- Fossil's list of users and their metadata (permissions, email
142142
--- www/fossil-is-not-relational.md
+++ www/fossil-is-not-relational.md
@@ -131,11 +131,11 @@
131 metadata.
132
133 - Raw file content of versioned files. These data are external to
134 artifacts, which refer to them by their hashes. How they are stored
135 is not the concern of the data model, but (spoiler alert!) Fossil
136 stores them in an sqlite database, one record per distinct hash, in
137 its `blob` table (which we will cover more very soon).
138
139 Non-SCM-relevant state includes:
140
141 - Fossil's list of users and their metadata (permissions, email
142
--- www/fossil-is-not-relational.md
+++ www/fossil-is-not-relational.md
@@ -131,11 +131,11 @@
131 metadata.
132
133 - Raw file content of versioned files. These data are external to
134 artifacts, which refer to them by their hashes. How they are stored
135 is not the concern of the data model, but (spoiler alert!) Fossil
136 stores them in an SQLite database, one record per distinct hash, in
137 its `blob` table (which we will cover more very soon).
138
139 Non-SCM-relevant state includes:
140
141 - Fossil's list of users and their metadata (permissions, email
142
--- www/fossil-v-git.wiki
+++ www/fossil-v-git.wiki
@@ -596,11 +596,11 @@
596596
597597
Because Git commingles the repository data with the initial checkout of
598598
that repository, the default mode of operation in Git is to stick to that
599599
single work/repo tree, even when that's a shortsighted way of working.
600600
601
-Fossil doesn't work that way. A Fossil repository is a SQLite database
601
+Fossil doesn't work that way. A Fossil repository is an SQLite database
602602
file which is normally stored outside the working checkout directory. You can
603603
[/help?cmd=open | open] a Fossil repository any number of times into
604604
any number of working directories. A common usage pattern is to have one
605605
working directory per active working branch, so that switching branches
606606
is done with a <tt>cd</tt> command rather than by checking out the
607607
--- www/fossil-v-git.wiki
+++ www/fossil-v-git.wiki
@@ -596,11 +596,11 @@
596
597 Because Git commingles the repository data with the initial checkout of
598 that repository, the default mode of operation in Git is to stick to that
599 single work/repo tree, even when that's a shortsighted way of working.
600
601 Fossil doesn't work that way. A Fossil repository is a SQLite database
602 file which is normally stored outside the working checkout directory. You can
603 [/help?cmd=open | open] a Fossil repository any number of times into
604 any number of working directories. A common usage pattern is to have one
605 working directory per active working branch, so that switching branches
606 is done with a <tt>cd</tt> command rather than by checking out the
607
--- www/fossil-v-git.wiki
+++ www/fossil-v-git.wiki
@@ -596,11 +596,11 @@
596
597 Because Git commingles the repository data with the initial checkout of
598 that repository, the default mode of operation in Git is to stick to that
599 single work/repo tree, even when that's a shortsighted way of working.
600
601 Fossil doesn't work that way. A Fossil repository is an SQLite database
602 file which is normally stored outside the working checkout directory. You can
603 [/help?cmd=open | open] a Fossil repository any number of times into
604 any number of working directories. A common usage pattern is to have one
605 working directory per active working branch, so that switching branches
606 is done with a <tt>cd</tt> command rather than by checking out the
607
+2 -2
--- www/gitusers.md
+++ www/gitusers.md
@@ -56,11 +56,11 @@
5656
5757
5858
5959
#### <a id="cwork" name="scw"></a> Checkout Workflows
6060
61
-A Fossil repository is a SQLite database storing the entire history of a
61
+A Fossil repository is an SQLite database storing the entire history of a
6262
project. It is not normally stored inside the working tree.
6363
A Fossil working tree — [also called a check-out](./glossary.md#check-out) — is a directory
6464
that contains a snapshot of your project that you are currently working
6565
on, extracted for you from the repository database file by the `fossil`
6666
program.
@@ -148,11 +148,11 @@
148148
option, it won’t let you close the check-out with uncommitted changes to
149149
those managed files.
150150
151151
The `close` command also refuses to run without `--force` when you have
152152
certain other precious per-checkout data that Fossil stores in the
153
-`.fslckout` file at the root of a check-out directory. This is a SQLite
153
+`.fslckout` file at the root of a check-out directory. This is an SQLite
154154
database that keeps track of local state such as what version you have
155155
checked out, the contents of the [stash] for that working directory, the
156156
[undo] buffers, per-checkout [settings][set], and so forth. The stash
157157
and undo buffers are considered precious uncommitted changes,
158158
so you have to force Fossil to discard these as part of closing the
159159
--- www/gitusers.md
+++ www/gitusers.md
@@ -56,11 +56,11 @@
56
57
58
59 #### <a id="cwork" name="scw"></a> Checkout Workflows
60
61 A Fossil repository is a SQLite database storing the entire history of a
62 project. It is not normally stored inside the working tree.
63 A Fossil working tree — [also called a check-out](./glossary.md#check-out) — is a directory
64 that contains a snapshot of your project that you are currently working
65 on, extracted for you from the repository database file by the `fossil`
66 program.
@@ -148,11 +148,11 @@
148 option, it won’t let you close the check-out with uncommitted changes to
149 those managed files.
150
151 The `close` command also refuses to run without `--force` when you have
152 certain other precious per-checkout data that Fossil stores in the
153 `.fslckout` file at the root of a check-out directory. This is a SQLite
154 database that keeps track of local state such as what version you have
155 checked out, the contents of the [stash] for that working directory, the
156 [undo] buffers, per-checkout [settings][set], and so forth. The stash
157 and undo buffers are considered precious uncommitted changes,
158 so you have to force Fossil to discard these as part of closing the
159
--- www/gitusers.md
+++ www/gitusers.md
@@ -56,11 +56,11 @@
56
57
58
59 #### <a id="cwork" name="scw"></a> Checkout Workflows
60
61 A Fossil repository is an SQLite database storing the entire history of a
62 project. It is not normally stored inside the working tree.
63 A Fossil working tree — [also called a check-out](./glossary.md#check-out) — is a directory
64 that contains a snapshot of your project that you are currently working
65 on, extracted for you from the repository database file by the `fossil`
66 program.
@@ -148,11 +148,11 @@
148 option, it won’t let you close the check-out with uncommitted changes to
149 those managed files.
150
151 The `close` command also refuses to run without `--force` when you have
152 certain other precious per-checkout data that Fossil stores in the
153 `.fslckout` file at the root of a check-out directory. This is an SQLite
154 database that keeps track of local state such as what version you have
155 checked out, the contents of the [stash] for that working directory, the
156 [undo] buffers, per-checkout [settings][set], and so forth. The stash
157 and undo buffers are considered precious uncommitted changes,
158 so you have to force Fossil to discard these as part of closing the
159
+1 -1
--- www/glossary.md
+++ www/glossary.md
@@ -348,11 +348,11 @@
348348
349349
* In the same way that one cannot extract files from a zip archive
350350
without having a copy of that zip file, one cannot make check-outs
351351
without access to the repository file or a clone thereof.
352352
353
-* Because a Fossil repository is a SQLite database file, the same
353
+* Because a Fossil repository is an SQLite database file, the same
354354
rules for avoiding data corruption apply to it. In particular, it is
355355
[nearly a hard requirement][h2cflp] that the repository clone be on
356356
the same machine as the one where you make check-outs and the
357357
subsequent check-ins.
358358
359359
--- www/glossary.md
+++ www/glossary.md
@@ -348,11 +348,11 @@
348
349 * In the same way that one cannot extract files from a zip archive
350 without having a copy of that zip file, one cannot make check-outs
351 without access to the repository file or a clone thereof.
352
353 * Because a Fossil repository is a SQLite database file, the same
354 rules for avoiding data corruption apply to it. In particular, it is
355 [nearly a hard requirement][h2cflp] that the repository clone be on
356 the same machine as the one where you make check-outs and the
357 subsequent check-ins.
358
359
--- www/glossary.md
+++ www/glossary.md
@@ -348,11 +348,11 @@
348
349 * In the same way that one cannot extract files from a zip archive
350 without having a copy of that zip file, one cannot make check-outs
351 without access to the repository file or a clone thereof.
352
353 * Because a Fossil repository is an SQLite database file, the same
354 rules for avoiding data corruption apply to it. In particular, it is
355 [nearly a hard requirement][h2cflp] that the repository clone be on
356 the same machine as the one where you make check-outs and the
357 subsequent check-ins.
358
359
+4 -4
--- www/index.wiki
+++ www/index.wiki
@@ -84,16 +84,16 @@
8484
the repository are consistent prior to each commit.
8585
8686
8. <b>Free and Open-Source</b> — [../COPYRIGHT-BSD2.txt|2-clause BSD license].
8787
8888
<hr>
89
-<h3>Latest Release: 2.24 ([/timeline?c=version-2.24|2024-04-23])</h3>
89
+<h3>Latest Release: 2.25 ([/timeline?c=version-2.25|2024-11-06])</h3>
9090
9191
* [/uv/download.html|Download]
92
- * [./changes.wiki#v2_24|Change Summary]
93
- * [/timeline?p=version-2.24&bt=version-2.23&y=ci|Check-ins in version 2.24]
94
- * [/timeline?df=version-2.24&y=ci|Check-ins derived from the 2.24 release]
92
+ * [./changes.wiki#v2_25|Change Summary]
93
+ * [/timeline?p=version-2.25&bt=version-2.24&y=ci|Check-ins in version 2.25]
94
+ * [/timeline?df=version-2.25&y=ci|Check-ins derived from the 2.25 release]
9595
* [/timeline?t=release|Timeline of all past releases]
9696
9797
<hr>
9898
<h3>Quick Start</h3>
9999
100100
--- www/index.wiki
+++ www/index.wiki
@@ -84,16 +84,16 @@
84 the repository are consistent prior to each commit.
85
86 8. <b>Free and Open-Source</b> — [../COPYRIGHT-BSD2.txt|2-clause BSD license].
87
88 <hr>
89 <h3>Latest Release: 2.24 ([/timeline?c=version-2.24|2024-04-23])</h3>
90
91 * [/uv/download.html|Download]
92 * [./changes.wiki#v2_24|Change Summary]
93 * [/timeline?p=version-2.24&bt=version-2.23&y=ci|Check-ins in version 2.24]
94 * [/timeline?df=version-2.24&y=ci|Check-ins derived from the 2.24 release]
95 * [/timeline?t=release|Timeline of all past releases]
96
97 <hr>
98 <h3>Quick Start</h3>
99
100
--- www/index.wiki
+++ www/index.wiki
@@ -84,16 +84,16 @@
84 the repository are consistent prior to each commit.
85
86 8. <b>Free and Open-Source</b> — [../COPYRIGHT-BSD2.txt|2-clause BSD license].
87
88 <hr>
89 <h3>Latest Release: 2.25 ([/timeline?c=version-2.25|2024-11-06])</h3>
90
91 * [/uv/download.html|Download]
92 * [./changes.wiki#v2_25|Change Summary]
93 * [/timeline?p=version-2.25&bt=version-2.24&y=ci|Check-ins in version 2.25]
94 * [/timeline?df=version-2.25&y=ci|Check-ins derived from the 2.25 release]
95 * [/timeline?t=release|Timeline of all past releases]
96
97 <hr>
98 <h3>Quick Start</h3>
99
100
+1 -1
--- www/qandc.wiki
+++ www/qandc.wiki
@@ -142,11 +142,11 @@
142142
fossil supports disconnected operation.
143143
144144
As for bloat: Fossil is a single self-contained executable.
145145
You do not need any other packages
146146
(diff, patch, merge, cvs, svn, rcs, git, python, perl, tcl, apache,
147
-sqlite, and so forth)
147
+SQLite, and so forth)
148148
in order to run fossil. Fossil runs just fine in a chroot jail all
149149
by itself. And the self-contained fossil
150150
executable is much less than 1MB in size. (Update 2015-01-12: Fossil has
151151
grown in the years since the previous sentence was written but is still
152152
much less than 2MB according to "size" when compiled using -Os on x64 Linux.)
153153
--- www/qandc.wiki
+++ www/qandc.wiki
@@ -142,11 +142,11 @@
142 fossil supports disconnected operation.
143
144 As for bloat: Fossil is a single self-contained executable.
145 You do not need any other packages
146 (diff, patch, merge, cvs, svn, rcs, git, python, perl, tcl, apache,
147 sqlite, and so forth)
148 in order to run fossil. Fossil runs just fine in a chroot jail all
149 by itself. And the self-contained fossil
150 executable is much less than 1MB in size. (Update 2015-01-12: Fossil has
151 grown in the years since the previous sentence was written but is still
152 much less than 2MB according to "size" when compiled using -Os on x64 Linux.)
153
--- www/qandc.wiki
+++ www/qandc.wiki
@@ -142,11 +142,11 @@
142 fossil supports disconnected operation.
143
144 As for bloat: Fossil is a single self-contained executable.
145 You do not need any other packages
146 (diff, patch, merge, cvs, svn, rcs, git, python, perl, tcl, apache,
147 SQLite, and so forth)
148 in order to run fossil. Fossil runs just fine in a chroot jail all
149 by itself. And the self-contained fossil
150 executable is much less than 1MB in size. (Update 2015-01-12: Fossil has
151 grown in the years since the previous sentence was written but is still
152 much less than 2MB according to "size" when compiled using -Os on x64 Linux.)
153
+197 -88
--- www/ssl-server.md
+++ www/ssl-server.md
@@ -12,76 +12,107 @@
1212
1313
[0]: ./ssl.wiki
1414
[1]: /timeline?c=b05cb4a0e15d0712&y=ci&n=13
1515
1616
Beginning in [late December 2021](/timeline?c=f6263bb64195b07f&y=a&n=13),
17
-this has been fixed. Commands like
17
+Fossil servers are now able to converse directly over TLS. Commands like
1818
1919
* "[fossil server](/help?cmd=server)"
2020
* "[fossil ui](/help?cmd=ui)", and
2121
* "[fossil http](/help?cmd=http)"
2222
23
-now all handle server-mode SSL/TLS encryption natively. It is now possible
24
-to run a secure Fossil server without having to put Fossil behind an encrypting
25
-web server or reverse proxy. Hence, it is now possible to stand up a complete
26
-Fossil project website on an inexpensive VPS with no added software other than
27
-Fossil itself and something like [certbot](https://certbot.eff.org) for
28
-obtaining a CA-signed certificate.
29
-
30
-## Usage
23
+may now handle the encryption natively when suitably configured, without
24
+requiring a third-party proxy layer.
25
+
26
+## <a id="usage"></a>Usage
3127
3228
To put any of the Fossil server commands into SSL/TLS mode, simply
33
-add the "--cert" command-line option.
29
+add the "`--cert`" command-line option:
3430
3531
fossil ui --cert unsafe-builtin
3632
37
-The --cert option is what tells Fossil to use TLS encryption.
38
-Normally, the argument to --cert is the name of a file containing
39
-the certificate (the "fullchain.pem" file) for the website. In this
40
-example, the magic name "unsafe-builtin" is used, which causes Fossil
41
-to use a self-signed cert rather than a real cert obtained from a
42
-[Certificate Authority](https://en.wikipedia.org/wiki/Certificate_authority)
43
-or "CA". As the name implies, this self-signed cert is not secure and
44
-should only be used for testing. Your web-browser will complain bitterly
45
-and will refuse to display the pages using the "unsafe-builtin" cert.
46
-Firefox will allow you to click an "I know the risks" button and continue.
47
-Other web browsers will stubornly refuse to display the page, under the theory
48
-that weak encryption is worse than no encryption at all. Continue reading
49
-to see how to solve this.
50
-
51
-## About Certs
52
-
53
-Certs are based on public-key or asymmetric cryptography. To create a cert,
54
-you first create a new "key pair" consisting of a public key and a private key.
55
-The public key can be freely shared with the world, but you must keep the
56
-private key secret. If anyone gains access to your private key then he will be
57
-able to impersonate you and break into your system.
58
-
59
-To obtain a cert, you send your public key and the name of the domain you
60
-want to protect to a certificate authority. The CA then digitally signs
61
-the combination of those two things using their own private key and sends
62
-the signed combination back to you. The CA's digital signature of your
63
-public key and domain name is the cert.
64
-
65
-SSL/TLS servers need two things in order to prove their identity to clients:
66
-
67
- 1. The cert that was signed by a CA
68
- 2. The private key
69
-
70
-The SSL/TLS servers send the cert to each client, so that the client
71
-can verify it. But the private key is kept strictly private and is never
72
-shared with anyone.
73
-
74
-## How To Tell Fossil About Your Cert And Private Key
75
-
76
-If you do not have your own cert and private key, you can ask Fossil
33
+Here, we are passing the magic name "unsafe-builtin" to cause Fossil to
34
+use a [hard-coded self-signed cert][hcssc] rather than one obtained from
35
+a recognized [Certificate Authority][CA], or "CA".
36
+
37
+As the name implies, this self-signed cert is _not secure_ and should
38
+only be used for testing. Your web browser is likely to complain
39
+bitterly about it and will refuse to display the pages using the
40
+"unsafe-builtin" cert until you placate it. The complexity of the
41
+ceremony demanded depends on how paranoid your browser’s creators have
42
+decided to be. It may require as little as clicking a single big "I know
43
+the risks" type of button, or it may require a sequence be several
44
+clicks designed to discourage the “yes, yes, just let me do the thing”
45
+crowd lest they run themselves into trouble by disregarding well-meant
46
+warnings.
47
+
48
+Our purpose here is to show you an alternate path that will avoid the
49
+issue entirely, not weigh in on which browser handles self-signed
50
+certificates best.
51
+
52
+[CA]: https://en.wikipedia.org/wiki/Certificate_authority
53
+[hcssc]: /info/c2a7b14c3f541edb96?ln=89-116
54
+
55
+## <a id="about"></a>About Certs
56
+
57
+The X.509 certificate system used by browsers to secure TLS connections
58
+is based on asymmetric public-key cryptography. The methods for
59
+obtaining one vary widely, with a resulting tradeoff we may summarize as
60
+trustworthiness versus convenience, the latter characteristic falling as
61
+the former rises.(^No strict correlation exists. CAs have invented
62
+highly inconvenient certification schemes that offer little additional
63
+real-world trustworthiness. Extreme cases along this axis may be fairly
64
+characterized as [security theater][st]. We focus in this document on
65
+well-balanced trade-offs between decreasing convenience and useful
66
+levels of trustworthiness gained thereby.)
67
+
68
+The self-signed method demonstrated above offers approximately zero
69
+trustworthiness, though not zero _value_ since it does still provide
70
+connection encryption.
71
+
72
+More trustworthy methods are necessarily less convenient. One such is to
73
+send your public key and the name of the domain you want to protect to a
74
+recognized CA, which then performs one or more tests to convince itself
75
+that the requester is in control of that domain. If the CA’s tests all
76
+pass, it produces an X.509 certificate bound to that domain, which
77
+includes assorted other information under the CA’s digital signature
78
+attesting to the validity of the document’s contents. The result is sent
79
+back to the requester, which may then use it to transitively attest to
80
+these tests’ success: presuming one cannot fake the type of signature
81
+used, the document must have been signed by the trusted, recognized CA.
82
+
83
+There is one element of the assorted information included with a
84
+certificate that is neither supplied by the requester nor rubber-stamped
85
+on it in passing by the CA. It also generates a one-time key pair and
86
+stores the public half in the certificate. The cryptosystem this keypair
87
+is intended to work with varies both by the CA and by time, as older
88
+systems become obsolete. Details aside, the CA then puts this matching
89
+private half of the key in a separate file, often encrypted under a
90
+separate cryptosystem for security.
91
+
92
+SSL/TLS servers need both resulting halves to make these attestations,
93
+but they send only the public half to the client when establishing the
94
+connection. The client then makes its own checks to determine whether it
95
+trusts the attestations being made.
96
+
97
+A properly written and administered server never releases the private
98
+key to anyone. Ideally, it goes directly from the CA to the requesting
99
+server and never moves from there; then when it expires, the server
100
+deletes it permanently.
101
+
102
+[st]: https://en.wikipedia.org/wiki/Security_theater
103
+
104
+## <a id="startup"></a>How To Tell Fossil About Your Cert And Private Key
105
+
106
+As we saw [above](#usage),
107
+if you do not have your own cert and private key, you can ask Fossil
77108
to use "unsafe-builtin", which is a self-signed cert that is built into
78
-Fossil. This is wildly insecure, since the private key is not really private -
79
-it is [in plain sight](/info/c2a7b14c3f541edb96?ln=89-116) in the Fossil
109
+Fossil. This is wildly insecure, since the private key is not really private;
110
+it is [in plain sight][hcssc] in the Fossil
80111
source tree for anybody to read. <b>Never add the private key that is
81112
built into Fossil to your OS's trust store</b> as doing so will severely
82
-compromise your computer. The built-in cert is only useful for testing.
113
+compromise your computer.[^ssattack] This built-in cert is only useful for testing.
83114
If you want actual security, you will need to come up with your own private
84115
key and cert.
85116
86117
Fossil wants to read certs and public keys in the
87118
[PEM format](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail).
@@ -98,66 +129,142 @@
98129
*base-64 encoding of the certificate*
99130
-----END CERTIFICATE-----
100131
101132
In both formats, text outside of the delimiters is ignored. That means
102133
that if you have a PEM-formatted private key and a separate PEM-formatted
103
-certificate, you can concatenate the two into a single file and the
134
+certificate, you can concatenate the two into a single file, and the
104135
individual components will still be easily accessible.
105136
106
-If you have a single file that holds both your private key and your
137
+### <a id="cat"></a>Separate or Concatenated?
138
+
139
+Given a single concatenated file that holds both your private key and your
107140
cert, you can hand it off to the "[fossil server](/help?cmd=server)"
108
-command using the --cert option. Like this:
141
+command using the `--cert` option, like this:
109142
110143
fossil server --port 443 --cert mycert.pem /home/www/myproject.fossil
111144
112145
The command above is sufficient to run a fully-encrypted web site for
113146
the "myproject.fossil" Fossil repository. This command must be run as
114147
root, since it wants to listen on TCP port 443, and only root processes are
115148
allowed to do that. This is safe, however, since before reading any
116
-information off of the wire, Fossil will put itself inside a chroot jail
117
-at /home/www and drop all root privileges.
118
-
119
-### Keeping The Cert And Private Key In Separate Files
120
-
121
-If you do not want to combine your cert and private key into a single
122
-big PEM file, you can keep them separate using the --pkey option to
123
-Fossil.
149
+information off of the wire, Fossil will [put itself inside a chroot
150
+jail](./chroot.md) at `/home/www` and drop all root privileges.
151
+
152
+This method of combining your cert and private key into a single big PEM
153
+file carries risks, one of which is that the system administrator must
154
+make both halves readable by the user running the Fossil server. Given
155
+the chroot jail feature, a more secure scheme separates the halves so
156
+that only root can read the private half, which then means that when
157
+Fossil drops its root privileges, it becomes unable to access the
158
+private key on disk. Fossil’s `server` feature includes the `--pkey`
159
+option to allow for that use case:
124160
125161
fossil server --port 443 --cert fullchain.pem --pkey privkey.pem /home/www/myproject.fossil
126162
127
-## The ACME Protocol
163
+[^ssattack]: ^How, you ask? Because the keys are known, they can be used
164
+ to provide signed certificates for **any** other domain. One foolish
165
+ enough to tell their OS’s TLS mechanisms to trust the signing
166
+ certificate is implicitly handing over all TLS encryption controls
167
+ to any attacker that knows they did this. Don’t do it.
168
+
169
+### <a id="chain"></a>Chains and Links
170
+
171
+The file name “`fullchain.pem`” used above is a reference to a term of
172
+art within this world of TLS protocols and their associated X.509
173
+certificates. Within the simplistic scheme originally envisioned by the
174
+creators of SSL — the predecessor to TLS — we were all expected to agree
175
+on a single set of CA root authorities, and we would all agree to get
176
+our certificates from one of them. The real world is more complicated:
177
+
178
+* The closest we have to universal acceptance of CAs is via the
179
+ [CA/Browser Forum][CAB], and even within its select membership there
180
+ is continual argument over which roots are trustworthy. (Hashing
181
+ that out is arguably this group’s key purpose.)
182
+
183
+* CAB’s decision regarding trustworthiness may not match that of any
184
+ given system’s administrator. There are solid, defensible reasons to
185
+ prune back the stock CA root set included with your browser, then to
186
+ augment it with ones CAB _doesn’t_ trust.
187
+
188
+* TLS isn’t limited to use between web browsers and public Internet
189
+ sites. Several common use cases preclude use of the process CAB
190
+ envisions, with servers able to contact Internet-based CA roots as
191
+ part of proving their identity. Different use cases demand different
192
+ CA root authority stores.
193
+
194
+ The most common of these divergent cases are servers behind strict
195
+ firewalls and edge devices that never interact with the public
196
+ Internet. This class ranges from cheap home IoT devices to the
197
+ internal equipment managed by IT for a massive global corporation.
198
+
199
+Your private Fossil server is liable to fall into that last category.
200
+This may then require that you generate a more complicated “chain” of
201
+certificates for Fossil to use here, without which the client may not be
202
+able to get back to a CA root it trusts. This is true regardless of
203
+whether that client is another copy of Fossil or a web browser
204
+traversing Fossil’s web UI, though that fact complicates matters by
205
+allowing for multiple classes of client, each of which may have their
206
+own rules for modifying the stock certificate scheme.
207
+
208
+This is distressingly common, in fact: Fossil links to OpenSSL to
209
+provide its TLS support, but there is a good chance that your browser
210
+uses another TLS implementation entirely. They may or may not agree on a
211
+single CA root store.
212
+
213
+How you accommodate all this complexity varies by the CA and other
214
+details. As but one example, Firefox’s “View Certificate” feature offers
215
+_two_ ways to download a given web site’s certificate: the cert alone or
216
+the “chain” leading back to the root. Depending on the use case, the
217
+standalone certificate might suffice, or you might need some type of
218
+cert chain. Complicating this is that the last link in the chain may be
219
+left off when it is for a mutually trusted CA root, implicitly
220
+completing the chain.
221
+
222
+[CAB]: https://en.wikipedia.org/wiki/CA/Browser_Forum
223
+
224
+## <a id="acme"></a>The ACME Protocol
225
+
226
+The [ACME Protocol][2] simplifies all this by automating the process of
227
+proving to a recognized public CA that you are in control of a given
228
+website. Without this proof, no valid CA will issue a cert for that
229
+domain, as that allows fraudulent impersonation.
128230
129
-The [ACME Protocol][2] is used to prove to a CA that you control a
130
-website. CAs require proof that you control a domain before they
131
-will issue a cert for that domain. The usual means of dealing
132
-with ACME is to run the separate [certbot](https://certbot.eff.org) tool.
231
+The primary implementation of ACME is [certbot], a product of the Let’s
232
+Encrypt organization.
133233
Here is, in a nutshell, what certbot will do to obtain your cert:
134234
135
- 1. Certbot sends your "signing request" (the document that contains
235
+ 1. It sends your "signing request" (the document that contains
136236
your public key and your domain name) to the CA.
137237
138
- 2. After receiving the signing request, the CA needs to verify that you
139
- control the domain of the cert. To do this (or, one common
140
- way of doing this, at least) the CA sends a secret token back to
141
- certbot through a secure backchannel, and instructs certbot to make
142
- that token accessible on the (unencrypted, ordinary "http:") web site
143
- for the domain in a particular file under the ".well-known" subdirectory.
144
-
145
- 3. Certbot puts the token where the CA requested it, then notifies the
146
- CA that it is there.
147
-
148
- 4. The CA accesses the token to confirm that you do indeed control the
149
- website. It then creates the cert and sends it back to certbot.
150
-
151
- 5. Certbot stores your cert and deletes the ".well-known" token.
238
+ 2. After receiving the signing request, the CA needs to verify that
239
+ you control the domain of the cert. One of several methods certbot has
240
+ for accomplishing this is to create a secret token and place it at
241
+ a well-known location, then tell the CA about it over ACME.
242
+
243
+ 3. The CA then tries pulling that token, which if successful proves
244
+ that the requester is able to create arbitrary data on the server,
245
+ implicitly proving control over that server. This must be done
246
+ over the unencrypted HTTP protocol since TLS isn’t working yet.
247
+
248
+ 4. If satisfied by this proof of control, the CA then creates the
249
+ keypair described above and bakes the public half into the
250
+ certificate it signs. It then sends this and the private half of
251
+ the key back to certbot.
252
+
253
+ 5. Certbot stores these halves separately for the reasons sketched
254
+ out above.
255
+
256
+ 6. It then deletes the secret one-time-use token it used to prove
257
+ domain control. ACME’s design precludes replay attacks.
152258
153259
In order for all of this to happen, certbot needs to be able to create
154
-a subdirectory named ".well-known", within a directory you specify, and
260
+a subdirectory named ".well-known", within a directory you specify,
155261
then populate that subdirectory with a token file of some kind. To support
156262
this, the "[fossil server](/help?cmd=server)" and
157263
"[fossil http](/help?cmd=http)" commands have the --acme option.
158
-When the --acme option is specified and Fossil sees a URL where the path
264
+
265
+When specified, Fossil sees a URL where the path
159266
begins with ".well-known", then instead of doing its normal processing, it
160267
looks for a file with that pathname and returns it to the client. If
161268
the "server" or "http" command is referencing a single Fossil repository,
162269
then the ".well-known" sub-directory should be in the same directory as
163270
the repository file. If the "server" or "http" command are run against
@@ -172,9 +279,11 @@
172279
Then you create your public/private key pair and run certbot, giving it
173280
a --webroot of /home/www. Certbot will create the sub-directory
174281
named "/home/www/.well-known" and put token files there, which the CA
175282
will verify. Then certbot will store your new cert in a particular file.
176283
177
-Once certbot has obtained your cert, then you can concatenate that
178
-cert with your private key and run Fossil in SSL/TLS mode as shown above.
284
+Once certbot has obtained your cert, you may either pass the two halves
285
+to Fossil separately using the `--pkey` and `--cert` options described
286
+above, or you may concatenate them and pass that via `--cert` alone.
179287
180288
[2]: https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment
289
+[certbot]: https://certbot.eff.org
181290
--- www/ssl-server.md
+++ www/ssl-server.md
@@ -12,76 +12,107 @@
12
13 [0]: ./ssl.wiki
14 [1]: /timeline?c=b05cb4a0e15d0712&y=ci&n=13
15
16 Beginning in [late December 2021](/timeline?c=f6263bb64195b07f&y=a&n=13),
17 this has been fixed. Commands like
18
19 * "[fossil server](/help?cmd=server)"
20 * "[fossil ui](/help?cmd=ui)", and
21 * "[fossil http](/help?cmd=http)"
22
23 now all handle server-mode SSL/TLS encryption natively. It is now possible
24 to run a secure Fossil server without having to put Fossil behind an encrypting
25 web server or reverse proxy. Hence, it is now possible to stand up a complete
26 Fossil project website on an inexpensive VPS with no added software other than
27 Fossil itself and something like [certbot](https://certbot.eff.org) for
28 obtaining a CA-signed certificate.
29
30 ## Usage
31
32 To put any of the Fossil server commands into SSL/TLS mode, simply
33 add the "--cert" command-line option.
34
35 fossil ui --cert unsafe-builtin
36
37 The --cert option is what tells Fossil to use TLS encryption.
38 Normally, the argument to --cert is the name of a file containing
39 the certificate (the "fullchain.pem" file) for the website. In this
40 example, the magic name "unsafe-builtin" is used, which causes Fossil
41 to use a self-signed cert rather than a real cert obtained from a
42 [Certificate Authority](https://en.wikipedia.org/wiki/Certificate_authority)
43 or "CA". As the name implies, this self-signed cert is not secure and
44 should only be used for testing. Your web-browser will complain bitterly
45 and will refuse to display the pages using the "unsafe-builtin" cert.
46 Firefox will allow you to click an "I know the risks" button and continue.
47 Other web browsers will stubornly refuse to display the page, under the theory
48 that weak encryption is worse than no encryption at all. Continue reading
49 to see how to solve this.
50
51 ## About Certs
52
53 Certs are based on public-key or asymmetric cryptography. To create a cert,
54 you first create a new "key pair" consisting of a public key and a private key.
55 The public key can be freely shared with the world, but you must keep the
56 private key secret. If anyone gains access to your private key then he will be
57 able to impersonate you and break into your system.
58
59 To obtain a cert, you send your public key and the name of the domain you
60 want to protect to a certificate authority. The CA then digitally signs
61 the combination of those two things using their own private key and sends
62 the signed combination back to you. The CA's digital signature of your
63 public key and domain name is the cert.
64
65 SSL/TLS servers need two things in order to prove their identity to clients:
66
67 1. The cert that was signed by a CA
68 2. The private key
69
70 The SSL/TLS servers send the cert to each client, so that the client
71 can verify it. But the private key is kept strictly private and is never
72 shared with anyone.
73
74 ## How To Tell Fossil About Your Cert And Private Key
75
76 If you do not have your own cert and private key, you can ask Fossil
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77 to use "unsafe-builtin", which is a self-signed cert that is built into
78 Fossil. This is wildly insecure, since the private key is not really private -
79 it is [in plain sight](/info/c2a7b14c3f541edb96?ln=89-116) in the Fossil
80 source tree for anybody to read. <b>Never add the private key that is
81 built into Fossil to your OS's trust store</b> as doing so will severely
82 compromise your computer. The built-in cert is only useful for testing.
83 If you want actual security, you will need to come up with your own private
84 key and cert.
85
86 Fossil wants to read certs and public keys in the
87 [PEM format](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail).
@@ -98,66 +129,142 @@
98 *base-64 encoding of the certificate*
99 -----END CERTIFICATE-----
100
101 In both formats, text outside of the delimiters is ignored. That means
102 that if you have a PEM-formatted private key and a separate PEM-formatted
103 certificate, you can concatenate the two into a single file and the
104 individual components will still be easily accessible.
105
106 If you have a single file that holds both your private key and your
 
 
107 cert, you can hand it off to the "[fossil server](/help?cmd=server)"
108 command using the --cert option. Like this:
109
110 fossil server --port 443 --cert mycert.pem /home/www/myproject.fossil
111
112 The command above is sufficient to run a fully-encrypted web site for
113 the "myproject.fossil" Fossil repository. This command must be run as
114 root, since it wants to listen on TCP port 443, and only root processes are
115 allowed to do that. This is safe, however, since before reading any
116 information off of the wire, Fossil will put itself inside a chroot jail
117 at /home/www and drop all root privileges.
118
119 ### Keeping The Cert And Private Key In Separate Files
120
121 If you do not want to combine your cert and private key into a single
122 big PEM file, you can keep them separate using the --pkey option to
123 Fossil.
 
 
 
124
125 fossil server --port 443 --cert fullchain.pem --pkey privkey.pem /home/www/myproject.fossil
126
127 ## The ACME Protocol
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
129 The [ACME Protocol][2] is used to prove to a CA that you control a
130 website. CAs require proof that you control a domain before they
131 will issue a cert for that domain. The usual means of dealing
132 with ACME is to run the separate [certbot](https://certbot.eff.org) tool.
133 Here is, in a nutshell, what certbot will do to obtain your cert:
134
135 1. Certbot sends your "signing request" (the document that contains
136 your public key and your domain name) to the CA.
137
138 2. After receiving the signing request, the CA needs to verify that you
139 control the domain of the cert. To do this (or, one common
140 way of doing this, at least) the CA sends a secret token back to
141 certbot through a secure backchannel, and instructs certbot to make
142 that token accessible on the (unencrypted, ordinary "http:") web site
143 for the domain in a particular file under the ".well-known" subdirectory.
144
145 3. Certbot puts the token where the CA requested it, then notifies the
146 CA that it is there.
147
148 4. The CA accesses the token to confirm that you do indeed control the
149 website. It then creates the cert and sends it back to certbot.
150
151 5. Certbot stores your cert and deletes the ".well-known" token.
 
 
 
 
 
 
152
153 In order for all of this to happen, certbot needs to be able to create
154 a subdirectory named ".well-known", within a directory you specify, and
155 then populate that subdirectory with a token file of some kind. To support
156 this, the "[fossil server](/help?cmd=server)" and
157 "[fossil http](/help?cmd=http)" commands have the --acme option.
158 When the --acme option is specified and Fossil sees a URL where the path
 
159 begins with ".well-known", then instead of doing its normal processing, it
160 looks for a file with that pathname and returns it to the client. If
161 the "server" or "http" command is referencing a single Fossil repository,
162 then the ".well-known" sub-directory should be in the same directory as
163 the repository file. If the "server" or "http" command are run against
@@ -172,9 +279,11 @@
172 Then you create your public/private key pair and run certbot, giving it
173 a --webroot of /home/www. Certbot will create the sub-directory
174 named "/home/www/.well-known" and put token files there, which the CA
175 will verify. Then certbot will store your new cert in a particular file.
176
177 Once certbot has obtained your cert, then you can concatenate that
178 cert with your private key and run Fossil in SSL/TLS mode as shown above.
 
179
180 [2]: https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment
 
181
--- www/ssl-server.md
+++ www/ssl-server.md
@@ -12,76 +12,107 @@
12
13 [0]: ./ssl.wiki
14 [1]: /timeline?c=b05cb4a0e15d0712&y=ci&n=13
15
16 Beginning in [late December 2021](/timeline?c=f6263bb64195b07f&y=a&n=13),
17 Fossil servers are now able to converse directly over TLS. Commands like
18
19 * "[fossil server](/help?cmd=server)"
20 * "[fossil ui](/help?cmd=ui)", and
21 * "[fossil http](/help?cmd=http)"
22
23 may now handle the encryption natively when suitably configured, without
24 requiring a third-party proxy layer.
25
26 ## <a id="usage"></a>Usage
 
 
 
 
27
28 To put any of the Fossil server commands into SSL/TLS mode, simply
29 add the "`--cert`" command-line option:
30
31 fossil ui --cert unsafe-builtin
32
33 Here, we are passing the magic name "unsafe-builtin" to cause Fossil to
34 use a [hard-coded self-signed cert][hcssc] rather than one obtained from
35 a recognized [Certificate Authority][CA], or "CA".
36
37 As the name implies, this self-signed cert is _not secure_ and should
38 only be used for testing. Your web browser is likely to complain
39 bitterly about it and will refuse to display the pages using the
40 "unsafe-builtin" cert until you placate it. The complexity of the
41 ceremony demanded depends on how paranoid your browser’s creators have
42 decided to be. It may require as little as clicking a single big "I know
43 the risks" type of button, or it may require a sequence be several
44 clicks designed to discourage the “yes, yes, just let me do the thing”
45 crowd lest they run themselves into trouble by disregarding well-meant
46 warnings.
47
48 Our purpose here is to show you an alternate path that will avoid the
49 issue entirely, not weigh in on which browser handles self-signed
50 certificates best.
51
52 [CA]: https://en.wikipedia.org/wiki/Certificate_authority
53 [hcssc]: /info/c2a7b14c3f541edb96?ln=89-116
54
55 ## <a id="about"></a>About Certs
56
57 The X.509 certificate system used by browsers to secure TLS connections
58 is based on asymmetric public-key cryptography. The methods for
59 obtaining one vary widely, with a resulting tradeoff we may summarize as
60 trustworthiness versus convenience, the latter characteristic falling as
61 the former rises.(^No strict correlation exists. CAs have invented
62 highly inconvenient certification schemes that offer little additional
63 real-world trustworthiness. Extreme cases along this axis may be fairly
64 characterized as [security theater][st]. We focus in this document on
65 well-balanced trade-offs between decreasing convenience and useful
66 levels of trustworthiness gained thereby.)
67
68 The self-signed method demonstrated above offers approximately zero
69 trustworthiness, though not zero _value_ since it does still provide
70 connection encryption.
71
72 More trustworthy methods are necessarily less convenient. One such is to
73 send your public key and the name of the domain you want to protect to a
74 recognized CA, which then performs one or more tests to convince itself
75 that the requester is in control of that domain. If the CA’s tests all
76 pass, it produces an X.509 certificate bound to that domain, which
77 includes assorted other information under the CA’s digital signature
78 attesting to the validity of the document’s contents. The result is sent
79 back to the requester, which may then use it to transitively attest to
80 these tests’ success: presuming one cannot fake the type of signature
81 used, the document must have been signed by the trusted, recognized CA.
82
83 There is one element of the assorted information included with a
84 certificate that is neither supplied by the requester nor rubber-stamped
85 on it in passing by the CA. It also generates a one-time key pair and
86 stores the public half in the certificate. The cryptosystem this keypair
87 is intended to work with varies both by the CA and by time, as older
88 systems become obsolete. Details aside, the CA then puts this matching
89 private half of the key in a separate file, often encrypted under a
90 separate cryptosystem for security.
91
92 SSL/TLS servers need both resulting halves to make these attestations,
93 but they send only the public half to the client when establishing the
94 connection. The client then makes its own checks to determine whether it
95 trusts the attestations being made.
96
97 A properly written and administered server never releases the private
98 key to anyone. Ideally, it goes directly from the CA to the requesting
99 server and never moves from there; then when it expires, the server
100 deletes it permanently.
101
102 [st]: https://en.wikipedia.org/wiki/Security_theater
103
104 ## <a id="startup"></a>How To Tell Fossil About Your Cert And Private Key
105
106 As we saw [above](#usage),
107 if you do not have your own cert and private key, you can ask Fossil
108 to use "unsafe-builtin", which is a self-signed cert that is built into
109 Fossil. This is wildly insecure, since the private key is not really private;
110 it is [in plain sight][hcssc] in the Fossil
111 source tree for anybody to read. <b>Never add the private key that is
112 built into Fossil to your OS's trust store</b> as doing so will severely
113 compromise your computer.[^ssattack] This built-in cert is only useful for testing.
114 If you want actual security, you will need to come up with your own private
115 key and cert.
116
117 Fossil wants to read certs and public keys in the
118 [PEM format](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail).
@@ -98,66 +129,142 @@
129 *base-64 encoding of the certificate*
130 -----END CERTIFICATE-----
131
132 In both formats, text outside of the delimiters is ignored. That means
133 that if you have a PEM-formatted private key and a separate PEM-formatted
134 certificate, you can concatenate the two into a single file, and the
135 individual components will still be easily accessible.
136
137 ### <a id="cat"></a>Separate or Concatenated?
138
139 Given a single concatenated file that holds both your private key and your
140 cert, you can hand it off to the "[fossil server](/help?cmd=server)"
141 command using the `--cert` option, like this:
142
143 fossil server --port 443 --cert mycert.pem /home/www/myproject.fossil
144
145 The command above is sufficient to run a fully-encrypted web site for
146 the "myproject.fossil" Fossil repository. This command must be run as
147 root, since it wants to listen on TCP port 443, and only root processes are
148 allowed to do that. This is safe, however, since before reading any
149 information off of the wire, Fossil will [put itself inside a chroot
150 jail](./chroot.md) at `/home/www` and drop all root privileges.
151
152 This method of combining your cert and private key into a single big PEM
153 file carries risks, one of which is that the system administrator must
154 make both halves readable by the user running the Fossil server. Given
155 the chroot jail feature, a more secure scheme separates the halves so
156 that only root can read the private half, which then means that when
157 Fossil drops its root privileges, it becomes unable to access the
158 private key on disk. Fossil’s `server` feature includes the `--pkey`
159 option to allow for that use case:
160
161 fossil server --port 443 --cert fullchain.pem --pkey privkey.pem /home/www/myproject.fossil
162
163 [^ssattack]: ^How, you ask? Because the keys are known, they can be used
164 to provide signed certificates for **any** other domain. One foolish
165 enough to tell their OS’s TLS mechanisms to trust the signing
166 certificate is implicitly handing over all TLS encryption controls
167 to any attacker that knows they did this. Don’t do it.
168
169 ### <a id="chain"></a>Chains and Links
170
171 The file name “`fullchain.pem`” used above is a reference to a term of
172 art within this world of TLS protocols and their associated X.509
173 certificates. Within the simplistic scheme originally envisioned by the
174 creators of SSL — the predecessor to TLS — we were all expected to agree
175 on a single set of CA root authorities, and we would all agree to get
176 our certificates from one of them. The real world is more complicated:
177
178 * The closest we have to universal acceptance of CAs is via the
179 [CA/Browser Forum][CAB], and even within its select membership there
180 is continual argument over which roots are trustworthy. (Hashing
181 that out is arguably this group’s key purpose.)
182
183 * CAB’s decision regarding trustworthiness may not match that of any
184 given system’s administrator. There are solid, defensible reasons to
185 prune back the stock CA root set included with your browser, then to
186 augment it with ones CAB _doesn’t_ trust.
187
188 * TLS isn’t limited to use between web browsers and public Internet
189 sites. Several common use cases preclude use of the process CAB
190 envisions, with servers able to contact Internet-based CA roots as
191 part of proving their identity. Different use cases demand different
192 CA root authority stores.
193
194 The most common of these divergent cases are servers behind strict
195 firewalls and edge devices that never interact with the public
196 Internet. This class ranges from cheap home IoT devices to the
197 internal equipment managed by IT for a massive global corporation.
198
199 Your private Fossil server is liable to fall into that last category.
200 This may then require that you generate a more complicated “chain” of
201 certificates for Fossil to use here, without which the client may not be
202 able to get back to a CA root it trusts. This is true regardless of
203 whether that client is another copy of Fossil or a web browser
204 traversing Fossil’s web UI, though that fact complicates matters by
205 allowing for multiple classes of client, each of which may have their
206 own rules for modifying the stock certificate scheme.
207
208 This is distressingly common, in fact: Fossil links to OpenSSL to
209 provide its TLS support, but there is a good chance that your browser
210 uses another TLS implementation entirely. They may or may not agree on a
211 single CA root store.
212
213 How you accommodate all this complexity varies by the CA and other
214 details. As but one example, Firefox’s “View Certificate” feature offers
215 _two_ ways to download a given web site’s certificate: the cert alone or
216 the “chain” leading back to the root. Depending on the use case, the
217 standalone certificate might suffice, or you might need some type of
218 cert chain. Complicating this is that the last link in the chain may be
219 left off when it is for a mutually trusted CA root, implicitly
220 completing the chain.
221
222 [CAB]: https://en.wikipedia.org/wiki/CA/Browser_Forum
223
224 ## <a id="acme"></a>The ACME Protocol
225
226 The [ACME Protocol][2] simplifies all this by automating the process of
227 proving to a recognized public CA that you are in control of a given
228 website. Without this proof, no valid CA will issue a cert for that
229 domain, as that allows fraudulent impersonation.
230
231 The primary implementation of ACME is [certbot], a product of the Let’s
232 Encrypt organization.
 
 
233 Here is, in a nutshell, what certbot will do to obtain your cert:
234
235 1. It sends your "signing request" (the document that contains
236 your public key and your domain name) to the CA.
237
238 2. After receiving the signing request, the CA needs to verify that
239 you control the domain of the cert. One of several methods certbot has
240 for accomplishing this is to create a secret token and place it at
241 a well-known location, then tell the CA about it over ACME.
242
243 3. The CA then tries pulling that token, which if successful proves
244 that the requester is able to create arbitrary data on the server,
245 implicitly proving control over that server. This must be done
246 over the unencrypted HTTP protocol since TLS isn’t working yet.
247
248 4. If satisfied by this proof of control, the CA then creates the
249 keypair described above and bakes the public half into the
250 certificate it signs. It then sends this and the private half of
251 the key back to certbot.
252
253 5. Certbot stores these halves separately for the reasons sketched
254 out above.
255
256 6. It then deletes the secret one-time-use token it used to prove
257 domain control. ACME’s design precludes replay attacks.
258
259 In order for all of this to happen, certbot needs to be able to create
260 a subdirectory named ".well-known", within a directory you specify,
261 then populate that subdirectory with a token file of some kind. To support
262 this, the "[fossil server](/help?cmd=server)" and
263 "[fossil http](/help?cmd=http)" commands have the --acme option.
264
265 When specified, Fossil sees a URL where the path
266 begins with ".well-known", then instead of doing its normal processing, it
267 looks for a file with that pathname and returns it to the client. If
268 the "server" or "http" command is referencing a single Fossil repository,
269 then the ".well-known" sub-directory should be in the same directory as
270 the repository file. If the "server" or "http" command are run against
@@ -172,9 +279,11 @@
279 Then you create your public/private key pair and run certbot, giving it
280 a --webroot of /home/www. Certbot will create the sub-directory
281 named "/home/www/.well-known" and put token files there, which the CA
282 will verify. Then certbot will store your new cert in a particular file.
283
284 Once certbot has obtained your cert, you may either pass the two halves
285 to Fossil separately using the `--pkey` and `--cert` options described
286 above, or you may concatenate them and pass that via `--cert` alone.
287
288 [2]: https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment
289 [certbot]: https://certbot.eff.org
290
--- www/sync.wiki
+++ www/sync.wiki
@@ -789,10 +789,18 @@
789789
a successful commit. This instructs the server to release
790790
any lock on any check-in previously held by that client.
791791
The ci-unlock pragma helps to avoid false-positive lock warnings
792792
that might arise if a check-in is aborted and then restarted
793793
on a branch.
794
+
795
+<li><b>req-clusters</b> A client sends the "req-clusters" pragma
796
+to the server to ask the server to reply with "igot" cards for
797
+every [./fileformat.wiki#cluster|cluster artifact] that it holds. The
798
+client typically does this when it thinks that it might be attempting
799
+to pull a long chain of cluster artifacts. Sending the artifacts
800
+all at once can dramatically reduce the number of round trip
801
+messages needed to complete the synchronization.
794802
</ol>
795803
796804
<h3 id="comment">3.12 Comment Cards</h3>
797805
798806
Any card that begins with "#" (ASCII 0x23) is a comment card and
799807
--- www/sync.wiki
+++ www/sync.wiki
@@ -789,10 +789,18 @@
789 a successful commit. This instructs the server to release
790 any lock on any check-in previously held by that client.
791 The ci-unlock pragma helps to avoid false-positive lock warnings
792 that might arise if a check-in is aborted and then restarted
793 on a branch.
 
 
 
 
 
 
 
 
794 </ol>
795
796 <h3 id="comment">3.12 Comment Cards</h3>
797
798 Any card that begins with "#" (ASCII 0x23) is a comment card and
799
--- www/sync.wiki
+++ www/sync.wiki
@@ -789,10 +789,18 @@
789 a successful commit. This instructs the server to release
790 any lock on any check-in previously held by that client.
791 The ci-unlock pragma helps to avoid false-positive lock warnings
792 that might arise if a check-in is aborted and then restarted
793 on a branch.
794
795 <li><b>req-clusters</b> A client sends the "req-clusters" pragma
796 to the server to ask the server to reply with "igot" cards for
797 every [./fileformat.wiki#cluster|cluster artifact] that it holds. The
798 client typically does this when it thinks that it might be attempting
799 to pull a long chain of cluster artifacts. Sending the artifacts
800 all at once can dramatically reduce the number of round trip
801 messages needed to complete the synchronization.
802 </ol>
803
804 <h3 id="comment">3.12 Comment Cards</h3>
805
806 Any card that begins with "#" (ASCII 0x23) is a comment card and
807

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button