Fossil SCM

Merge from trunk.

brickviking 2024-12-22 20:42 bv-corrections01 merge
Commit 8add3c418ac8cdeb1310e3b533e5d11c416ea09b3320db17e159ba55c317ff73
+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
+191 -178
--- extsrc/sqlite3.c
+++ extsrc/sqlite3.c
@@ -16,11 +16,11 @@
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
-** e2bae4143afd07de1ae55a6d2606a3b541a5 with changes in files:
21
+** e6c30ee52c5cdc193804cec63374d558b45e with changes in files:
2222
**
2323
**
2424
*/
2525
#ifndef SQLITE_AMALGAMATION
2626
#define SQLITE_CORE 1
@@ -465,11 +465,11 @@
465465
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
466466
** [sqlite_version()] and [sqlite_source_id()].
467467
*/
468468
#define SQLITE_VERSION "3.48.0"
469469
#define SQLITE_VERSION_NUMBER 3048000
470
-#define SQLITE_SOURCE_ID "2024-12-09 20:46:36 e2bae4143afd07de1ae55a6d2606a3b541a5b94568aa41f6a96e5d1245471653"
470
+#define SQLITE_SOURCE_ID "2024-12-19 19:02:09 e6c30ee52c5cdc193804cec63374d558b45e4d67fc6bde58771ca78485ca0acf"
471471
472472
/*
473473
** CAPI3REF: Run-Time Library Version Numbers
474474
** KEYWORDS: sqlite3_version sqlite3_sourceid
475475
**
@@ -14046,13 +14046,17 @@
1404614046
# define SQLITE_MAX_VDBE_OP 250000000
1404714047
#endif
1404814048
1404914049
/*
1405014050
** 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.
1405114055
*/
1405214056
#ifndef SQLITE_MAX_FUNCTION_ARG
14053
-# define SQLITE_MAX_FUNCTION_ARG 127
14057
+# define SQLITE_MAX_FUNCTION_ARG 1000
1405414058
#endif
1405514059
1405614060
/*
1405714061
** The suggested maximum number of in-memory pages to use for
1405814062
** the main database table and for temporary tables.
@@ -16049,10 +16053,26 @@
1604916053
#define PAGER_JOURNALMODE_PERSIST 1 /* Commit by zeroing journal header */
1605016054
#define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */
1605116055
#define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */
1605216056
#define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */
1605316057
#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)
1605416074
1605516075
/*
1605616076
** Flags that make up the mask passed to sqlite3PagerGet().
1605716077
*/
1605816078
#define PAGER_GET_NOCONTENT 0x01 /* Do not load data from disk */
@@ -18113,11 +18133,11 @@
1811318133
**
1811418134
** The u.pHash field is used by the global built-ins. The u.pDestructor
1811518135
** field is used by per-connection app-def functions.
1811618136
*/
1811718137
struct FuncDef {
18118
- i8 nArg; /* Number of arguments. -1 means unlimited */
18138
+ i16 nArg; /* Number of arguments. -1 means unlimited */
1811918139
u32 funcFlags; /* Some combination of SQLITE_FUNC_* */
1812018140
void *pUserData; /* User data parameter */
1812118141
FuncDef *pNext; /* Next function with same name */
1812218142
void (*xSFunc)(sqlite3_context*,int,sqlite3_value**); /* func or agg-step */
1812318143
void (*xFinalize)(sqlite3_context*); /* Agg finalizer */
@@ -23708,11 +23728,11 @@
2370823728
Vdbe *pVdbe; /* The VM that owns this context */
2370923729
int iOp; /* Instruction number of OP_Function */
2371023730
int isError; /* Error code returned by the function. */
2371123731
u8 enc; /* Encoding to use for results */
2371223732
u8 skipFlag; /* Skip accumulator loading if true */
23713
- u8 argc; /* Number of arguments */
23733
+ u16 argc; /* Number of arguments */
2371423734
sqlite3_value *argv[1]; /* Argument set */
2371523735
};
2371623736
2371723737
/* A bitfield type for use inside of structures. Always follow with :N where
2371823738
** N is the number of bits.
@@ -58014,24 +58034,10 @@
5801458034
# define USEFETCH(x) ((x)->bUseFetch)
5801558035
#else
5801658036
# define USEFETCH(x) 0
5801758037
#endif
5801858038
58019
-/*
58020
-** The argument to this macro is a file descriptor (type sqlite3_file*).
58021
-** Return 0 if it is not open, or non-zero (but not 1) if it is.
58022
-**
58023
-** This is so that expressions can be written as:
58024
-**
58025
-** if( isOpen(pPager->jfd) ){ ...
58026
-**
58027
-** instead of
58028
-**
58029
-** if( pPager->jfd->pMethods ){ ...
58030
-*/
58031
-#define isOpen(pFd) ((pFd)->pMethods!=0)
58032
-
5803358039
#ifdef SQLITE_DIRECT_OVERFLOW_READ
5803458040
/*
5803558041
** Return true if page pgno can be read directly from the database file
5803658042
** by the b-tree layer. This is the case if:
5803758043
**
@@ -59313,11 +59319,11 @@
5931359319
rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags);
5931459320
}
5931559321
}
5931659322
pPager->journalOff = 0;
5931759323
}else if( pPager->journalMode==PAGER_JOURNALMODE_PERSIST
59318
- || (pPager->exclusiveMode && pPager->journalMode!=PAGER_JOURNALMODE_WAL)
59324
+ || (pPager->exclusiveMode && pPager->journalMode<PAGER_JOURNALMODE_WAL)
5931959325
){
5932059326
rc = zeroJournalHdr(pPager, hasSuper||pPager->tempFile);
5932159327
pPager->journalOff = 0;
5932259328
}else{
5932359329
/* This branch may be executed with Pager.journalMode==MEMORY if
@@ -68023,15 +68029,11 @@
6802368029
** so it takes care to hold an exclusive lock on the corresponding
6802468030
** WAL_READ_LOCK() while changing values.
6802568031
*/
6802668032
static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){
6802768033
volatile WalCkptInfo *pInfo; /* Checkpoint information in wal-index */
68028
- u32 mxReadMark; /* Largest aReadMark[] value */
68029
- int mxI; /* Index of largest aReadMark[] value */
68030
- int i; /* Loop counter */
6803168034
int rc = SQLITE_OK; /* Return code */
68032
- u32 mxFrame; /* Wal frame to lock to */
6803368035
#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
6803468036
int nBlockTmout = 0;
6803568037
#endif
6803668038
6803768039
assert( pWal->readLock<0 ); /* Not currently locked */
@@ -68133,145 +68135,151 @@
6813368135
6813468136
assert( pWal->nWiData>0 );
6813568137
assert( pWal->apWiData[0]!=0 );
6813668138
pInfo = walCkptInfo(pWal);
6813768139
SEH_INJECT_FAULT;
68138
- if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame
68139
-#ifdef SQLITE_ENABLE_SNAPSHOT
68140
- && ((pWal->bGetSnapshot==0 && pWal->pSnapshot==0) || pWal->hdr.mxFrame==0)
68141
-#endif
68142
- ){
68143
- /* The WAL has been completely backfilled (or it is empty).
68144
- ** and can be safely ignored.
68145
- */
68146
- rc = walLockShared(pWal, WAL_READ_LOCK(0));
68147
- walShmBarrier(pWal);
68148
- if( rc==SQLITE_OK ){
68149
- if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) ){
68150
- /* It is not safe to allow the reader to continue here if frames
68151
- ** may have been appended to the log before READ_LOCK(0) was obtained.
68152
- ** When holding READ_LOCK(0), the reader ignores the entire log file,
68153
- ** which implies that the database file contains a trustworthy
68154
- ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from
68155
- ** happening, this is usually correct.
68156
- **
68157
- ** However, if frames have been appended to the log (or if the log
68158
- ** is wrapped and written for that matter) before the READ_LOCK(0)
68159
- ** is obtained, that is not necessarily true. A checkpointer may
68160
- ** have started to backfill the appended frames but crashed before
68161
- ** it finished. Leaving a corrupt image in the database file.
68162
- */
68163
- walUnlockShared(pWal, WAL_READ_LOCK(0));
68164
- return WAL_RETRY;
68165
- }
68166
- pWal->readLock = 0;
68167
- return SQLITE_OK;
68168
- }else if( rc!=SQLITE_BUSY ){
68169
- return rc;
68170
- }
68171
- }
68172
-
68173
- /* If we get this far, it means that the reader will want to use
68174
- ** the WAL to get at content from recent commits. The job now is
68175
- ** to select one of the aReadMark[] entries that is closest to
68176
- ** but not exceeding pWal->hdr.mxFrame and lock that entry.
68177
- */
68178
- mxReadMark = 0;
68179
- mxI = 0;
68180
- mxFrame = pWal->hdr.mxFrame;
68181
-#ifdef SQLITE_ENABLE_SNAPSHOT
68182
- if( pWal->pSnapshot && pWal->pSnapshot->mxFrame<mxFrame ){
68183
- mxFrame = pWal->pSnapshot->mxFrame;
68184
- }
68185
-#endif
68186
- for(i=1; i<WAL_NREADER; i++){
68187
- u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT;
68188
- if( mxReadMark<=thisMark && thisMark<=mxFrame ){
68189
- assert( thisMark!=READMARK_NOT_USED );
68190
- mxReadMark = thisMark;
68191
- mxI = i;
68192
- }
68193
- }
68194
- if( (pWal->readOnly & WAL_SHM_RDONLY)==0
68195
- && (mxReadMark<mxFrame || mxI==0)
68196
- ){
68197
- for(i=1; i<WAL_NREADER; i++){
68198
- rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
68199
- if( rc==SQLITE_OK ){
68200
- AtomicStore(pInfo->aReadMark+i,mxFrame);
68201
- mxReadMark = mxFrame;
68202
- mxI = i;
68203
- walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1);
68204
- break;
68205
- }else if( rc!=SQLITE_BUSY ){
68206
- return rc;
68207
- }
68208
- }
68209
- }
68210
- if( mxI==0 ){
68211
- assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 );
68212
- return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT;
68213
- }
68214
-
68215
- (void)walEnableBlockingMs(pWal, nBlockTmout);
68216
- rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
68217
- walDisableBlocking(pWal);
68218
- if( rc ){
68219
-#ifdef SQLITE_ENABLE_SETLK_TIMEOUT
68220
- if( rc==SQLITE_BUSY_TIMEOUT ){
68221
- *pCnt |= WAL_RETRY_BLOCKED_MASK;
68222
- }
68223
-#else
68224
- assert( rc!=SQLITE_BUSY_TIMEOUT );
68225
-#endif
68226
- assert( (rc&0xFF)!=SQLITE_BUSY||rc==SQLITE_BUSY||rc==SQLITE_BUSY_TIMEOUT );
68227
- return (rc&0xFF)==SQLITE_BUSY ? WAL_RETRY : rc;
68228
- }
68229
- /* Now that the read-lock has been obtained, check that neither the
68230
- ** value in the aReadMark[] array or the contents of the wal-index
68231
- ** header have changed.
68232
- **
68233
- ** It is necessary to check that the wal-index header did not change
68234
- ** between the time it was read and when the shared-lock was obtained
68235
- ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility
68236
- ** that the log file may have been wrapped by a writer, or that frames
68237
- ** that occur later in the log than pWal->hdr.mxFrame may have been
68238
- ** copied into the database by a checkpointer. If either of these things
68239
- ** happened, then reading the database with the current value of
68240
- ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry
68241
- ** instead.
68242
- **
68243
- ** Before checking that the live wal-index header has not changed
68244
- ** since it was read, set Wal.minFrame to the first frame in the wal
68245
- ** file that has not yet been checkpointed. This client will not need
68246
- ** to read any frames earlier than minFrame from the wal file - they
68247
- ** can be safely read directly from the database file.
68248
- **
68249
- ** Because a ShmBarrier() call is made between taking the copy of
68250
- ** nBackfill and checking that the wal-header in shared-memory still
68251
- ** matches the one cached in pWal->hdr, it is guaranteed that the
68252
- ** checkpointer that set nBackfill was not working with a wal-index
68253
- ** header newer than that cached in pWal->hdr. If it were, that could
68254
- ** cause a problem. The checkpointer could omit to checkpoint
68255
- ** a version of page X that lies before pWal->minFrame (call that version
68256
- ** A) on the basis that there is a newer version (version B) of the same
68257
- ** page later in the wal file. But if version B happens to like past
68258
- ** frame pWal->hdr.mxFrame - then the client would incorrectly assume
68259
- ** that it can read version A from the database file. However, since
68260
- ** we can guarantee that the checkpointer that set nBackfill could not
68261
- ** see any pages past pWal->hdr.mxFrame, this problem does not come up.
68262
- */
68263
- pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT;
68264
- walShmBarrier(pWal);
68265
- if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark
68266
- || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr))
68267
- ){
68268
- walUnlockShared(pWal, WAL_READ_LOCK(mxI));
68269
- return WAL_RETRY;
68270
- }else{
68271
- assert( mxReadMark<=pWal->hdr.mxFrame );
68272
- 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
+ }
6827368281
}
6827468282
return rc;
6827568283
}
6827668284
6827768285
#ifdef SQLITE_ENABLE_SNAPSHOT
@@ -91889,11 +91897,11 @@
9188991897
**
9189091898
** sqlite3_column_int()
9189191899
** sqlite3_column_int64()
9189291900
** sqlite3_column_text()
9189391901
** sqlite3_column_text16()
91894
-** sqlite3_column_real()
91902
+** sqlite3_column_double()
9189591903
** sqlite3_column_bytes()
9189691904
** sqlite3_column_bytes16()
9189791905
** sqlite3_column_blob()
9189891906
*/
9189991907
static void columnMallocFailure(sqlite3_stmt *pStmt)
@@ -109896,11 +109904,11 @@
109896109904
p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight);
109897109905
}
109898109906
p5 = binaryCompareP5(pLeft, pRight, jumpIfNull);
109899109907
addr = sqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1,
109900109908
(void*)p4, P4_COLLSEQ);
109901
- sqlite3VdbeChangeP5(pParse->pVdbe, (u8)p5);
109909
+ sqlite3VdbeChangeP5(pParse->pVdbe, (u16)p5);
109902109910
return addr;
109903109911
}
109904109912
109905109913
/*
109906109914
** Return true if expression pExpr is a vector, or false otherwise.
@@ -129732,11 +129740,11 @@
129732129740
|| (argc==3 && sqlite3_value_type(argv[2])==SQLITE_NULL)
129733129741
){
129734129742
return;
129735129743
}
129736129744
p0type = sqlite3_value_type(argv[0]);
129737
- p1 = sqlite3_value_int(argv[1]);
129745
+ p1 = sqlite3_value_int64(argv[1]);
129738129746
if( p0type==SQLITE_BLOB ){
129739129747
len = sqlite3_value_bytes(argv[0]);
129740129748
z = sqlite3_value_blob(argv[0]);
129741129749
if( z==0 ) return;
129742129750
assert( len==sqlite3_value_bytes(argv[0]) );
@@ -129757,11 +129765,11 @@
129757129765
** from 2009-02-02 for compatibility of applications that exploited the
129758129766
** old buggy behavior. */
129759129767
if( p1==0 ) p1 = 1; /* <rdar://problem/6778339> */
129760129768
#endif
129761129769
if( argc==3 ){
129762
- p2 = sqlite3_value_int(argv[2]);
129770
+ p2 = sqlite3_value_int64(argv[2]);
129763129771
if( p2<0 ){
129764129772
p2 = -p2;
129765129773
negP2 = 1;
129766129774
}
129767129775
}else{
@@ -129796,13 +129804,15 @@
129796129804
SQLITE_SKIP_UTF8(z2);
129797129805
}
129798129806
sqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT,
129799129807
SQLITE_UTF8);
129800129808
}else{
129801
- if( p1+p2>len ){
129809
+ if( p1>=len ){
129810
+ p1 = p2 = 0;
129811
+ }else if( p2>len-p1 ){
129802129812
p2 = len-p1;
129803
- if( p2<0 ) p2 = 0;
129813
+ assert( p2>0 );
129804129814
}
129805129815
sqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT);
129806129816
}
129807129817
}
129808129818
@@ -129809,17 +129819,17 @@
129809129819
/*
129810129820
** Implementation of the round() function
129811129821
*/
129812129822
#ifndef SQLITE_OMIT_FLOATING_POINT
129813129823
static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
129814
- int n = 0;
129824
+ i64 n = 0;
129815129825
double r;
129816129826
char *zBuf;
129817129827
assert( argc==1 || argc==2 );
129818129828
if( argc==2 ){
129819129829
if( SQLITE_NULL==sqlite3_value_type(argv[1]) ) return;
129820
- n = sqlite3_value_int(argv[1]);
129830
+ n = sqlite3_value_int64(argv[1]);
129821129831
if( n>30 ) n = 30;
129822129832
if( n<0 ) n = 0;
129823129833
}
129824129834
if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
129825129835
r = sqlite3_value_double(argv[0]);
@@ -141316,11 +141326,11 @@
141316141326
sqlite3VdbeAddOp3(v, OP_Null, 0, 8, 8+cnt);
141317141327
sqlite3ClearTempRegCache(pParse);
141318141328
141319141329
/* Do the b-tree integrity checks */
141320141330
sqlite3VdbeAddOp4(v, OP_IntegrityCk, 1, cnt, 8, (char*)aRoot,P4_INTARRAY);
141321
- sqlite3VdbeChangeP5(v, (u8)i);
141331
+ sqlite3VdbeChangeP5(v, (u16)i);
141322141332
addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v);
141323141333
sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0,
141324141334
sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName),
141325141335
P4_DYNAMIC);
141326141336
sqlite3VdbeAddOp3(v, OP_Concat, 2, 3, 3);
@@ -150549,11 +150559,11 @@
150549150559
}
150550150560
sqlite3ReleaseTempReg(pParse, regSubtype);
150551150561
}
150552150562
sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
150553150563
sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
150554
- sqlite3VdbeChangeP5(v, (u8)nArg);
150564
+ sqlite3VdbeChangeP5(v, (u16)nArg);
150555150565
sqlite3VdbeAddOp2(v, OP_Next, pF->iOBTab, iTop+1); VdbeCoverage(v);
150556150566
sqlite3VdbeJumpHere(v, iTop);
150557150567
sqlite3ReleaseTempRange(pParse, regAgg, nArg);
150558150568
}
150559150569
sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i),
@@ -150712,11 +150722,11 @@
150712150722
sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0,
150713150723
(char *)pColl, P4_COLLSEQ);
150714150724
}
150715150725
sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
150716150726
sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
150717
- sqlite3VdbeChangeP5(v, (u8)nArg);
150727
+ sqlite3VdbeChangeP5(v, (u16)nArg);
150718150728
sqlite3ReleaseTempRange(pParse, regAgg, nArg);
150719150729
}
150720150730
if( addrNext ){
150721150731
sqlite3VdbeResolveLabel(v, addrNext);
150722150732
}
@@ -154106,11 +154116,11 @@
154106154116
/* Set the P5 operand of the OP_Program instruction to non-zero if
154107154117
** recursive invocation of this trigger program is disallowed. Recursive
154108154118
** invocation is disallowed if (a) the sub-program is really a trigger,
154109154119
** not a foreign key action, and (b) the flag to enable recursive triggers
154110154120
** is clear. */
154111
- sqlite3VdbeChangeP5(v, (u8)bRecursive);
154121
+ sqlite3VdbeChangeP5(v, (u16)bRecursive);
154112154122
}
154113154123
}
154114154124
154115154125
/*
154116154126
** This is called to code the required FOR EACH ROW triggers for an operation
@@ -170414,10 +170424,11 @@
170414170424
int pc,
170415170425
VdbeOp *pOp
170416170426
){
170417170427
if( (db->flags & SQLITE_VdbeAddopTrace)==0 ) return;
170418170428
sqlite3VdbePrintOp(0, pc, pOp);
170429
+ sqlite3ShowWhereTerm(0); /* So compiler won't complain about unused func */
170419170430
}
170420170431
#endif
170421170432
170422170433
/*
170423170434
** Generate the end of the WHERE loop. See comments on
@@ -172523,11 +172534,11 @@
172523172534
sqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ);
172524172535
}
172525172536
sqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep,
172526172537
bInverse, regArg, pWin->regAccum);
172527172538
sqlite3VdbeAppendP4(v, pFunc, P4_FUNCDEF);
172528
- sqlite3VdbeChangeP5(v, (u8)nArg);
172539
+ sqlite3VdbeChangeP5(v, (u16)nArg);
172529172540
if( pWin->bExprArgs ){
172530172541
sqlite3ReleaseTempRange(pParse, regArg, nArg);
172531172542
}
172532172543
}
172533172544
@@ -184078,12 +184089,12 @@
184078184089
# error SQLITE_MAX_COMPOUND_SELECT must be at least 2
184079184090
#endif
184080184091
#if SQLITE_MAX_VDBE_OP<40
184081184092
# error SQLITE_MAX_VDBE_OP must be at least 40
184082184093
#endif
184083
-#if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>127
184084
-# error SQLITE_MAX_FUNCTION_ARG must be between 0 and 127
184094
+#if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>32767
184095
+# error SQLITE_MAX_FUNCTION_ARG must be between 0 and 32767
184085184096
#endif
184086184097
#if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>125
184087184098
# error SQLITE_MAX_ATTACHED must be between 0 and 125
184088184099
#endif
184089184100
#if SQLITE_MAX_LIKE_PATTERN_LENGTH<1
@@ -185492,11 +185503,10 @@
185492185503
#if defined(SQLITE_DEBUG)
185493185504
/* Invoke these debugging routines so that the compiler does not
185494185505
** issue "defined but not used" warnings. */
185495185506
if( x==9999 ){
185496185507
sqlite3ShowExpr(0);
185497
- sqlite3ShowExpr(0);
185498185508
sqlite3ShowExprList(0);
185499185509
sqlite3ShowIdList(0);
185500185510
sqlite3ShowSrcList(0);
185501185511
sqlite3ShowWith(0);
185502185512
sqlite3ShowUpsert(0);
@@ -185509,11 +185519,10 @@
185509185519
#ifndef SQLITE_OMIT_WINDOWFUNC
185510185520
sqlite3ShowWindow(0);
185511185521
sqlite3ShowWinFunc(0);
185512185522
#endif
185513185523
sqlite3ShowSelect(0);
185514
- sqlite3ShowWhereTerm(0);
185515185524
}
185516185525
#endif
185517185526
break;
185518185527
}
185519185528
@@ -226344,11 +226353,15 @@
226344226353
static int dbpageSync(sqlite3_vtab *pVtab){
226345226354
DbpageTable *pTab = (DbpageTable *)pVtab;
226346226355
if( pTab->pgnoTrunc>0 ){
226347226356
Btree *pBt = pTab->db->aDb[pTab->iDbTrunc].pBt;
226348226357
Pager *pPager = sqlite3BtreePager(pBt);
226349
- sqlite3PagerTruncateImage(pPager, pTab->pgnoTrunc);
226358
+ sqlite3BtreeEnter(pBt);
226359
+ if( pTab->pgnoTrunc<sqlite3BtreeLastPage(pBt) ){
226360
+ sqlite3PagerTruncateImage(pPager, pTab->pgnoTrunc);
226361
+ }
226362
+ sqlite3BtreeLeave(pBt);
226350226363
}
226351226364
pTab->pgnoTrunc = 0;
226352226365
return SQLITE_OK;
226353226366
}
226354226367
@@ -255419,11 +255432,11 @@
255419255432
int nArg, /* Number of args */
255420255433
sqlite3_value **apUnused /* Function arguments */
255421255434
){
255422255435
assert( nArg==0 );
255423255436
UNUSED_PARAM2(nArg, apUnused);
255424
- sqlite3_result_text(pCtx, "fts5: 2024-12-09 20:46:36 e2bae4143afd07de1ae55a6d2606a3b541a5b94568aa41f6a96e5d1245471653", -1, SQLITE_TRANSIENT);
255437
+ sqlite3_result_text(pCtx, "fts5: 2024-12-19 14:20:47 5b96dcf5f6bf41dcb89ced64efd4585e36dce718c428c2324d94e4942905c3bb", -1, SQLITE_TRANSIENT);
255425255438
}
255426255439
255427255440
/*
255428255441
** Implementation of fts5_locale(LOCALE, TEXT) function.
255429255442
**
255430255443
--- extsrc/sqlite3.c
+++ extsrc/sqlite3.c
@@ -16,11 +16,11 @@
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 ** e2bae4143afd07de1ae55a6d2606a3b541a5 with changes in files:
22 **
23 **
24 */
25 #ifndef SQLITE_AMALGAMATION
26 #define SQLITE_CORE 1
@@ -465,11 +465,11 @@
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-09 20:46:36 e2bae4143afd07de1ae55a6d2606a3b541a5b94568aa41f6a96e5d1245471653"
471
472 /*
473 ** CAPI3REF: Run-Time Library Version Numbers
474 ** KEYWORDS: sqlite3_version sqlite3_sourceid
475 **
@@ -14046,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 #ifndef SQLITE_MAX_FUNCTION_ARG
14053 # define SQLITE_MAX_FUNCTION_ARG 127
14054 #endif
14055
14056 /*
14057 ** The suggested maximum number of in-memory pages to use for
14058 ** the main database table and for temporary tables.
@@ -16049,10 +16053,26 @@
16049 #define PAGER_JOURNALMODE_PERSIST 1 /* Commit by zeroing journal header */
16050 #define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */
16051 #define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */
16052 #define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */
16053 #define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16054
16055 /*
16056 ** Flags that make up the mask passed to sqlite3PagerGet().
16057 */
16058 #define PAGER_GET_NOCONTENT 0x01 /* Do not load data from disk */
@@ -18113,11 +18133,11 @@
18113 **
18114 ** The u.pHash field is used by the global built-ins. The u.pDestructor
18115 ** field is used by per-connection app-def functions.
18116 */
18117 struct FuncDef {
18118 i8 nArg; /* Number of arguments. -1 means unlimited */
18119 u32 funcFlags; /* Some combination of SQLITE_FUNC_* */
18120 void *pUserData; /* User data parameter */
18121 FuncDef *pNext; /* Next function with same name */
18122 void (*xSFunc)(sqlite3_context*,int,sqlite3_value**); /* func or agg-step */
18123 void (*xFinalize)(sqlite3_context*); /* Agg finalizer */
@@ -23708,11 +23728,11 @@
23708 Vdbe *pVdbe; /* The VM that owns this context */
23709 int iOp; /* Instruction number of OP_Function */
23710 int isError; /* Error code returned by the function. */
23711 u8 enc; /* Encoding to use for results */
23712 u8 skipFlag; /* Skip accumulator loading if true */
23713 u8 argc; /* Number of arguments */
23714 sqlite3_value *argv[1]; /* Argument set */
23715 };
23716
23717 /* A bitfield type for use inside of structures. Always follow with :N where
23718 ** N is the number of bits.
@@ -58014,24 +58034,10 @@
58014 # define USEFETCH(x) ((x)->bUseFetch)
58015 #else
58016 # define USEFETCH(x) 0
58017 #endif
58018
58019 /*
58020 ** The argument to this macro is a file descriptor (type sqlite3_file*).
58021 ** Return 0 if it is not open, or non-zero (but not 1) if it is.
58022 **
58023 ** This is so that expressions can be written as:
58024 **
58025 ** if( isOpen(pPager->jfd) ){ ...
58026 **
58027 ** instead of
58028 **
58029 ** if( pPager->jfd->pMethods ){ ...
58030 */
58031 #define isOpen(pFd) ((pFd)->pMethods!=0)
58032
58033 #ifdef SQLITE_DIRECT_OVERFLOW_READ
58034 /*
58035 ** Return true if page pgno can be read directly from the database file
58036 ** by the b-tree layer. This is the case if:
58037 **
@@ -59313,11 +59319,11 @@
59313 rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags);
59314 }
59315 }
59316 pPager->journalOff = 0;
59317 }else if( pPager->journalMode==PAGER_JOURNALMODE_PERSIST
59318 || (pPager->exclusiveMode && pPager->journalMode!=PAGER_JOURNALMODE_WAL)
59319 ){
59320 rc = zeroJournalHdr(pPager, hasSuper||pPager->tempFile);
59321 pPager->journalOff = 0;
59322 }else{
59323 /* This branch may be executed with Pager.journalMode==MEMORY if
@@ -68023,15 +68029,11 @@
68023 ** so it takes care to hold an exclusive lock on the corresponding
68024 ** WAL_READ_LOCK() while changing values.
68025 */
68026 static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){
68027 volatile WalCkptInfo *pInfo; /* Checkpoint information in wal-index */
68028 u32 mxReadMark; /* Largest aReadMark[] value */
68029 int mxI; /* Index of largest aReadMark[] value */
68030 int i; /* Loop counter */
68031 int rc = SQLITE_OK; /* Return code */
68032 u32 mxFrame; /* Wal frame to lock to */
68033 #ifdef SQLITE_ENABLE_SETLK_TIMEOUT
68034 int nBlockTmout = 0;
68035 #endif
68036
68037 assert( pWal->readLock<0 ); /* Not currently locked */
@@ -68133,145 +68135,151 @@
68133
68134 assert( pWal->nWiData>0 );
68135 assert( pWal->apWiData[0]!=0 );
68136 pInfo = walCkptInfo(pWal);
68137 SEH_INJECT_FAULT;
68138 if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame
68139 #ifdef SQLITE_ENABLE_SNAPSHOT
68140 && ((pWal->bGetSnapshot==0 && pWal->pSnapshot==0) || pWal->hdr.mxFrame==0)
68141 #endif
68142 ){
68143 /* The WAL has been completely backfilled (or it is empty).
68144 ** and can be safely ignored.
68145 */
68146 rc = walLockShared(pWal, WAL_READ_LOCK(0));
68147 walShmBarrier(pWal);
68148 if( rc==SQLITE_OK ){
68149 if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) ){
68150 /* It is not safe to allow the reader to continue here if frames
68151 ** may have been appended to the log before READ_LOCK(0) was obtained.
68152 ** When holding READ_LOCK(0), the reader ignores the entire log file,
68153 ** which implies that the database file contains a trustworthy
68154 ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from
68155 ** happening, this is usually correct.
68156 **
68157 ** However, if frames have been appended to the log (or if the log
68158 ** is wrapped and written for that matter) before the READ_LOCK(0)
68159 ** is obtained, that is not necessarily true. A checkpointer may
68160 ** have started to backfill the appended frames but crashed before
68161 ** it finished. Leaving a corrupt image in the database file.
68162 */
68163 walUnlockShared(pWal, WAL_READ_LOCK(0));
68164 return WAL_RETRY;
68165 }
68166 pWal->readLock = 0;
68167 return SQLITE_OK;
68168 }else if( rc!=SQLITE_BUSY ){
68169 return rc;
68170 }
68171 }
68172
68173 /* If we get this far, it means that the reader will want to use
68174 ** the WAL to get at content from recent commits. The job now is
68175 ** to select one of the aReadMark[] entries that is closest to
68176 ** but not exceeding pWal->hdr.mxFrame and lock that entry.
68177 */
68178 mxReadMark = 0;
68179 mxI = 0;
68180 mxFrame = pWal->hdr.mxFrame;
68181 #ifdef SQLITE_ENABLE_SNAPSHOT
68182 if( pWal->pSnapshot && pWal->pSnapshot->mxFrame<mxFrame ){
68183 mxFrame = pWal->pSnapshot->mxFrame;
68184 }
68185 #endif
68186 for(i=1; i<WAL_NREADER; i++){
68187 u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT;
68188 if( mxReadMark<=thisMark && thisMark<=mxFrame ){
68189 assert( thisMark!=READMARK_NOT_USED );
68190 mxReadMark = thisMark;
68191 mxI = i;
68192 }
68193 }
68194 if( (pWal->readOnly & WAL_SHM_RDONLY)==0
68195 && (mxReadMark<mxFrame || mxI==0)
68196 ){
68197 for(i=1; i<WAL_NREADER; i++){
68198 rc = walLockExclusive(pWal, WAL_READ_LOCK(i), 1);
68199 if( rc==SQLITE_OK ){
68200 AtomicStore(pInfo->aReadMark+i,mxFrame);
68201 mxReadMark = mxFrame;
68202 mxI = i;
68203 walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1);
68204 break;
68205 }else if( rc!=SQLITE_BUSY ){
68206 return rc;
68207 }
68208 }
68209 }
68210 if( mxI==0 ){
68211 assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 );
68212 return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT;
68213 }
68214
68215 (void)walEnableBlockingMs(pWal, nBlockTmout);
68216 rc = walLockShared(pWal, WAL_READ_LOCK(mxI));
68217 walDisableBlocking(pWal);
68218 if( rc ){
68219 #ifdef SQLITE_ENABLE_SETLK_TIMEOUT
68220 if( rc==SQLITE_BUSY_TIMEOUT ){
68221 *pCnt |= WAL_RETRY_BLOCKED_MASK;
68222 }
68223 #else
68224 assert( rc!=SQLITE_BUSY_TIMEOUT );
68225 #endif
68226 assert( (rc&0xFF)!=SQLITE_BUSY||rc==SQLITE_BUSY||rc==SQLITE_BUSY_TIMEOUT );
68227 return (rc&0xFF)==SQLITE_BUSY ? WAL_RETRY : rc;
68228 }
68229 /* Now that the read-lock has been obtained, check that neither the
68230 ** value in the aReadMark[] array or the contents of the wal-index
68231 ** header have changed.
68232 **
68233 ** It is necessary to check that the wal-index header did not change
68234 ** between the time it was read and when the shared-lock was obtained
68235 ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility
68236 ** that the log file may have been wrapped by a writer, or that frames
68237 ** that occur later in the log than pWal->hdr.mxFrame may have been
68238 ** copied into the database by a checkpointer. If either of these things
68239 ** happened, then reading the database with the current value of
68240 ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry
68241 ** instead.
68242 **
68243 ** Before checking that the live wal-index header has not changed
68244 ** since it was read, set Wal.minFrame to the first frame in the wal
68245 ** file that has not yet been checkpointed. This client will not need
68246 ** to read any frames earlier than minFrame from the wal file - they
68247 ** can be safely read directly from the database file.
68248 **
68249 ** Because a ShmBarrier() call is made between taking the copy of
68250 ** nBackfill and checking that the wal-header in shared-memory still
68251 ** matches the one cached in pWal->hdr, it is guaranteed that the
68252 ** checkpointer that set nBackfill was not working with a wal-index
68253 ** header newer than that cached in pWal->hdr. If it were, that could
68254 ** cause a problem. The checkpointer could omit to checkpoint
68255 ** a version of page X that lies before pWal->minFrame (call that version
68256 ** A) on the basis that there is a newer version (version B) of the same
68257 ** page later in the wal file. But if version B happens to like past
68258 ** frame pWal->hdr.mxFrame - then the client would incorrectly assume
68259 ** that it can read version A from the database file. However, since
68260 ** we can guarantee that the checkpointer that set nBackfill could not
68261 ** see any pages past pWal->hdr.mxFrame, this problem does not come up.
68262 */
68263 pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT;
68264 walShmBarrier(pWal);
68265 if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark
68266 || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr))
68267 ){
68268 walUnlockShared(pWal, WAL_READ_LOCK(mxI));
68269 return WAL_RETRY;
68270 }else{
68271 assert( mxReadMark<=pWal->hdr.mxFrame );
68272 pWal->readLock = (i16)mxI;
 
 
 
 
 
 
68273 }
68274 return rc;
68275 }
68276
68277 #ifdef SQLITE_ENABLE_SNAPSHOT
@@ -91889,11 +91897,11 @@
91889 **
91890 ** sqlite3_column_int()
91891 ** sqlite3_column_int64()
91892 ** sqlite3_column_text()
91893 ** sqlite3_column_text16()
91894 ** sqlite3_column_real()
91895 ** sqlite3_column_bytes()
91896 ** sqlite3_column_bytes16()
91897 ** sqlite3_column_blob()
91898 */
91899 static void columnMallocFailure(sqlite3_stmt *pStmt)
@@ -109896,11 +109904,11 @@
109896 p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight);
109897 }
109898 p5 = binaryCompareP5(pLeft, pRight, jumpIfNull);
109899 addr = sqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1,
109900 (void*)p4, P4_COLLSEQ);
109901 sqlite3VdbeChangeP5(pParse->pVdbe, (u8)p5);
109902 return addr;
109903 }
109904
109905 /*
109906 ** Return true if expression pExpr is a vector, or false otherwise.
@@ -129732,11 +129740,11 @@
129732 || (argc==3 && sqlite3_value_type(argv[2])==SQLITE_NULL)
129733 ){
129734 return;
129735 }
129736 p0type = sqlite3_value_type(argv[0]);
129737 p1 = sqlite3_value_int(argv[1]);
129738 if( p0type==SQLITE_BLOB ){
129739 len = sqlite3_value_bytes(argv[0]);
129740 z = sqlite3_value_blob(argv[0]);
129741 if( z==0 ) return;
129742 assert( len==sqlite3_value_bytes(argv[0]) );
@@ -129757,11 +129765,11 @@
129757 ** from 2009-02-02 for compatibility of applications that exploited the
129758 ** old buggy behavior. */
129759 if( p1==0 ) p1 = 1; /* <rdar://problem/6778339> */
129760 #endif
129761 if( argc==3 ){
129762 p2 = sqlite3_value_int(argv[2]);
129763 if( p2<0 ){
129764 p2 = -p2;
129765 negP2 = 1;
129766 }
129767 }else{
@@ -129796,13 +129804,15 @@
129796 SQLITE_SKIP_UTF8(z2);
129797 }
129798 sqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT,
129799 SQLITE_UTF8);
129800 }else{
129801 if( p1+p2>len ){
 
 
129802 p2 = len-p1;
129803 if( p2<0 ) p2 = 0;
129804 }
129805 sqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT);
129806 }
129807 }
129808
@@ -129809,17 +129819,17 @@
129809 /*
129810 ** Implementation of the round() function
129811 */
129812 #ifndef SQLITE_OMIT_FLOATING_POINT
129813 static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
129814 int n = 0;
129815 double r;
129816 char *zBuf;
129817 assert( argc==1 || argc==2 );
129818 if( argc==2 ){
129819 if( SQLITE_NULL==sqlite3_value_type(argv[1]) ) return;
129820 n = sqlite3_value_int(argv[1]);
129821 if( n>30 ) n = 30;
129822 if( n<0 ) n = 0;
129823 }
129824 if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
129825 r = sqlite3_value_double(argv[0]);
@@ -141316,11 +141326,11 @@
141316 sqlite3VdbeAddOp3(v, OP_Null, 0, 8, 8+cnt);
141317 sqlite3ClearTempRegCache(pParse);
141318
141319 /* Do the b-tree integrity checks */
141320 sqlite3VdbeAddOp4(v, OP_IntegrityCk, 1, cnt, 8, (char*)aRoot,P4_INTARRAY);
141321 sqlite3VdbeChangeP5(v, (u8)i);
141322 addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v);
141323 sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0,
141324 sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName),
141325 P4_DYNAMIC);
141326 sqlite3VdbeAddOp3(v, OP_Concat, 2, 3, 3);
@@ -150549,11 +150559,11 @@
150549 }
150550 sqlite3ReleaseTempReg(pParse, regSubtype);
150551 }
150552 sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
150553 sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
150554 sqlite3VdbeChangeP5(v, (u8)nArg);
150555 sqlite3VdbeAddOp2(v, OP_Next, pF->iOBTab, iTop+1); VdbeCoverage(v);
150556 sqlite3VdbeJumpHere(v, iTop);
150557 sqlite3ReleaseTempRange(pParse, regAgg, nArg);
150558 }
150559 sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i),
@@ -150712,11 +150722,11 @@
150712 sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0,
150713 (char *)pColl, P4_COLLSEQ);
150714 }
150715 sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
150716 sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
150717 sqlite3VdbeChangeP5(v, (u8)nArg);
150718 sqlite3ReleaseTempRange(pParse, regAgg, nArg);
150719 }
150720 if( addrNext ){
150721 sqlite3VdbeResolveLabel(v, addrNext);
150722 }
@@ -154106,11 +154116,11 @@
154106 /* Set the P5 operand of the OP_Program instruction to non-zero if
154107 ** recursive invocation of this trigger program is disallowed. Recursive
154108 ** invocation is disallowed if (a) the sub-program is really a trigger,
154109 ** not a foreign key action, and (b) the flag to enable recursive triggers
154110 ** is clear. */
154111 sqlite3VdbeChangeP5(v, (u8)bRecursive);
154112 }
154113 }
154114
154115 /*
154116 ** This is called to code the required FOR EACH ROW triggers for an operation
@@ -170414,10 +170424,11 @@
170414 int pc,
170415 VdbeOp *pOp
170416 ){
170417 if( (db->flags & SQLITE_VdbeAddopTrace)==0 ) return;
170418 sqlite3VdbePrintOp(0, pc, pOp);
 
170419 }
170420 #endif
170421
170422 /*
170423 ** Generate the end of the WHERE loop. See comments on
@@ -172523,11 +172534,11 @@
172523 sqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ);
172524 }
172525 sqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep,
172526 bInverse, regArg, pWin->regAccum);
172527 sqlite3VdbeAppendP4(v, pFunc, P4_FUNCDEF);
172528 sqlite3VdbeChangeP5(v, (u8)nArg);
172529 if( pWin->bExprArgs ){
172530 sqlite3ReleaseTempRange(pParse, regArg, nArg);
172531 }
172532 }
172533
@@ -184078,12 +184089,12 @@
184078 # error SQLITE_MAX_COMPOUND_SELECT must be at least 2
184079 #endif
184080 #if SQLITE_MAX_VDBE_OP<40
184081 # error SQLITE_MAX_VDBE_OP must be at least 40
184082 #endif
184083 #if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>127
184084 # error SQLITE_MAX_FUNCTION_ARG must be between 0 and 127
184085 #endif
184086 #if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>125
184087 # error SQLITE_MAX_ATTACHED must be between 0 and 125
184088 #endif
184089 #if SQLITE_MAX_LIKE_PATTERN_LENGTH<1
@@ -185492,11 +185503,10 @@
185492 #if defined(SQLITE_DEBUG)
185493 /* Invoke these debugging routines so that the compiler does not
185494 ** issue "defined but not used" warnings. */
185495 if( x==9999 ){
185496 sqlite3ShowExpr(0);
185497 sqlite3ShowExpr(0);
185498 sqlite3ShowExprList(0);
185499 sqlite3ShowIdList(0);
185500 sqlite3ShowSrcList(0);
185501 sqlite3ShowWith(0);
185502 sqlite3ShowUpsert(0);
@@ -185509,11 +185519,10 @@
185509 #ifndef SQLITE_OMIT_WINDOWFUNC
185510 sqlite3ShowWindow(0);
185511 sqlite3ShowWinFunc(0);
185512 #endif
185513 sqlite3ShowSelect(0);
185514 sqlite3ShowWhereTerm(0);
185515 }
185516 #endif
185517 break;
185518 }
185519
@@ -226344,11 +226353,15 @@
226344 static int dbpageSync(sqlite3_vtab *pVtab){
226345 DbpageTable *pTab = (DbpageTable *)pVtab;
226346 if( pTab->pgnoTrunc>0 ){
226347 Btree *pBt = pTab->db->aDb[pTab->iDbTrunc].pBt;
226348 Pager *pPager = sqlite3BtreePager(pBt);
226349 sqlite3PagerTruncateImage(pPager, pTab->pgnoTrunc);
 
 
 
 
226350 }
226351 pTab->pgnoTrunc = 0;
226352 return SQLITE_OK;
226353 }
226354
@@ -255419,11 +255432,11 @@
255419 int nArg, /* Number of args */
255420 sqlite3_value **apUnused /* Function arguments */
255421 ){
255422 assert( nArg==0 );
255423 UNUSED_PARAM2(nArg, apUnused);
255424 sqlite3_result_text(pCtx, "fts5: 2024-12-09 20:46:36 e2bae4143afd07de1ae55a6d2606a3b541a5b94568aa41f6a96e5d1245471653", -1, SQLITE_TRANSIENT);
255425 }
255426
255427 /*
255428 ** Implementation of fts5_locale(LOCALE, TEXT) function.
255429 **
255430
--- extsrc/sqlite3.c
+++ extsrc/sqlite3.c
@@ -16,11 +16,11 @@
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 ** e6c30ee52c5cdc193804cec63374d558b45e with changes in files:
22 **
23 **
24 */
25 #ifndef SQLITE_AMALGAMATION
26 #define SQLITE_CORE 1
@@ -465,11 +465,11 @@
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-19 19:02:09 e6c30ee52c5cdc193804cec63374d558b45e4d67fc6bde58771ca78485ca0acf"
471
472 /*
473 ** CAPI3REF: Run-Time Library Version Numbers
474 ** KEYWORDS: sqlite3_version sqlite3_sourceid
475 **
@@ -14046,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.
@@ -16049,10 +16053,26 @@
16053 #define PAGER_JOURNALMODE_PERSIST 1 /* Commit by zeroing journal header */
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 */
@@ -18113,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 */
@@ -23708,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.
@@ -58014,24 +58034,10 @@
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 **
@@ -59313,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
@@ -68023,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 */
@@ -68133,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
@@ -91889,11 +91897,11 @@
91897 **
91898 ** sqlite3_column_int()
91899 ** sqlite3_column_int64()
91900 ** sqlite3_column_text()
91901 ** sqlite3_column_text16()
91902 ** sqlite3_column_double()
91903 ** sqlite3_column_bytes()
91904 ** sqlite3_column_bytes16()
91905 ** sqlite3_column_blob()
91906 */
91907 static void columnMallocFailure(sqlite3_stmt *pStmt)
@@ -109896,11 +109904,11 @@
109904 p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight);
109905 }
109906 p5 = binaryCompareP5(pLeft, pRight, jumpIfNull);
109907 addr = sqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1,
109908 (void*)p4, P4_COLLSEQ);
109909 sqlite3VdbeChangeP5(pParse->pVdbe, (u16)p5);
109910 return addr;
109911 }
109912
109913 /*
109914 ** Return true if expression pExpr is a vector, or false otherwise.
@@ -129732,11 +129740,11 @@
129740 || (argc==3 && sqlite3_value_type(argv[2])==SQLITE_NULL)
129741 ){
129742 return;
129743 }
129744 p0type = sqlite3_value_type(argv[0]);
129745 p1 = sqlite3_value_int64(argv[1]);
129746 if( p0type==SQLITE_BLOB ){
129747 len = sqlite3_value_bytes(argv[0]);
129748 z = sqlite3_value_blob(argv[0]);
129749 if( z==0 ) return;
129750 assert( len==sqlite3_value_bytes(argv[0]) );
@@ -129757,11 +129765,11 @@
129765 ** from 2009-02-02 for compatibility of applications that exploited the
129766 ** old buggy behavior. */
129767 if( p1==0 ) p1 = 1; /* <rdar://problem/6778339> */
129768 #endif
129769 if( argc==3 ){
129770 p2 = sqlite3_value_int64(argv[2]);
129771 if( p2<0 ){
129772 p2 = -p2;
129773 negP2 = 1;
129774 }
129775 }else{
@@ -129796,13 +129804,15 @@
129804 SQLITE_SKIP_UTF8(z2);
129805 }
129806 sqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT,
129807 SQLITE_UTF8);
129808 }else{
129809 if( p1>=len ){
129810 p1 = p2 = 0;
129811 }else if( p2>len-p1 ){
129812 p2 = len-p1;
129813 assert( p2>0 );
129814 }
129815 sqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT);
129816 }
129817 }
129818
@@ -129809,17 +129819,17 @@
129819 /*
129820 ** Implementation of the round() function
129821 */
129822 #ifndef SQLITE_OMIT_FLOATING_POINT
129823 static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){
129824 i64 n = 0;
129825 double r;
129826 char *zBuf;
129827 assert( argc==1 || argc==2 );
129828 if( argc==2 ){
129829 if( SQLITE_NULL==sqlite3_value_type(argv[1]) ) return;
129830 n = sqlite3_value_int64(argv[1]);
129831 if( n>30 ) n = 30;
129832 if( n<0 ) n = 0;
129833 }
129834 if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return;
129835 r = sqlite3_value_double(argv[0]);
@@ -141316,11 +141326,11 @@
141326 sqlite3VdbeAddOp3(v, OP_Null, 0, 8, 8+cnt);
141327 sqlite3ClearTempRegCache(pParse);
141328
141329 /* Do the b-tree integrity checks */
141330 sqlite3VdbeAddOp4(v, OP_IntegrityCk, 1, cnt, 8, (char*)aRoot,P4_INTARRAY);
141331 sqlite3VdbeChangeP5(v, (u16)i);
141332 addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v);
141333 sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0,
141334 sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName),
141335 P4_DYNAMIC);
141336 sqlite3VdbeAddOp3(v, OP_Concat, 2, 3, 3);
@@ -150549,11 +150559,11 @@
150559 }
150560 sqlite3ReleaseTempReg(pParse, regSubtype);
150561 }
150562 sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
150563 sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
150564 sqlite3VdbeChangeP5(v, (u16)nArg);
150565 sqlite3VdbeAddOp2(v, OP_Next, pF->iOBTab, iTop+1); VdbeCoverage(v);
150566 sqlite3VdbeJumpHere(v, iTop);
150567 sqlite3ReleaseTempRange(pParse, regAgg, nArg);
150568 }
150569 sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i),
@@ -150712,11 +150722,11 @@
150722 sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0,
150723 (char *)pColl, P4_COLLSEQ);
150724 }
150725 sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i));
150726 sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF);
150727 sqlite3VdbeChangeP5(v, (u16)nArg);
150728 sqlite3ReleaseTempRange(pParse, regAgg, nArg);
150729 }
150730 if( addrNext ){
150731 sqlite3VdbeResolveLabel(v, addrNext);
150732 }
@@ -154106,11 +154116,11 @@
154116 /* Set the P5 operand of the OP_Program instruction to non-zero if
154117 ** recursive invocation of this trigger program is disallowed. Recursive
154118 ** invocation is disallowed if (a) the sub-program is really a trigger,
154119 ** not a foreign key action, and (b) the flag to enable recursive triggers
154120 ** is clear. */
154121 sqlite3VdbeChangeP5(v, (u16)bRecursive);
154122 }
154123 }
154124
154125 /*
154126 ** This is called to code the required FOR EACH ROW triggers for an operation
@@ -170414,10 +170424,11 @@
170424 int pc,
170425 VdbeOp *pOp
170426 ){
170427 if( (db->flags & SQLITE_VdbeAddopTrace)==0 ) return;
170428 sqlite3VdbePrintOp(0, pc, pOp);
170429 sqlite3ShowWhereTerm(0); /* So compiler won't complain about unused func */
170430 }
170431 #endif
170432
170433 /*
170434 ** Generate the end of the WHERE loop. See comments on
@@ -172523,11 +172534,11 @@
172534 sqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ);
172535 }
172536 sqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep,
172537 bInverse, regArg, pWin->regAccum);
172538 sqlite3VdbeAppendP4(v, pFunc, P4_FUNCDEF);
172539 sqlite3VdbeChangeP5(v, (u16)nArg);
172540 if( pWin->bExprArgs ){
172541 sqlite3ReleaseTempRange(pParse, regArg, nArg);
172542 }
172543 }
172544
@@ -184078,12 +184089,12 @@
184089 # error SQLITE_MAX_COMPOUND_SELECT must be at least 2
184090 #endif
184091 #if SQLITE_MAX_VDBE_OP<40
184092 # error SQLITE_MAX_VDBE_OP must be at least 40
184093 #endif
184094 #if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>32767
184095 # error SQLITE_MAX_FUNCTION_ARG must be between 0 and 32767
184096 #endif
184097 #if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>125
184098 # error SQLITE_MAX_ATTACHED must be between 0 and 125
184099 #endif
184100 #if SQLITE_MAX_LIKE_PATTERN_LENGTH<1
@@ -185492,11 +185503,10 @@
185503 #if defined(SQLITE_DEBUG)
185504 /* Invoke these debugging routines so that the compiler does not
185505 ** issue "defined but not used" warnings. */
185506 if( x==9999 ){
185507 sqlite3ShowExpr(0);
 
185508 sqlite3ShowExprList(0);
185509 sqlite3ShowIdList(0);
185510 sqlite3ShowSrcList(0);
185511 sqlite3ShowWith(0);
185512 sqlite3ShowUpsert(0);
@@ -185509,11 +185519,10 @@
185519 #ifndef SQLITE_OMIT_WINDOWFUNC
185520 sqlite3ShowWindow(0);
185521 sqlite3ShowWinFunc(0);
185522 #endif
185523 sqlite3ShowSelect(0);
 
185524 }
185525 #endif
185526 break;
185527 }
185528
@@ -226344,11 +226353,15 @@
226353 static int dbpageSync(sqlite3_vtab *pVtab){
226354 DbpageTable *pTab = (DbpageTable *)pVtab;
226355 if( pTab->pgnoTrunc>0 ){
226356 Btree *pBt = pTab->db->aDb[pTab->iDbTrunc].pBt;
226357 Pager *pPager = sqlite3BtreePager(pBt);
226358 sqlite3BtreeEnter(pBt);
226359 if( pTab->pgnoTrunc<sqlite3BtreeLastPage(pBt) ){
226360 sqlite3PagerTruncateImage(pPager, pTab->pgnoTrunc);
226361 }
226362 sqlite3BtreeLeave(pBt);
226363 }
226364 pTab->pgnoTrunc = 0;
226365 return SQLITE_OK;
226366 }
226367
@@ -255419,11 +255432,11 @@
255432 int nArg, /* Number of args */
255433 sqlite3_value **apUnused /* Function arguments */
255434 ){
255435 assert( nArg==0 );
255436 UNUSED_PARAM2(nArg, apUnused);
255437 sqlite3_result_text(pCtx, "fts5: 2024-12-19 14:20:47 5b96dcf5f6bf41dcb89ced64efd4585e36dce718c428c2324d94e4942905c3bb", -1, SQLITE_TRANSIENT);
255438 }
255439
255440 /*
255441 ** Implementation of fts5_locale(LOCALE, TEXT) function.
255442 **
255443
--- extsrc/sqlite3.h
+++ extsrc/sqlite3.h
@@ -146,11 +146,11 @@
146146
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
147147
** [sqlite_version()] and [sqlite_source_id()].
148148
*/
149149
#define SQLITE_VERSION "3.48.0"
150150
#define SQLITE_VERSION_NUMBER 3048000
151
-#define SQLITE_SOURCE_ID "2024-12-09 20:46:36 e2bae4143afd07de1ae55a6d2606a3b541a5b94568aa41f6a96e5d1245471653"
151
+#define SQLITE_SOURCE_ID "2024-12-19 19:02:09 e6c30ee52c5cdc193804cec63374d558b45e4d67fc6bde58771ca78485ca0acf"
152152
153153
/*
154154
** CAPI3REF: Run-Time Library Version Numbers
155155
** KEYWORDS: sqlite3_version sqlite3_sourceid
156156
**
157157
--- extsrc/sqlite3.h
+++ extsrc/sqlite3.h
@@ -146,11 +146,11 @@
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-09 20:46:36 e2bae4143afd07de1ae55a6d2606a3b541a5b94568aa41f6a96e5d1245471653"
152
153 /*
154 ** CAPI3REF: Run-Time Library Version Numbers
155 ** KEYWORDS: sqlite3_version sqlite3_sourceid
156 **
157
--- extsrc/sqlite3.h
+++ extsrc/sqlite3.h
@@ -146,11 +146,11 @@
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-19 19:02:09 e6c30ee52c5cdc193804cec63374d558b45e4d67fc6bde58771ca78485ca0acf"
152
153 /*
154 ** CAPI3REF: Run-Time Library Version Numbers
155 ** KEYWORDS: sqlite3_version sqlite3_sourceid
156 **
157
+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
+1 -1
--- src/db.c
+++ src/db.c
@@ -2105,11 +2105,11 @@
21052105
sqlite3 *, char **, const sqlite3_api_routines *
21062106
);
21072107
sqlite3_appendvfs_init(0,0,0);
21082108
g.zVfsName = "apndvfs";
21092109
}
2110
- blob_zero(&bNameCheck);
2110
+ blob_reset(&bNameCheck);
21112111
rc = sqlite3_open_v2(
21122112
zDbName, &db,
21132113
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
21142114
g.zVfsName
21152115
);
21162116
--- src/db.c
+++ src/db.c
@@ -2105,11 +2105,11 @@
2105 sqlite3 *, char **, const sqlite3_api_routines *
2106 );
2107 sqlite3_appendvfs_init(0,0,0);
2108 g.zVfsName = "apndvfs";
2109 }
2110 blob_zero(&bNameCheck);
2111 rc = sqlite3_open_v2(
2112 zDbName, &db,
2113 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
2114 g.zVfsName
2115 );
2116
--- src/db.c
+++ src/db.c
@@ -2105,11 +2105,11 @@
2105 sqlite3 *, char **, const sqlite3_api_routines *
2106 );
2107 sqlite3_appendvfs_init(0,0,0);
2108 g.zVfsName = "apndvfs";
2109 }
2110 blob_reset(&bNameCheck);
2111 rc = sqlite3_open_v2(
2112 zDbName, &db,
2113 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
2114 g.zVfsName
2115 );
2116
+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
--- src/forum.c
+++ src/forum.c
@@ -1830,11 +1830,10 @@
18301830
if( db_int(0, "SELECT count(*) FROM user "
18311831
" WHERE cap GLOB '*5*' AND cap NOT GLOB '*[as6]*'")==0 ){
18321832
@ <p>No non-supervisor moderators
18331833
}else{
18341834
Stmt q = empty_Stmt;
1835
- int nRows = 0;
18361835
db_prepare(&q, "SELECT uid, login, cap FROM user "
18371836
"WHERE cap GLOB '*5*' AND cap NOT GLOB '*[as6]*'"
18381837
" ORDER BY login");
18391838
@ <table class='bordered'>
18401839
@ <thead><tr><th>User</th><th>Capabilities</th></tr></thead>
@@ -1841,11 +1840,10 @@
18411840
@ <tbody>
18421841
while( SQLITE_ROW==db_step(&q) ){
18431842
const int iUid = db_column_int(&q, 0);
18441843
const char *zUser = db_column_text(&q, 1);
18451844
const char *zCap = db_column_text(&q, 2);
1846
- ++nRows;
18471845
@ <tr>
18481846
@ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td>
18491847
@ <td>(%h(zCap))</td>
18501848
@ </tr>
18511849
}
18521850
--- src/forum.c
+++ src/forum.c
@@ -1830,11 +1830,10 @@
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 int nRows = 0;
1836 db_prepare(&q, "SELECT uid, login, cap FROM user "
1837 "WHERE cap GLOB '*5*' AND cap NOT GLOB '*[as6]*'"
1838 " ORDER BY login");
1839 @ <table class='bordered'>
1840 @ <thead><tr><th>User</th><th>Capabilities</th></tr></thead>
@@ -1841,11 +1840,10 @@
1841 @ <tbody>
1842 while( SQLITE_ROW==db_step(&q) ){
1843 const int iUid = db_column_int(&q, 0);
1844 const char *zUser = db_column_text(&q, 1);
1845 const char *zCap = db_column_text(&q, 2);
1846 ++nRows;
1847 @ <tr>
1848 @ <td><a href='%R/setup_uedit?id=%d(iUid)'>%h(zUser)</a></td>
1849 @ <td>(%h(zCap))</td>
1850 @ </tr>
1851 }
1852
--- src/forum.c
+++ src/forum.c
@@ -1830,11 +1830,10 @@
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>
@@ -1841,11 +1840,10 @@
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
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -28,17 +28,21 @@
2828
window.fossil.onPageLoad(function(){
2929
/**
3030
Adds toggle checkboxes to each file entry in the diff views for
3131
/info and similar pages.
3232
*/
33
+ if( !window.fossil.page.diffControlContainer ){
34
+ return;
35
+ }
3336
const D = window.fossil.dom;
3437
const allToggles = [/*collection of all diff-toggle checkboxes*/];
3538
let checkedCount =
3639
0 /* When showing more than one diff, keep track of how many
3740
"show/hide" checkboxes are are checked so we can update the
3841
"show/hide all" label dynamically. */;
39
- let btnAll /* show/hide all diffs UI control */;
42
+ let btnAll /* UI control to show/hide all diffs */;
43
+ /* Install a diff-toggle button for the given diff table element. */
4044
const addToggle = function(diffElem){
4145
const sib = diffElem.previousElementSibling,
4246
ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
4347
if(!sib) return;
4448
const lblToggle = D.label();
@@ -59,10 +63,15 @@
5963
if( !document.querySelector('body.fdiff') ){
6064
/* Don't show the diff toggle button for /fdiff because it only
6165
has a single file to show (and also a different DOM layout). */
6266
document.querySelectorAll('table.diff').forEach(addToggle);
6367
}
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
+ */
6473
const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0;
6574
if(icm) {
6675
btnAll = D.addClass(D.a("#", "Hide diffs"), "button");
6776
D.append( icm, btnAll );
6877
btnAll.addEventListener('click', function(ev){
6978
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -28,17 +28,21 @@
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 const D = window.fossil.dom;
34 const allToggles = [/*collection of all diff-toggle checkboxes*/];
35 let checkedCount =
36 0 /* When showing more than one diff, keep track of how many
37 "show/hide" checkboxes are are checked so we can update the
38 "show/hide all" label dynamically. */;
39 let btnAll /* show/hide all diffs UI control */;
 
40 const addToggle = function(diffElem){
41 const sib = diffElem.previousElementSibling,
42 ckbox = sib ? D.addClass(D.checkbox(true), 'diff-toggle') : 0;
43 if(!sib) return;
44 const lblToggle = D.label();
@@ -59,10 +63,15 @@
59 if( !document.querySelector('body.fdiff') ){
60 /* Don't show the diff toggle button for /fdiff because it only
61 has a single file to show (and also a different DOM layout). */
62 document.querySelectorAll('table.diff').forEach(addToggle);
63 }
 
 
 
 
 
64 const icm = allToggles.length>1 ? window.fossil.page.diffControlContainer : 0;
65 if(icm) {
66 btnAll = D.addClass(D.a("#", "Hide diffs"), "button");
67 D.append( icm, btnAll );
68 btnAll.addEventListener('click', function(ev){
69
--- src/fossil.diff.js
+++ src/fossil.diff.js
@@ -28,17 +28,21 @@
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();
@@ -59,10 +63,15 @@
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
+149 -12
--- 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
@@ -624,11 +625,10 @@
624625
pCfg = construct_diff_flags(diffType, &DCfg);
625626
nChng = db_int(0, "SELECT count(*) FROM vfile"
626627
" WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid);
627628
if( nChng==0 ){
628629
@ <p>No uncommitted changes</p>
629
- style_finish_page();
630630
return;
631631
}
632632
db_prepare(&q,
633633
/* 0 1 2 3 4 5 6 */
634634
"SELECT pathname, deleted, chnged , rid==0, rid, islink, uuid"
@@ -785,17 +785,12 @@
785785
@ Changes to %h(zFile)
786786
@ </span></div>
787787
if( pCfg ){
788788
char *zFullFN;
789789
char *zHexFN;
790
- int nFullFN;
791790
zFullFN = file_canonical_name_dup(zLhs);
792
- nFullFN = (int)strlen(zFullFN);
793
- zHexFN = fossil_malloc( nFullFN*2 + 5 );
794
- zHexFN[0] = 'x';
795
- encode16((const u8*)zFullFN, (u8*)(zHexFN+1), nFullFN);
796
- zHexFN[1+nFullFN*2] = 0;
791
+ zHexFN = mprintf("x%H", zFullFN);
797792
fossil_free(zFullFN);
798793
pCfg->zLeftHash = zHexFN;
799794
text_diff(&lhs, &rhs, cgi_output_blob(), pCfg);
800795
pCfg->zLeftHash = 0;
801796
fossil_free(zHexFN);
@@ -820,11 +815,14 @@
820815
**
821816
** If the "exbase=PATH" query parameter is provided, then the diff shown
822817
** uses the files in PATH as the baseline. This is the same as using
823818
** the "--from PATH" argument to the "fossil diff" command-line. In fact,
824819
** when using "fossil ui --from PATH", the --from argument becomes the value
825
-** of the exbase query parameter for the start page.
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.
826824
**
827825
** Other query parameters related to diffs are also accepted.
828826
*/
829827
void ckout_page(void){
830828
int vid;
@@ -832,11 +830,11 @@
832830
int nHome;
833831
const char *zExBase;
834832
char *zHostname;
835833
char *zCwd;
836834
837
- if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){
835
+ if( !cgi_is_loopback(g.zIpAddr) || !db_open_local(0) ){
838836
cgi_redirectf("%R/home");
839837
return;
840838
}
841839
file_chdir(g.zLocalRoot, 0);
842840
vid = db_lget_int("checkout", 0);
@@ -862,18 +860,20 @@
862860
}
863861
render_checkin_context(vid, 0, 0, 0);
864862
@ <hr>
865863
zExBase = P("exbase");
866864
if( zExBase && zExBase[0] ){
867
- char *zCBase = file_canonical_name_dup(zExBase);
865
+ char *zPath = decode16_dup(zExBase);
866
+ char *zCBase = file_canonical_name_dup(zPath?zPath:zExBase);
868867
if( nHome && strncmp(zCBase, zHome, nHome)==0 && zCBase[nHome]=='/' ){
869868
@ <p>Using external baseline: ~%h(zCBase+nHome)</p>
870869
}else{
871870
@ <p>Using external baseline: %h(zCBase)</p>
872871
}
873872
ckout_external_base_diff(vid, zCBase);
874873
fossil_free(zCBase);
874
+ fossil_free(zPath);
875875
}else{
876876
ckout_normal_diff(vid);
877877
}
878878
style_finish_page();
879879
}
@@ -1933,11 +1933,11 @@
19331933
tag_private_status(rid);
19341934
}
19351935
db_finalize(&q);
19361936
if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d AND tagid=%d",
19371937
rid, TAG_CLUSTER) ){
1938
- @ Cluster
1938
+ @ Cluster %z(href("%R/info/%S",zUuid))%S(zUuid)</a>.
19391939
cnt++;
19401940
}
19411941
if( cnt==0 ){
19421942
@ Unrecognized artifact
19431943
if( pDownloadName && blob_size(pDownloadName)==0 ){
@@ -2249,12 +2249,12 @@
22492249
}
22502250
if( zName[0]=='x'
22512251
&& ((nName-1)&1)==0
22522252
&& validate16(&zName[1],nName-1)
22532253
&& g.perm.Admin
2254
- && db_open_local(0)
22552254
&& cgi_is_loopback(g.zIpAddr)
2255
+ && db_open_local(0)
22562256
){
22572257
/* Treat the HASH as a hex-encoded filename */
22582258
int n = (nName-1)/2;
22592259
char *zFN = fossil_malloc(n+1);
22602260
decode16((const u8*)&zName[1], (u8*)zFN, nName-1);
@@ -3168,10 +3168,143 @@
31683168
ticket_output_change_artifact(pTktChng, 0, 1, 0);
31693169
manifest_destroy(pTktChng);
31703170
style_finish_page();
31713171
}
31723172
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
+
31733306
31743307
/*
31753308
** WEBPAGE: info
31763309
** URL: info/NAME
31773310
**
@@ -3256,10 +3389,14 @@
32563389
if( db_exists("SELECT 1 FROM plink WHERE pid=%d", rid) ){
32573390
ci_page();
32583391
}else
32593392
if( db_exists("SELECT 1 FROM attachment WHERE attachid=%d", rid) ){
32603393
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);
32613398
}else
32623399
{
32633400
artifact_page();
32643401
}
32653402
}
32663403
--- 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
@@ -624,11 +625,10 @@
624 pCfg = construct_diff_flags(diffType, &DCfg);
625 nChng = db_int(0, "SELECT count(*) FROM vfile"
626 " WHERE vid=%d AND (deleted OR chnged OR rid==0)", vid);
627 if( nChng==0 ){
628 @ <p>No uncommitted changes</p>
629 style_finish_page();
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"
@@ -785,17 +785,12 @@
785 @ Changes to %h(zFile)
786 @ </span></div>
787 if( pCfg ){
788 char *zFullFN;
789 char *zHexFN;
790 int nFullFN;
791 zFullFN = file_canonical_name_dup(zLhs);
792 nFullFN = (int)strlen(zFullFN);
793 zHexFN = fossil_malloc( nFullFN*2 + 5 );
794 zHexFN[0] = 'x';
795 encode16((const u8*)zFullFN, (u8*)(zHexFN+1), nFullFN);
796 zHexFN[1+nFullFN*2] = 0;
797 fossil_free(zFullFN);
798 pCfg->zLeftHash = zHexFN;
799 text_diff(&lhs, &rhs, cgi_output_blob(), pCfg);
800 pCfg->zLeftHash = 0;
801 fossil_free(zHexFN);
@@ -820,11 +815,14 @@
820 **
821 ** If the "exbase=PATH" query parameter is provided, then the diff shown
822 ** uses the files in PATH as the baseline. This is the same as using
823 ** the "--from PATH" argument to the "fossil diff" command-line. In fact,
824 ** when using "fossil ui --from PATH", the --from argument becomes the value
825 ** of the exbase query parameter for the start page.
 
 
 
826 **
827 ** Other query parameters related to diffs are also accepted.
828 */
829 void ckout_page(void){
830 int vid;
@@ -832,11 +830,11 @@
832 int nHome;
833 const char *zExBase;
834 char *zHostname;
835 char *zCwd;
836
837 if( !db_open_local(0) || !cgi_is_loopback(g.zIpAddr) ){
838 cgi_redirectf("%R/home");
839 return;
840 }
841 file_chdir(g.zLocalRoot, 0);
842 vid = db_lget_int("checkout", 0);
@@ -862,18 +860,20 @@
862 }
863 render_checkin_context(vid, 0, 0, 0);
864 @ <hr>
865 zExBase = P("exbase");
866 if( zExBase && zExBase[0] ){
867 char *zCBase = file_canonical_name_dup(zExBase);
 
868 if( nHome && strncmp(zCBase, zHome, nHome)==0 && zCBase[nHome]=='/' ){
869 @ <p>Using external baseline: ~%h(zCBase+nHome)</p>
870 }else{
871 @ <p>Using external baseline: %h(zCBase)</p>
872 }
873 ckout_external_base_diff(vid, zCBase);
874 fossil_free(zCBase);
 
875 }else{
876 ckout_normal_diff(vid);
877 }
878 style_finish_page();
879 }
@@ -1933,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
1939 cnt++;
1940 }
1941 if( cnt==0 ){
1942 @ Unrecognized artifact
1943 if( pDownloadName && blob_size(pDownloadName)==0 ){
@@ -2249,12 +2249,12 @@
2249 }
2250 if( zName[0]=='x'
2251 && ((nName-1)&1)==0
2252 && validate16(&zName[1],nName-1)
2253 && g.perm.Admin
2254 && db_open_local(0)
2255 && cgi_is_loopback(g.zIpAddr)
 
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);
@@ -3168,10 +3168,143 @@
3168 ticket_output_change_artifact(pTktChng, 0, 1, 0);
3169 manifest_destroy(pTktChng);
3170 style_finish_page();
3171 }
3172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3173
3174 /*
3175 ** WEBPAGE: info
3176 ** URL: info/NAME
3177 **
@@ -3256,10 +3389,14 @@
3256 if( db_exists("SELECT 1 FROM plink WHERE pid=%d", rid) ){
3257 ci_page();
3258 }else
3259 if( db_exists("SELECT 1 FROM attachment WHERE attachid=%d", rid) ){
3260 ainfo_page();
 
 
 
 
3261 }else
3262 {
3263 artifact_page();
3264 }
3265 }
3266
--- 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
@@ -624,11 +625,10 @@
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"
@@ -785,17 +785,12 @@
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);
@@ -820,11 +815,14 @@
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;
@@ -832,11 +830,11 @@
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);
@@ -862,18 +860,20 @@
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 }
@@ -1933,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 ){
@@ -2249,12 +2249,12 @@
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);
@@ -3168,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 **
@@ -3256,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/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
@@ -3297,11 +3298,11 @@
32973298
}
32983299
zInitPage = find_option("page", "p", 1);
32993300
if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
33003301
zFossilCmd = find_option("fossilcmd", 0, 1);
33013302
if( zFrom && zInitPage==0 ){
3302
- zInitPage = mprintf("ckout?exbase=%T", zFrom);
3303
+ zInitPage = mprintf("ckout?exbase=%H", zFrom);
33033304
}
33043305
}
33053306
zNotFound = find_option("notfound", 0, 1);
33063307
allowRepoList = find_option("repolist",0,0)!=0;
33073308
if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1;
33083309
--- 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
@@ -3297,11 +3298,11 @@
3297 }
3298 zInitPage = find_option("page", "p", 1);
3299 if( zInitPage && zInitPage[0]=='/' ) zInitPage++;
3300 zFossilCmd = find_option("fossilcmd", 0, 1);
3301 if( zFrom && zInitPage==0 ){
3302 zInitPage = mprintf("ckout?exbase=%T", zFrom);
3303 }
3304 }
3305 zNotFound = find_option("notfound", 0, 1);
3306 allowRepoList = find_option("repolist",0,0)!=0;
3307 if( find_option("nocompress",0,0)!=0 ) g.fNoHttpCompress = 1;
3308
--- 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
@@ -3297,11 +3298,11 @@
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
+12 -8
--- src/merge.c
+++ src/merge.c
@@ -687,10 +687,11 @@
687687
** -K|--keep-merge-files On merge conflict, retain the temporary files
688688
** used for merging, named *-baseline, *-original,
689689
** and *-merge.
690690
** -n|--dry-run Do not actually change files on disk
691691
** --nosync Do not auto-sync prior to merging
692
+** --noundo Do not record changes in the undo log
692693
** -v|--verbose Show additional details of the merge
693694
*/
694695
void merge_cmd(void){
695696
int vid; /* Current version "V" */
696697
int mid; /* Version we are merging from "M" */
@@ -712,10 +713,11 @@
712713
int nOverwrite = 0; /* Number of unmanaged files overwritten */
713714
char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */
714715
const char *zVersion; /* The VERSION argument */
715716
int bMultiMerge = 0; /* True if there are two or more VERSION arguments */
716717
int nMerge = 0; /* Number of prior merges processed */
718
+ int useUndo = 1; /* True to record changes in the undo log */
717719
Stmt q; /* SQL statment used for merge processing */
718720
719721
720722
/* Notation:
721723
**
@@ -760,10 +762,12 @@
760762
** * The --dry-run option is also useful in combination with --debug.
761763
*/
762764
debugFlag = find_option("debug",0,0)!=0;
763765
if( debugFlag && verboseFlag ) debugFlag = 2;
764766
showVfileFlag = find_option("show-vfile",0,0)!=0;
767
+ useUndo = find_option("noundo",0,0)==0;
768
+ if( dryRunFlag ) useUndo = 0;
765769
766770
verify_all_options();
767771
db_must_be_within_tree();
768772
if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
769773
vid = db_lget_int("checkout", 0);
@@ -926,11 +930,11 @@
926930
integrateFlag ? "integrate:" : "merge-from:");
927931
print_checkin_description(pid, 12, "baseline:");
928932
}
929933
vfile_check_signature(vid, CKSIG_ENOTFILE);
930934
if( nMerge==0 ) db_begin_transaction();
931
- if( !dryRunFlag ) undo_begin();
935
+ if( useUndo ) undo_begin();
932936
if( load_vfile_from_rid(mid) && !forceMissingFlag ){
933937
fossil_fatal("missing content, unable to merge");
934938
}
935939
if( load_vfile_from_rid(pid) && !forceMissingFlag ){
936940
fossil_fatal("missing content, unable to merge");
@@ -1183,12 +1187,12 @@
11831187
int ridm = db_column_int(&q, 1);
11841188
const char *zName = db_column_text(&q, 2);
11851189
int islinkm = db_column_int(&q, 3);
11861190
/* Copy content from idm over into idv. Overwrite idv. */
11871191
fossil_print("UPDATE %s\n", zName);
1192
+ if( useUndo ) undo_save(zName);
11881193
if( !dryRunFlag ){
1189
- undo_save(zName);
11901194
db_multi_exec(
11911195
"UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d,"
11921196
" mhash=CASE WHEN rid<>%d"
11931197
" THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END"
11941198
" WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv
@@ -1265,11 +1269,11 @@
12651269
}else{
12661270
i64 sz;
12671271
const char *zErrMsg = 0;
12681272
int nc = 0;
12691273
1270
- if( !dryRunFlag ) undo_save(zName);
1274
+ if( useUndo ) undo_save(zName);
12711275
zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
12721276
sz = file_size(zFullPath, ExtFILE);
12731277
content_get(ridp, &p);
12741278
content_get(ridm, &m);
12751279
if( isBinary ){
@@ -1352,11 +1356,11 @@
13521356
zErrMsg = "local edits lost";
13531357
zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
13541358
sz = file_size(zFullPath, ExtFILE);
13551359
fossil_free(zFullPath);
13561360
}
1357
- if( !dryRunFlag ) undo_save(zName);
1361
+ if( useUndo ) undo_save(zName);
13581362
db_multi_exec(
13591363
"UPDATE vfile SET deleted=1 WHERE id=%d", idv
13601364
);
13611365
if( !dryRunFlag ){
13621366
char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
@@ -1399,12 +1403,12 @@
13991403
int idv = db_column_int(&q, 0);
14001404
const char *zOldName = db_column_text(&q, 1);
14011405
const char *zNewName = db_column_text(&q, 2);
14021406
int isExe = db_column_int(&q, 3);
14031407
fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
1404
- if( !dryRunFlag ) undo_save(zOldName);
1405
- if( !dryRunFlag ) undo_save(zNewName);
1408
+ if( useUndo ) undo_save(zOldName);
1409
+ if( useUndo ) undo_save(zNewName);
14061410
db_multi_exec(
14071411
"UPDATE mergestat SET fnr=fnm WHERE fnp=%Q",
14081412
zOldName
14091413
);
14101414
db_multi_exec(
@@ -1498,12 +1502,12 @@
14981502
"VALUES('ADDED',%Q,%d,%Q)",
14991503
/* fnm */ zName,
15001504
/* ridm */ db_column_int(&q,2),
15011505
/* fnr */ zName
15021506
);
1507
+ if( useUndo ) undo_save(zName);
15031508
if( !dryRunFlag ){
1504
- undo_save(zName);
15051509
vfile_to_disk(0, idm, 0, 0);
15061510
}
15071511
}
15081512
db_finalize(&q);
15091513
@@ -1558,9 +1562,9 @@
15581562
}
15591563
if( bMultiMerge && nConflict==0 ){
15601564
nMerge++;
15611565
goto merge_next_child;
15621566
}
1563
- if( !dryRunFlag ) undo_finish();
1567
+ if( useUndo ) undo_finish();
15641568
15651569
db_end_transaction(dryRunFlag);
15661570
}
15671571
--- src/merge.c
+++ src/merge.c
@@ -687,10 +687,11 @@
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 ** -v|--verbose Show additional details of the merge
693 */
694 void merge_cmd(void){
695 int vid; /* Current version "V" */
696 int mid; /* Version we are merging from "M" */
@@ -712,10 +713,11 @@
712 int nOverwrite = 0; /* Number of unmanaged files overwritten */
713 char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */
714 const char *zVersion; /* The VERSION argument */
715 int bMultiMerge = 0; /* True if there are two or more VERSION arguments */
716 int nMerge = 0; /* Number of prior merges processed */
 
717 Stmt q; /* SQL statment used for merge processing */
718
719
720 /* Notation:
721 **
@@ -760,10 +762,12 @@
760 ** * The --dry-run option is also useful in combination with --debug.
761 */
762 debugFlag = find_option("debug",0,0)!=0;
763 if( debugFlag && verboseFlag ) debugFlag = 2;
764 showVfileFlag = find_option("show-vfile",0,0)!=0;
 
 
765
766 verify_all_options();
767 db_must_be_within_tree();
768 if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
769 vid = db_lget_int("checkout", 0);
@@ -926,11 +930,11 @@
926 integrateFlag ? "integrate:" : "merge-from:");
927 print_checkin_description(pid, 12, "baseline:");
928 }
929 vfile_check_signature(vid, CKSIG_ENOTFILE);
930 if( nMerge==0 ) db_begin_transaction();
931 if( !dryRunFlag ) undo_begin();
932 if( load_vfile_from_rid(mid) && !forceMissingFlag ){
933 fossil_fatal("missing content, unable to merge");
934 }
935 if( load_vfile_from_rid(pid) && !forceMissingFlag ){
936 fossil_fatal("missing content, unable to merge");
@@ -1183,12 +1187,12 @@
1183 int ridm = db_column_int(&q, 1);
1184 const char *zName = db_column_text(&q, 2);
1185 int islinkm = db_column_int(&q, 3);
1186 /* Copy content from idm over into idv. Overwrite idv. */
1187 fossil_print("UPDATE %s\n", zName);
 
1188 if( !dryRunFlag ){
1189 undo_save(zName);
1190 db_multi_exec(
1191 "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d,"
1192 " mhash=CASE WHEN rid<>%d"
1193 " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END"
1194 " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv
@@ -1265,11 +1269,11 @@
1265 }else{
1266 i64 sz;
1267 const char *zErrMsg = 0;
1268 int nc = 0;
1269
1270 if( !dryRunFlag ) undo_save(zName);
1271 zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
1272 sz = file_size(zFullPath, ExtFILE);
1273 content_get(ridp, &p);
1274 content_get(ridm, &m);
1275 if( isBinary ){
@@ -1352,11 +1356,11 @@
1352 zErrMsg = "local edits lost";
1353 zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
1354 sz = file_size(zFullPath, ExtFILE);
1355 fossil_free(zFullPath);
1356 }
1357 if( !dryRunFlag ) undo_save(zName);
1358 db_multi_exec(
1359 "UPDATE vfile SET deleted=1 WHERE id=%d", idv
1360 );
1361 if( !dryRunFlag ){
1362 char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
@@ -1399,12 +1403,12 @@
1399 int idv = db_column_int(&q, 0);
1400 const char *zOldName = db_column_text(&q, 1);
1401 const char *zNewName = db_column_text(&q, 2);
1402 int isExe = db_column_int(&q, 3);
1403 fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
1404 if( !dryRunFlag ) undo_save(zOldName);
1405 if( !dryRunFlag ) undo_save(zNewName);
1406 db_multi_exec(
1407 "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q",
1408 zOldName
1409 );
1410 db_multi_exec(
@@ -1498,12 +1502,12 @@
1498 "VALUES('ADDED',%Q,%d,%Q)",
1499 /* fnm */ zName,
1500 /* ridm */ db_column_int(&q,2),
1501 /* fnr */ zName
1502 );
 
1503 if( !dryRunFlag ){
1504 undo_save(zName);
1505 vfile_to_disk(0, idm, 0, 0);
1506 }
1507 }
1508 db_finalize(&q);
1509
@@ -1558,9 +1562,9 @@
1558 }
1559 if( bMultiMerge && nConflict==0 ){
1560 nMerge++;
1561 goto merge_next_child;
1562 }
1563 if( !dryRunFlag ) undo_finish();
1564
1565 db_end_transaction(dryRunFlag);
1566 }
1567
--- src/merge.c
+++ src/merge.c
@@ -687,10 +687,11 @@
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" */
@@ -712,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 **
@@ -760,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);
@@ -926,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");
@@ -1183,12 +1187,12 @@
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
@@ -1265,11 +1269,11 @@
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 ){
@@ -1352,11 +1356,11 @@
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);
@@ -1399,12 +1403,12 @@
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(
@@ -1498,12 +1502,12 @@
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
@@ -1558,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
+119 -15
--- src/name.c
+++ src/name.c
@@ -1676,34 +1676,44 @@
16761676
** n=N Show N artifacts
16771677
** s=S Start with artifact number S
16781678
** priv Show only unpublished or private artifacts
16791679
** phan Show only phantom artifacts
16801680
** hclr Color code hash types (SHA1 vs SHA3)
1681
+** recent Show the most recent N artifacts
16811682
*/
16821683
void bloblist_page(void){
16831684
Stmt q;
16841685
int s = atoi(PD("s","0"));
16851686
int n = atoi(PD("n","5000"));
16861687
int mx = db_int(0, "SELECT max(rid) FROM blob");
16871688
int privOnly = PB("priv");
16881689
int phantomOnly = PB("phan");
16891690
int hashClr = PB("hclr");
1691
+ int bRecent = PB("recent");
1692
+ int bUnclst = PB("unclustered");
16901693
char *zRange;
16911694
char *zSha1Bg;
16921695
char *zSha3Bg;
16931696
16941697
login_check_credentials();
16951698
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
16961699
cgi_check_for_malice();
16971700
style_header("List Of Artifacts");
16981701
style_submenu_element("250 Largest", "bigbloblist");
1702
+ if( bRecent==0 || n!=250 ){
1703
+ style_submenu_element("Recent","bloblist?n=250&recent");
1704
+ }
1705
+ if( bUnclst==0 ){
1706
+ style_submenu_element("Unclustered","bloblist?unclustered");
1707
+ }
16991708
if( g.perm.Admin ){
17001709
style_submenu_element("Artifact Log", "rcvfromlist");
17011710
}
17021711
if( !phantomOnly ){
17031712
style_submenu_element("Phantoms", "bloblist?phan");
17041713
}
1714
+ style_submenu_element("Clusters","clusterlist");
17051715
if( g.perm.Private || g.perm.Admin ){
17061716
if( !privOnly ){
17071717
style_submenu_element("Private", "bloblist?priv");
17081718
}
17091719
}else{
@@ -1710,58 +1720,70 @@
17101720
privOnly = 0;
17111721
}
17121722
if( g.perm.Write ){
17131723
style_submenu_element("Artifact Stats", "artifact_stats");
17141724
}
1715
- if( !privOnly && !phantomOnly && mx>n && P("s")==0 ){
1725
+ if( !privOnly && !phantomOnly && mx>n && P("s")==0 && !bRecent && !bUnclst ){
17161726
int i;
17171727
@ <p>Select a range of artifacts to view:</p>
17181728
@ <ul>
17191729
for(i=1; i<=mx; i+=n){
17201730
@ <li> %z(href("%R/bloblist?s=%d&n=%d",i,n))
17211731
@ %d(i)..%d(i+n-1<mx?i+n-1:mx)</a>
17221732
}
1733
+ @ <li> %z(href("%R/bloblist?n=250&recent"))250 most recent</a>
1734
+ @ <li> %z(href("%R/bloblist?unclustered"))All unclustered</a>
17231735
@ </ul>
17241736
style_finish_page();
17251737
return;
17261738
}
17271739
if( phantomOnly || privOnly || mx>n ){
17281740
style_submenu_element("Index", "bloblist");
17291741
}
17301742
if( privOnly ){
1743
+ @ <h2>Private Artifacts</h2>
17311744
zRange = mprintf("IN private");
17321745
}else if( phantomOnly ){
1746
+ @ <h2>Phantom Artifacts</h2>
17331747
zRange = mprintf("IN phantom");
1748
+ }else if( bUnclst ){
1749
+ @ <h2>Unclustered Artifacts</h2>
1750
+ zRange = mprintf("IN unclustered");
1751
+ }else if( bRecent ){
1752
+ @ <h2>%d(n) Most Recent Artifacts</h2>
1753
+ zRange = mprintf(">=(SELECT rid FROM blob"
1754
+ " ORDER BY rid DESC LIMIT 1 OFFSET %d)",n);
17341755
}else{
17351756
zRange = mprintf("BETWEEN %d AND %d", s, s+n-1);
17361757
}
17371758
describe_artifacts(zRange);
17381759
fossil_free(zRange);
17391760
db_prepare(&q,
1740
- "SELECT rid, uuid, summary, isPrivate, type='phantom', rcvid, ref"
1741
- " FROM description ORDER BY rid"
1761
+ /* 0 1 2 3 4 5 6 */
1762
+ "SELECT rid, uuid, summary, isPrivate, type='phantom', ref, rcvid, "
1763
+ " datetime(rcvfrom.mtime)"
1764
+ " FROM description LEFT JOIN rcvfrom USING(rcvid)"
1765
+ " ORDER BY rid %s",
1766
+ ((bRecent||bUnclst)?"DESC":"ASC")/*safe-for-%s*/
17421767
);
17431768
if( skin_detail_boolean("white-foreground") ){
17441769
zSha1Bg = "#714417";
17451770
zSha3Bg = "#177117";
17461771
}else{
17471772
zSha1Bg = "#ebffb0";
17481773
zSha3Bg = "#b0ffb0";
17491774
}
17501775
@ <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
- }
1776
+ @ <tr><th>RID<th>Hash<th>Received<th>Description<th>Ref<th>Remarks
17561777
while( db_step(&q)==SQLITE_ROW ){
17571778
int rid = db_column_int(&q,0);
17581779
const char *zUuid = db_column_text(&q, 1);
17591780
const char *zDesc = db_column_text(&q, 2);
17601781
int isPriv = db_column_int(&q,3);
17611782
int isPhantom = db_column_int(&q,4);
1762
- const char *zRef = db_column_text(&q,6);
1783
+ const char *zRef = db_column_text(&q,5);
1784
+ const char *zDate = db_column_text(&q,7);
17631785
if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){
17641786
/* Don't show private artifacts to users without Private (x) permission */
17651787
continue;
17661788
}
17671789
if( hashClr ){
@@ -1770,16 +1792,14 @@
17701792
}else{
17711793
@ <tr><td align="right">%d(rid)</td>
17721794
}
17731795
@ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
17741796
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
- }
1797
+ int rcvid = db_column_int(&q, 6);
1798
+ @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%h(zDate)</a>
1799
+ }else{
1800
+ @ <td>%h(zDate)
17811801
}
17821802
@ <td align="left">%h(zDesc)</td>
17831803
if( zRef && zRef[0] ){
17841804
@ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a>
17851805
}else{
@@ -2163,5 +2183,89 @@
21632183
" ORDER BY 1");
21642184
@ <h1>Hash Prefix Collisions on All Artifacts</h1>
21652185
collision_report("SELECT uuid FROM blob ORDER BY 1");
21662186
style_finish_page();
21672187
}
2188
+
2189
+/*
2190
+** WEBPAGE: clusterlist
2191
+**
2192
+** Show information about all cluster artifacts in the database.
2193
+*/
2194
+void clusterlist_page(void){
2195
+ Stmt q;
2196
+ int cnt = 1;
2197
+ sqlite3_int64 szTotal = 0;
2198
+ sqlite3_int64 szCTotal = 0;
2199
+ login_check_credentials();
2200
+ if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2201
+ style_header("All Cluster Artifacts");
2202
+ style_submenu_element("All Artifactst", "bloblist");
2203
+ if( g.perm.Admin ){
2204
+ style_submenu_element("Artifact Log", "rcvfromlist");
2205
+ }
2206
+ style_submenu_element("Phantoms", "bloblist?phan");
2207
+ if( g.perm.Write ){
2208
+ style_submenu_element("Artifact Stats", "artifact_stats");
2209
+ }
2210
+
2211
+ db_prepare(&q,
2212
+ "SELECT blob.uuid, "
2213
+ " blob.size, "
2214
+ " octet_length(blob.content), "
2215
+ " datetime(rcvfrom.mtime),"
2216
+ " user.login,"
2217
+ " rcvfrom.ipaddr"
2218
+ " FROM tagxref JOIN blob ON tagxref.rid=blob.rid"
2219
+ " LEFT JOIN rcvfrom ON blob.rcvid=rcvfrom.rcvid"
2220
+ " LEFT JOIN user ON user.uid=rcvfrom.uid"
2221
+ " WHERE tagxref.tagid=%d"
2222
+ " ORDER BY rcvfrom.mtime, blob.uuid",
2223
+ TAG_CLUSTER
2224
+ );
2225
+ @ <table cellpadding="2" cellspacing="0" border="1">
2226
+ @ <tr><th>&nbsp;
2227
+ @ <th>Hash
2228
+ @ <th>Date&nbsp;Received
2229
+ @ <th>Size
2230
+ @ <th>Compressed&nbsp;Size
2231
+ if( g.perm.Admin ){
2232
+ @ <th>User<th>IP-Address
2233
+ }
2234
+ while( db_step(&q)==SQLITE_ROW ){
2235
+ const char *zUuid = db_column_text(&q, 0);
2236
+ sqlite3_int64 sz = db_column_int64(&q, 1);
2237
+ sqlite3_int64 szC = db_column_int64(&q, 2);
2238
+ const char *zDate = db_column_text(&q, 3);
2239
+ const char *zUser = db_column_text(&q, 4);
2240
+ const char *zIp = db_column_text(&q, 5);
2241
+ szTotal += sz;
2242
+ szCTotal += szC;
2243
+ @ <tr><td align="right">%d(cnt++)
2244
+ @ <td><a href="%R/info/%S(zUuid)">%S(zUuid)</a>
2245
+ if( zDate ){
2246
+ @ <td>%h(zDate)
2247
+ }else{
2248
+ @ <td>&nbsp;
2249
+ }
2250
+ @ <td align="right">%,lld(sz)
2251
+ @ <td align="right">%,lld(szC)
2252
+ if( g.perm.Admin ){
2253
+ if( zUser ){
2254
+ @ <td>%h(zUser)
2255
+ }else{
2256
+ @ <td>&nbsp;
2257
+ }
2258
+ if( zIp ){
2259
+ @ <td>%h(zIp)
2260
+ }else{
2261
+ @ <td>&nbsp;
2262
+ }
2263
+ }
2264
+ @ </tr>
2265
+ }
2266
+ @ </table>
2267
+ db_finalize(&q);
2268
+ @ <p>Total size of all clusters: %,lld(szTotal) bytes,
2269
+ @ %,lld(szCTotal) bytes compressed</p>
2270
+ style_finish_page();
2271
+}
21682272
--- src/name.c
+++ src/name.c
@@ -1676,34 +1676,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 +1720,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 +1792,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 +2183,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
@@ -1676,34 +1676,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 ** recent Show the most recent N artifacts
1682 */
1683 void bloblist_page(void){
1684 Stmt q;
1685 int s = atoi(PD("s","0"));
1686 int n = atoi(PD("n","5000"));
1687 int mx = db_int(0, "SELECT max(rid) FROM blob");
1688 int privOnly = PB("priv");
1689 int phantomOnly = PB("phan");
1690 int hashClr = PB("hclr");
1691 int bRecent = PB("recent");
1692 int bUnclst = PB("unclustered");
1693 char *zRange;
1694 char *zSha1Bg;
1695 char *zSha3Bg;
1696
1697 login_check_credentials();
1698 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
1699 cgi_check_for_malice();
1700 style_header("List Of Artifacts");
1701 style_submenu_element("250 Largest", "bigbloblist");
1702 if( bRecent==0 || n!=250 ){
1703 style_submenu_element("Recent","bloblist?n=250&recent");
1704 }
1705 if( bUnclst==0 ){
1706 style_submenu_element("Unclustered","bloblist?unclustered");
1707 }
1708 if( g.perm.Admin ){
1709 style_submenu_element("Artifact Log", "rcvfromlist");
1710 }
1711 if( !phantomOnly ){
1712 style_submenu_element("Phantoms", "bloblist?phan");
1713 }
1714 style_submenu_element("Clusters","clusterlist");
1715 if( g.perm.Private || g.perm.Admin ){
1716 if( !privOnly ){
1717 style_submenu_element("Private", "bloblist?priv");
1718 }
1719 }else{
@@ -1710,58 +1720,70 @@
1720 privOnly = 0;
1721 }
1722 if( g.perm.Write ){
1723 style_submenu_element("Artifact Stats", "artifact_stats");
1724 }
1725 if( !privOnly && !phantomOnly && mx>n && P("s")==0 && !bRecent && !bUnclst ){
1726 int i;
1727 @ <p>Select a range of artifacts to view:</p>
1728 @ <ul>
1729 for(i=1; i<=mx; i+=n){
1730 @ <li> %z(href("%R/bloblist?s=%d&n=%d",i,n))
1731 @ %d(i)..%d(i+n-1<mx?i+n-1:mx)</a>
1732 }
1733 @ <li> %z(href("%R/bloblist?n=250&recent"))250 most recent</a>
1734 @ <li> %z(href("%R/bloblist?unclustered"))All unclustered</a>
1735 @ </ul>
1736 style_finish_page();
1737 return;
1738 }
1739 if( phantomOnly || privOnly || mx>n ){
1740 style_submenu_element("Index", "bloblist");
1741 }
1742 if( privOnly ){
1743 @ <h2>Private Artifacts</h2>
1744 zRange = mprintf("IN private");
1745 }else if( phantomOnly ){
1746 @ <h2>Phantom Artifacts</h2>
1747 zRange = mprintf("IN phantom");
1748 }else if( bUnclst ){
1749 @ <h2>Unclustered Artifacts</h2>
1750 zRange = mprintf("IN unclustered");
1751 }else if( bRecent ){
1752 @ <h2>%d(n) Most Recent Artifacts</h2>
1753 zRange = mprintf(">=(SELECT rid FROM blob"
1754 " ORDER BY rid DESC LIMIT 1 OFFSET %d)",n);
1755 }else{
1756 zRange = mprintf("BETWEEN %d AND %d", s, s+n-1);
1757 }
1758 describe_artifacts(zRange);
1759 fossil_free(zRange);
1760 db_prepare(&q,
1761 /* 0 1 2 3 4 5 6 */
1762 "SELECT rid, uuid, summary, isPrivate, type='phantom', ref, rcvid, "
1763 " datetime(rcvfrom.mtime)"
1764 " FROM description LEFT JOIN rcvfrom USING(rcvid)"
1765 " ORDER BY rid %s",
1766 ((bRecent||bUnclst)?"DESC":"ASC")/*safe-for-%s*/
1767 );
1768 if( skin_detail_boolean("white-foreground") ){
1769 zSha1Bg = "#714417";
1770 zSha3Bg = "#177117";
1771 }else{
1772 zSha1Bg = "#ebffb0";
1773 zSha3Bg = "#b0ffb0";
1774 }
1775 @ <table cellpadding="2" cellspacing="0" border="1">
1776 @ <tr><th>RID<th>Hash<th>Received<th>Description<th>Ref<th>Remarks
 
 
 
 
1777 while( db_step(&q)==SQLITE_ROW ){
1778 int rid = db_column_int(&q,0);
1779 const char *zUuid = db_column_text(&q, 1);
1780 const char *zDesc = db_column_text(&q, 2);
1781 int isPriv = db_column_int(&q,3);
1782 int isPhantom = db_column_int(&q,4);
1783 const char *zRef = db_column_text(&q,5);
1784 const char *zDate = db_column_text(&q,7);
1785 if( isPriv && !isPhantom && !g.perm.Private && !g.perm.Admin ){
1786 /* Don't show private artifacts to users without Private (x) permission */
1787 continue;
1788 }
1789 if( hashClr ){
@@ -1770,16 +1792,14 @@
1792 }else{
1793 @ <tr><td align="right">%d(rid)</td>
1794 }
1795 @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
1796 if( g.perm.Admin ){
1797 int rcvid = db_column_int(&q, 6);
1798 @ <td><a href='%R/rcvfrom?rcvid=%d(rcvid)'>%h(zDate)</a>
1799 }else{
1800 @ <td>%h(zDate)
 
 
1801 }
1802 @ <td align="left">%h(zDesc)</td>
1803 if( zRef && zRef[0] ){
1804 @ <td>%z(href("%R/info/%!S",zRef))%S(zRef)</a>
1805 }else{
@@ -2163,5 +2183,89 @@
2183 " ORDER BY 1");
2184 @ <h1>Hash Prefix Collisions on All Artifacts</h1>
2185 collision_report("SELECT uuid FROM blob ORDER BY 1");
2186 style_finish_page();
2187 }
2188
2189 /*
2190 ** WEBPAGE: clusterlist
2191 **
2192 ** Show information about all cluster artifacts in the database.
2193 */
2194 void clusterlist_page(void){
2195 Stmt q;
2196 int cnt = 1;
2197 sqlite3_int64 szTotal = 0;
2198 sqlite3_int64 szCTotal = 0;
2199 login_check_credentials();
2200 if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
2201 style_header("All Cluster Artifacts");
2202 style_submenu_element("All Artifactst", "bloblist");
2203 if( g.perm.Admin ){
2204 style_submenu_element("Artifact Log", "rcvfromlist");
2205 }
2206 style_submenu_element("Phantoms", "bloblist?phan");
2207 if( g.perm.Write ){
2208 style_submenu_element("Artifact Stats", "artifact_stats");
2209 }
2210
2211 db_prepare(&q,
2212 "SELECT blob.uuid, "
2213 " blob.size, "
2214 " octet_length(blob.content), "
2215 " datetime(rcvfrom.mtime),"
2216 " user.login,"
2217 " rcvfrom.ipaddr"
2218 " FROM tagxref JOIN blob ON tagxref.rid=blob.rid"
2219 " LEFT JOIN rcvfrom ON blob.rcvid=rcvfrom.rcvid"
2220 " LEFT JOIN user ON user.uid=rcvfrom.uid"
2221 " WHERE tagxref.tagid=%d"
2222 " ORDER BY rcvfrom.mtime, blob.uuid",
2223 TAG_CLUSTER
2224 );
2225 @ <table cellpadding="2" cellspacing="0" border="1">
2226 @ <tr><th>&nbsp;
2227 @ <th>Hash
2228 @ <th>Date&nbsp;Received
2229 @ <th>Size
2230 @ <th>Compressed&nbsp;Size
2231 if( g.perm.Admin ){
2232 @ <th>User<th>IP-Address
2233 }
2234 while( db_step(&q)==SQLITE_ROW ){
2235 const char *zUuid = db_column_text(&q, 0);
2236 sqlite3_int64 sz = db_column_int64(&q, 1);
2237 sqlite3_int64 szC = db_column_int64(&q, 2);
2238 const char *zDate = db_column_text(&q, 3);
2239 const char *zUser = db_column_text(&q, 4);
2240 const char *zIp = db_column_text(&q, 5);
2241 szTotal += sz;
2242 szCTotal += szC;
2243 @ <tr><td align="right">%d(cnt++)
2244 @ <td><a href="%R/info/%S(zUuid)">%S(zUuid)</a>
2245 if( zDate ){
2246 @ <td>%h(zDate)
2247 }else{
2248 @ <td>&nbsp;
2249 }
2250 @ <td align="right">%,lld(sz)
2251 @ <td align="right">%,lld(szC)
2252 if( g.perm.Admin ){
2253 if( zUser ){
2254 @ <td>%h(zUser)
2255 }else{
2256 @ <td>&nbsp;
2257 }
2258 if( zIp ){
2259 @ <td>%h(zIp)
2260 }else{
2261 @ <td>&nbsp;
2262 }
2263 }
2264 @ </tr>
2265 }
2266 @ </table>
2267 db_finalize(&q);
2268 @ <p>Total size of all clusters: %,lld(szTotal) bytes,
2269 @ %,lld(szCTotal) bytes compressed</p>
2270 style_finish_page();
2271 }
2272
+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
+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
+1 -1
--- src/sitemap.c
+++ src/sitemap.c
@@ -116,11 +116,11 @@
116116
if( inSublist ){
117117
@ </ul>
118118
inSublist = 0;
119119
}
120120
@ </li>
121
- if( db_open_local(0) && cgi_is_loopback(g.zIpAddr) ){
121
+ if( cgi_is_loopback(g.zIpAddr) && db_open_local(0) ){
122122
@ <li>%z(href("%R/ckout"))Checkout Status</a></li>
123123
}
124124
if( g.perm.Read ){
125125
const char *zEditGlob = db_get("fileedit-glob","");
126126
@ <li>%z(href("%R/tree"))File Browser</a>
127127
--- src/sitemap.c
+++ src/sitemap.c
@@ -116,11 +116,11 @@
116 if( inSublist ){
117 @ </ul>
118 inSublist = 0;
119 }
120 @ </li>
121 if( db_open_local(0) && cgi_is_loopback(g.zIpAddr) ){
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
--- src/sitemap.c
+++ src/sitemap.c
@@ -116,11 +116,11 @@
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
--- 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
+31 -4
--- src/timeline.c
+++ src/timeline.c
@@ -1996,11 +1996,11 @@
19961996
zMatchStyle = "brlist";
19971997
}
19981998
}
19991999
20002000
/* Convert r=TAG to t=TAG&rel in order to populate the UI style widgets. */
2001
- if( zBrName && !related ){
2001
+ if( zBrName ){
20022002
cgi_delete_query_parameter("r");
20032003
cgi_set_query_parameter("t", zBrName); (void)P("t");
20042004
cgi_set_query_parameter("rel", "1");
20052005
zTagName = zBrName;
20062006
related = 1;
@@ -2207,10 +2207,12 @@
22072207
PathNode *p = 0;
22082208
const char *zFrom = 0;
22092209
const char *zTo = 0;
22102210
Blob ins;
22112211
int nNodeOnPath = 0;
2212
+ int commonAncs = 0; /* Common ancestors of me_rid and you_rid. */
2213
+ int earlierRid = 0, laterRid = 0;
22122214
22132215
if( from_rid && to_rid ){
22142216
if( from_to_mode==0 ){
22152217
p = path_shortest(from_rid, to_rid, noMerge, 0, 0);
22162218
}else if( from_to_mode==1 ){
@@ -2219,15 +2221,25 @@
22192221
p = path_shortest(to_rid, from_rid, 0, 1, 0);
22202222
}
22212223
zFrom = P("from");
22222224
zTo = zTo2 ? zTo2 : P("to");
22232225
}else{
2224
- if( path_common_ancestor(me_rid, you_rid) ){
2226
+ commonAncs = path_common_ancestor(me_rid, you_rid);
2227
+ if( commonAncs!=0 ){
22252228
p = path_first();
22262229
}
2227
- zFrom = P("me");
2228
- zTo = P("you");
2230
+ if( commonAncs==you_rid ){
2231
+ zFrom = P("you");
2232
+ zTo = P("me");
2233
+ earlierRid = you_rid;
2234
+ laterRid = me_rid;
2235
+ }else{
2236
+ zFrom = P("me");
2237
+ zTo = P("you");
2238
+ earlierRid = me_rid;
2239
+ laterRid = you_rid;
2240
+ }
22292241
}
22302242
blob_init(&ins, 0, 0);
22312243
db_multi_exec(
22322244
"CREATE TABLE IF NOT EXISTS temp.pathnode(x INTEGER PRIMARY KEY);"
22332245
);
@@ -2266,10 +2278,23 @@
22662278
" SELECT childid FROM cherrypick WHERE parentid IN pathnode;"
22672279
);
22682280
}
22692281
}
22702282
db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related");
2283
+ if( earlierRid && laterRid && commonAncs==earlierRid ){
2284
+ /* On a query with me=XXX, you=YYY, and rel, omit all nodes that
2285
+ ** are not ancestors of either XXX or YYY, as those nodes tend to
2286
+ ** be extraneous */
2287
+ db_multi_exec(
2288
+ "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
2289
+ );
2290
+ compute_ancestors(laterRid, 0, 0, earlierRid);
2291
+ db_multi_exec(
2292
+ "DELETE FROM related WHERE x NOT IN ok;"
2293
+ "DELETE FROM pathnode WHERE x NOT IN ok;"
2294
+ );
2295
+ }
22712296
}
22722297
blob_append_sql(&sql, " AND event.objid IN pathnode");
22732298
if( zChng && zChng[0] ){
22742299
db_multi_exec(
22752300
"DELETE FROM pathnode "
@@ -3077,10 +3102,12 @@
30773102
if( zOlderButton ){
30783103
@ %z(chref("button","%s",zOlderButton))%h(zOlderButtonLabel)\
30793104
@ &nbsp;&darr;</a>
30803105
}
30813106
document_emit_js(/*handles pikchrs rendered above*/);
3107
+ blob_reset(&sql);
3108
+ blob_reset(&desc);
30823109
style_finish_page();
30833110
}
30843111
30853112
/*
30863113
** Translate a timeline entry into the printable format by
30873114
--- src/timeline.c
+++ src/timeline.c
@@ -1996,11 +1996,11 @@
1996 zMatchStyle = "brlist";
1997 }
1998 }
1999
2000 /* Convert r=TAG to t=TAG&rel in order to populate the UI style widgets. */
2001 if( zBrName && !related ){
2002 cgi_delete_query_parameter("r");
2003 cgi_set_query_parameter("t", zBrName); (void)P("t");
2004 cgi_set_query_parameter("rel", "1");
2005 zTagName = zBrName;
2006 related = 1;
@@ -2207,10 +2207,12 @@
2207 PathNode *p = 0;
2208 const char *zFrom = 0;
2209 const char *zTo = 0;
2210 Blob ins;
2211 int nNodeOnPath = 0;
 
 
2212
2213 if( from_rid && to_rid ){
2214 if( from_to_mode==0 ){
2215 p = path_shortest(from_rid, to_rid, noMerge, 0, 0);
2216 }else if( from_to_mode==1 ){
@@ -2219,15 +2221,25 @@
2219 p = path_shortest(to_rid, from_rid, 0, 1, 0);
2220 }
2221 zFrom = P("from");
2222 zTo = zTo2 ? zTo2 : P("to");
2223 }else{
2224 if( path_common_ancestor(me_rid, you_rid) ){
 
2225 p = path_first();
2226 }
2227 zFrom = P("me");
2228 zTo = P("you");
 
 
 
 
 
 
 
 
 
2229 }
2230 blob_init(&ins, 0, 0);
2231 db_multi_exec(
2232 "CREATE TABLE IF NOT EXISTS temp.pathnode(x INTEGER PRIMARY KEY);"
2233 );
@@ -2266,10 +2278,23 @@
2266 " SELECT childid FROM cherrypick WHERE parentid IN pathnode;"
2267 );
2268 }
2269 }
2270 db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related");
 
 
 
 
 
 
 
 
 
 
 
 
 
2271 }
2272 blob_append_sql(&sql, " AND event.objid IN pathnode");
2273 if( zChng && zChng[0] ){
2274 db_multi_exec(
2275 "DELETE FROM pathnode "
@@ -3077,10 +3102,12 @@
3077 if( zOlderButton ){
3078 @ %z(chref("button","%s",zOlderButton))%h(zOlderButtonLabel)\
3079 @ &nbsp;&darr;</a>
3080 }
3081 document_emit_js(/*handles pikchrs rendered above*/);
 
 
3082 style_finish_page();
3083 }
3084
3085 /*
3086 ** Translate a timeline entry into the printable format by
3087
--- src/timeline.c
+++ src/timeline.c
@@ -1996,11 +1996,11 @@
1996 zMatchStyle = "brlist";
1997 }
1998 }
1999
2000 /* Convert r=TAG to t=TAG&rel in order to populate the UI style widgets. */
2001 if( zBrName ){
2002 cgi_delete_query_parameter("r");
2003 cgi_set_query_parameter("t", zBrName); (void)P("t");
2004 cgi_set_query_parameter("rel", "1");
2005 zTagName = zBrName;
2006 related = 1;
@@ -2207,10 +2207,12 @@
2207 PathNode *p = 0;
2208 const char *zFrom = 0;
2209 const char *zTo = 0;
2210 Blob ins;
2211 int nNodeOnPath = 0;
2212 int commonAncs = 0; /* Common ancestors of me_rid and you_rid. */
2213 int earlierRid = 0, laterRid = 0;
2214
2215 if( from_rid && to_rid ){
2216 if( from_to_mode==0 ){
2217 p = path_shortest(from_rid, to_rid, noMerge, 0, 0);
2218 }else if( from_to_mode==1 ){
@@ -2219,15 +2221,25 @@
2221 p = path_shortest(to_rid, from_rid, 0, 1, 0);
2222 }
2223 zFrom = P("from");
2224 zTo = zTo2 ? zTo2 : P("to");
2225 }else{
2226 commonAncs = path_common_ancestor(me_rid, you_rid);
2227 if( commonAncs!=0 ){
2228 p = path_first();
2229 }
2230 if( commonAncs==you_rid ){
2231 zFrom = P("you");
2232 zTo = P("me");
2233 earlierRid = you_rid;
2234 laterRid = me_rid;
2235 }else{
2236 zFrom = P("me");
2237 zTo = P("you");
2238 earlierRid = me_rid;
2239 laterRid = you_rid;
2240 }
2241 }
2242 blob_init(&ins, 0, 0);
2243 db_multi_exec(
2244 "CREATE TABLE IF NOT EXISTS temp.pathnode(x INTEGER PRIMARY KEY);"
2245 );
@@ -2266,10 +2278,23 @@
2278 " SELECT childid FROM cherrypick WHERE parentid IN pathnode;"
2279 );
2280 }
2281 }
2282 db_multi_exec("INSERT OR IGNORE INTO pathnode SELECT x FROM related");
2283 if( earlierRid && laterRid && commonAncs==earlierRid ){
2284 /* On a query with me=XXX, you=YYY, and rel, omit all nodes that
2285 ** are not ancestors of either XXX or YYY, as those nodes tend to
2286 ** be extraneous */
2287 db_multi_exec(
2288 "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY)"
2289 );
2290 compute_ancestors(laterRid, 0, 0, earlierRid);
2291 db_multi_exec(
2292 "DELETE FROM related WHERE x NOT IN ok;"
2293 "DELETE FROM pathnode WHERE x NOT IN ok;"
2294 );
2295 }
2296 }
2297 blob_append_sql(&sql, " AND event.objid IN pathnode");
2298 if( zChng && zChng[0] ){
2299 db_multi_exec(
2300 "DELETE FROM pathnode "
@@ -3077,10 +3102,12 @@
3102 if( zOlderButton ){
3103 @ %z(chref("button","%s",zOlderButton))%h(zOlderButtonLabel)\
3104 @ &nbsp;&darr;</a>
3105 }
3106 document_emit_js(/*handles pikchrs rendered above*/);
3107 blob_reset(&sql);
3108 blob_reset(&desc);
3109 style_finish_page();
3110 }
3111
3112 /*
3113 ** Translate a timeline entry into the printable format by
3114
+11 -4
--- src/update.c
+++ src/update.c
@@ -854,10 +854,11 @@
854854
**
855855
** If a file is reverted accidentally, it can be restored using
856856
** the "fossil undo" command.
857857
**
858858
** Options:
859
+** --noundo Do not record changes in the undo/redo log.
859860
** -r|--revision VERSION Revert given FILE(s) back to given
860861
** VERSION
861862
**
862863
** See also: [[redo]], [[undo]], [[checkout]], [[update]]
863864
*/
@@ -867,17 +868,19 @@
867868
ManifestFile *pCoFile; /* File within current check-out manifest */
868869
ManifestFile *pRvFile; /* File within revert version manifest */
869870
const char *zFile; /* Filename relative to check-out root */
870871
const char *zRevision; /* Selected revert version, NULL if current */
871872
Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */
873
+ int useUndo = 1; /* True to record changes in UNDO */
872874
int i;
873875
Stmt q;
874876
int revertAll = 0;
875877
int revisionOptNotSupported = 0;
876878
877879
undo_capture_command_line();
878880
zRevision = find_option("revision", "r", 1);
881
+ useUndo = find_option("noundo", 0, 0)==0;
879882
verify_all_options();
880883
881884
if( g.argc<2 ){
882885
usage("?OPTIONS? [FILE] ...");
883886
}
@@ -890,11 +893,15 @@
890893
/* Get manifests of revert version and (if different) current check-out. */
891894
pRvManifest = historical_manifest(zRevision);
892895
pCoManifest = zRevision ? historical_manifest(0) : 0;
893896
894897
db_begin_transaction();
895
- undo_begin();
898
+ if( useUndo ){
899
+ undo_begin();
900
+ }else{
901
+ undo_reset();
902
+ }
896903
db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);");
897904
898905
if( g.argc>2 ){
899906
for(i=2; i<g.argc; i++){
900907
Blob fname;
@@ -987,11 +994,11 @@
987994
if( !pRvFile ){
988995
if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q",
989996
zFile, zFile)==0 ){
990997
fossil_print("UNMANAGE %s\n", zFile);
991998
}else{
992
- undo_save(zFile);
999
+ if( useUndo ) undo_save(zFile);
9931000
file_delete(zFull);
9941001
fossil_print("DELETE %s\n", zFile);
9951002
}
9961003
db_multi_exec(
9971004
"UPDATE OR REPLACE vfile"
@@ -1014,11 +1021,11 @@
10141021
}
10151022
10161023
/* Get contents of reverted-to file. */
10171024
content_get(fast_uuid_to_rid(pRvFile->zUuid), &record);
10181025
1019
- undo_save(zFile);
1026
+ if( useUndo ) undo_save(zFile);
10201027
if( file_size(zFull, RepoFILE)>=0
10211028
&& (rvPerm==PERM_LNK || file_islink(0))
10221029
){
10231030
file_delete(zFull);
10241031
}
@@ -1040,12 +1047,12 @@
10401047
}
10411048
blob_reset(&record);
10421049
free(zFull);
10431050
}
10441051
db_finalize(&q);
1045
- undo_finish();
1052
+ if( useUndo) undo_finish();
10461053
db_end_transaction(0);
10471054
10481055
/* Deallocate parsed manifest structures. */
10491056
manifest_destroy(pRvManifest);
10501057
manifest_destroy(pCoManifest);
10511058
}
10521059
--- src/update.c
+++ src/update.c
@@ -854,10 +854,11 @@
854 **
855 ** If a file is reverted accidentally, it can be restored using
856 ** the "fossil undo" command.
857 **
858 ** Options:
 
859 ** -r|--revision VERSION Revert given FILE(s) back to given
860 ** VERSION
861 **
862 ** See also: [[redo]], [[undo]], [[checkout]], [[update]]
863 */
@@ -867,17 +868,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 i;
873 Stmt q;
874 int revertAll = 0;
875 int revisionOptNotSupported = 0;
876
877 undo_capture_command_line();
878 zRevision = find_option("revision", "r", 1);
 
879 verify_all_options();
880
881 if( g.argc<2 ){
882 usage("?OPTIONS? [FILE] ...");
883 }
@@ -890,11 +893,15 @@
890 /* Get manifests of revert version and (if different) current check-out. */
891 pRvManifest = historical_manifest(zRevision);
892 pCoManifest = zRevision ? historical_manifest(0) : 0;
893
894 db_begin_transaction();
895 undo_begin();
 
 
 
 
896 db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);");
897
898 if( g.argc>2 ){
899 for(i=2; i<g.argc; i++){
900 Blob fname;
@@ -987,11 +994,11 @@
987 if( !pRvFile ){
988 if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q",
989 zFile, zFile)==0 ){
990 fossil_print("UNMANAGE %s\n", zFile);
991 }else{
992 undo_save(zFile);
993 file_delete(zFull);
994 fossil_print("DELETE %s\n", zFile);
995 }
996 db_multi_exec(
997 "UPDATE OR REPLACE vfile"
@@ -1014,11 +1021,11 @@
1014 }
1015
1016 /* Get contents of reverted-to file. */
1017 content_get(fast_uuid_to_rid(pRvFile->zUuid), &record);
1018
1019 undo_save(zFile);
1020 if( file_size(zFull, RepoFILE)>=0
1021 && (rvPerm==PERM_LNK || file_islink(0))
1022 ){
1023 file_delete(zFull);
1024 }
@@ -1040,12 +1047,12 @@
1040 }
1041 blob_reset(&record);
1042 free(zFull);
1043 }
1044 db_finalize(&q);
1045 undo_finish();
1046 db_end_transaction(0);
1047
1048 /* Deallocate parsed manifest structures. */
1049 manifest_destroy(pRvManifest);
1050 manifest_destroy(pCoManifest);
1051 }
1052
--- src/update.c
+++ src/update.c
@@ -854,10 +854,11 @@
854 **
855 ** If a file is reverted accidentally, it can be restored using
856 ** the "fossil undo" command.
857 **
858 ** Options:
859 ** --noundo Do not record changes in the undo/redo log.
860 ** -r|--revision VERSION Revert given FILE(s) back to given
861 ** VERSION
862 **
863 ** See also: [[redo]], [[undo]], [[checkout]], [[update]]
864 */
@@ -867,17 +868,19 @@
868 ManifestFile *pCoFile; /* File within current check-out manifest */
869 ManifestFile *pRvFile; /* File within revert version manifest */
870 const char *zFile; /* Filename relative to check-out root */
871 const char *zRevision; /* Selected revert version, NULL if current */
872 Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */
873 int useUndo = 1; /* True to record changes in UNDO */
874 int i;
875 Stmt q;
876 int revertAll = 0;
877 int revisionOptNotSupported = 0;
878
879 undo_capture_command_line();
880 zRevision = find_option("revision", "r", 1);
881 useUndo = find_option("noundo", 0, 0)==0;
882 verify_all_options();
883
884 if( g.argc<2 ){
885 usage("?OPTIONS? [FILE] ...");
886 }
@@ -890,11 +893,15 @@
893 /* Get manifests of revert version and (if different) current check-out. */
894 pRvManifest = historical_manifest(zRevision);
895 pCoManifest = zRevision ? historical_manifest(0) : 0;
896
897 db_begin_transaction();
898 if( useUndo ){
899 undo_begin();
900 }else{
901 undo_reset();
902 }
903 db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);");
904
905 if( g.argc>2 ){
906 for(i=2; i<g.argc; i++){
907 Blob fname;
@@ -987,11 +994,11 @@
994 if( !pRvFile ){
995 if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q",
996 zFile, zFile)==0 ){
997 fossil_print("UNMANAGE %s\n", zFile);
998 }else{
999 if( useUndo ) undo_save(zFile);
1000 file_delete(zFull);
1001 fossil_print("DELETE %s\n", zFile);
1002 }
1003 db_multi_exec(
1004 "UPDATE OR REPLACE vfile"
@@ -1014,11 +1021,11 @@
1021 }
1022
1023 /* Get contents of reverted-to file. */
1024 content_get(fast_uuid_to_rid(pRvFile->zUuid), &record);
1025
1026 if( useUndo ) undo_save(zFile);
1027 if( file_size(zFull, RepoFILE)>=0
1028 && (rvPerm==PERM_LNK || file_islink(0))
1029 ){
1030 file_delete(zFull);
1031 }
@@ -1040,12 +1047,12 @@
1047 }
1048 blob_reset(&record);
1049 free(zFull);
1050 }
1051 db_finalize(&q);
1052 if( useUndo) undo_finish();
1053 db_end_transaction(0);
1054
1055 /* Deallocate parsed manifest structures. */
1056 manifest_destroy(pRvManifest);
1057 manifest_destroy(pCoManifest);
1058 }
1059
+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 \
342343
--- 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 \
342
--- 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 \
343
--- 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/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()' fail with an assertion that
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()' fail with an assertion that
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
--- www/changes.wiki
+++ www/changes.wiki
@@ -16,10 +16,18 @@
1616
merge or update operation.
1717
* Issue a warning if a user tries to commit on a check-in where the
1818
branch has been changed.
1919
* When a merge conflict occurs, a new section is added to the conflict
2020
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
+ * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
24
+ and debugging
25
+ * Fix a bug in [/help?cmd=patch|fossil patch create] that causes
26
+ [/help?cmd=revert|fossil revert] operations that happened on individual
27
+ files after a [/help?cmd=merge|fossil merge] to be omitted from the
28
+ patch.
2129
2230
<h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
2331
2432
* The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
2533
that have non-ASCII filenames
2634
--- www/changes.wiki
+++ www/changes.wiki
@@ -16,10 +16,18 @@
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
22 <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
23
24 * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
25 that have non-ASCII filenames
26
--- www/changes.wiki
+++ www/changes.wiki
@@ -16,10 +16,18 @@
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 * Added the [/help?cmd=/clusterlist|/clusterlist page] for analysis
24 and debugging
25 * Fix a bug in [/help?cmd=patch|fossil patch create] that causes
26 [/help?cmd=revert|fossil revert] operations that happened on individual
27 files after a [/help?cmd=merge|fossil merge] to be omitted from the
28 patch.
29
30 <h2 id='v2_25'>Changes for version 2.25 (2024-11-06)</h2>
31
32 * The "[/help?cmd=ui|fossil ui /]" command now works even for repositories
33 that have non-ASCII filenames
34
+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